aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
authorEugene Sandulenko2016-09-10 16:55:13 +0200
committerGitHub2016-09-10 16:55:13 +0200
commita46cc8597cdc2ab2aca146ed79c0fcf3f4df5617 (patch)
tree8d56828835eb1124d3d268e4e934e7957123ca3a /engines
parentd3d0819b00c0dddcb35d99561c2eeadf04791190 (diff)
parent4099814fbe6894931b7b393dc2f6ef5736297338 (diff)
downloadscummvm-rg350-a46cc8597cdc2ab2aca146ed79c0fcf3f4df5617.tar.gz
scummvm-rg350-a46cc8597cdc2ab2aca146ed79c0fcf3f4df5617.tar.bz2
scummvm-rg350-a46cc8597cdc2ab2aca146ed79c0fcf3f4df5617.zip
Merge pull request #817 from WinterGrascph/dm
DM: New Engine
Diffstat (limited to 'engines')
-rw-r--r--engines/dm/.gitattributes10
-rw-r--r--engines/dm/TODOs/methodtree.txt215
-rw-r--r--engines/dm/TODOs/todo.txt21
-rw-r--r--engines/dm/champion.cpp2607
-rw-r--r--engines/dm/champion.h576
-rw-r--r--engines/dm/configure.engine3
-rw-r--r--engines/dm/console.cpp283
-rw-r--r--engines/dm/console.h59
-rw-r--r--engines/dm/detection.cpp175
-rw-r--r--engines/dm/dialog.cpp257
-rw-r--r--engines/dm/dialog.h61
-rw-r--r--engines/dm/dm.cpp1050
-rw-r--r--engines/dm/dm.h331
-rw-r--r--engines/dm/dmglobals.cpp58
-rw-r--r--engines/dm/dungeonman.cpp1672
-rw-r--r--engines/dm/dungeonman.h740
-rw-r--r--engines/dm/eventman.cpp1659
-rw-r--r--engines/dm/eventman.h326
-rw-r--r--engines/dm/gfx.cpp3861
-rw-r--r--engines/dm/gfx.h832
-rw-r--r--engines/dm/group.cpp2036
-rw-r--r--engines/dm/group.h252
-rw-r--r--engines/dm/inventory.cpp1072
-rw-r--r--engines/dm/inventory.h99
-rw-r--r--engines/dm/loadsave.cpp448
-rw-r--r--engines/dm/loadsave.h44
-rw-r--r--engines/dm/lzw.cpp187
-rw-r--r--engines/dm/lzw.h69
-rw-r--r--engines/dm/menus.cpp1787
-rw-r--r--engines/dm/menus.h135
-rw-r--r--engines/dm/module.mk63
-rw-r--r--engines/dm/movesens.cpp1010
-rw-r--r--engines/dm/movesens.h74
-rw-r--r--engines/dm/objectman.cpp278
-rw-r--r--engines/dm/objectman.h82
-rw-r--r--engines/dm/projexpl.cpp562
-rw-r--r--engines/dm/projexpl.h107
-rw-r--r--engines/dm/sounds.cpp226
-rw-r--r--engines/dm/sounds.h101
-rw-r--r--engines/dm/text.cpp254
-rw-r--r--engines/dm/text.h82
-rw-r--r--engines/dm/timeline.cpp988
-rw-r--r--engines/dm/timeline.h202
43 files changed, 24954 insertions, 0 deletions
diff --git a/engines/dm/.gitattributes b/engines/dm/.gitattributes
new file mode 100644
index 0000000000..8620362551
--- /dev/null
+++ b/engines/dm/.gitattributes
@@ -0,0 +1,10 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+*.cpp text
+*.h text
+
+# Declare files that will always have CRLF line endings on checkout.
+*.sln text eol=crlf
diff --git a/engines/dm/TODOs/methodtree.txt b/engines/dm/TODOs/methodtree.txt
new file mode 100644
index 0000000000..cbd8d1fbba
--- /dev/null
+++ b/engines/dm/TODOs/methodtree.txt
@@ -0,0 +1,215 @@
+# This file is obsolete
+
+F0115_DUNGEONVIEW_DrawObjectsCreaturesProjectilesExplosions_CPSEF
+ F0113_DUNGEONVIEW_DrawField // stub method
+ F0133_VIDEO_BlitBoxFilledWithMaskedBitmap // dummy
+ FIELD_ASPECT // done
+ F0114_DUNGEONVIEW_GetExplosionBitmap // done
+ F0133_VIDEO_BlitBoxFilledWithMaskedBitmap // dummy
+ F0141_DUNGEON_GetObjectInfoIndex // done
+ F0142_DUNGEON_GetProjectileAspect // done
+ F0158_DUNGEON_GetWeaponInfo // done
+ M66_PROJECTILE_ASPECT_ORDINAL // done
+ F0176_GROUP_GetCreatureOrdinalInCell // done
+ F0145_DUNGEON_GetGroupCells // done
+ F0147_DUNGEON_GetGroupDirections // done
+ GROUP // done
+ CreatureType // done
+ G0077_B_DoNotDrawFluxcagesDuringEndgame // done
+ G0105_s_Graphic558_Box_ExplosionPattern_D0C // one
+ G0188_as_Graphic558_FieldAspects // done
+ G0216_auc_Graphic558_ExplosionBaseScales // done
+ G0217_aauc_Graphic558_ObjectPileShiftSetIndices // done
+ G0218_aaaauc_Graphic558_ObjectCoordinateSets // done
+ G0223_aac_Graphic558_ShiftSets // done
+ G0224_aaaauc_Graphic558_CreatureCoordinateSets // done
+ G0225_aai_Graphic558_CenteredExplosionCoordinates // done
+ G0226_aaai_Graphic558_ExplosionCoordinates // done
+ G0227_aai_Graphic558_RebirthStep2ExplosionCoordinates // done
+ G0228_aai_Graphic558_RebirthStep1ExplosionCoordinates // done
+ G0292_aT_PileTopObject // done
+ G0370_ps_Events // done
+
+
+
+
+F0380_COMMAND_ProcessQueue_CPSC // in progress
+ C080_COMMAND_CLICK_IN_DUNGEON_VIEW // cool
+ F0377_COMMAND_ProcessType80_ClickInDungeonView // done so-so
+ F0372_COMMAND_ProcessType80_ClickInDungeonView_TouchFrontWall // done so-so
+ F0275_SENSOR_IsTriggeredByClickOnWall // done so-so
+ F0280_CHAMPION_AddCandidateChampionToParty // done, so-so
+
+
+F0378_COMMAND_ProcessType81_ClickInPanel // done so-so
+ F0282_CHAMPION_ProcessCommands160To162_ClickInResurrectReincarnatePanel // done
+ F0368_COMMAND_SetLeader // done
+ F0457_START_DrawEnabledMenus_CPSF // can't yet see it's purpose
+ F0281_CHAMPION_Rename // stub
+ F0394_MENUS_SetMagicCasterAndDrawSpellArea // done
+ F0393_MENUS_DrawSpellAreaControls // done
+ F0051_TEXT_MESSAGEAREA_PrintLineFeed // post skip
+ F0047_TEXT_MESSAGEAREA_PrintMessage // post skip
+ F0067_MOUSE_SetPointerToNormal // skip
+
+
+
+F0280_CHAMPION_AddCandidateChampionToParty // done, so-so
+ M27_PORTRAIT_X // done
+ M28_PORTRAIT_Y // done
+ F0285_CHAMPION_GetIndexInCell // done
+ F0279_CHAMPION_GetDecodedValue // done
+ F0368_COMMAND_SetLeader // done
+ F0292_CHAMPION_DrawState // done
+ G0407_s_Party // done
+ G0048_s_Graphic562_Box_Mouth // done
+ G0049_s_Graphic562_Box_Eye // done
+ G0054_ai_Graphic562_Box_ChampionIcons // done
+ G0353_ac_StringBuildBuffer // done
+ G0046_auc_Graphic562_ChampionColor // done
+ F0354_INVENTORY_DrawStatusBoxPortrait // done
+ F0287_CHAMPION_DrawBarGraphs // done
+ F0290_CHAMPION_DrawHealthStaminaManaValues // done
+ F0309_CHAMPION_GetMaximumLoad // done
+ F0306_CHAMPION_GetStaminaAdjustedValue // done
+ F0288_CHAMPION_GetStringFromInteger // done
+ F0345_INVENTORY_DrawPanel_FoodWaterPoisoned // done
+ F0344_INVENTORY_DrawPanel_FoodOrWaterBar // done
+ F0343_INVENTORY_DrawPanel_HorizontalBar // done
+ G0032_s_Graphic562_Box_Panel // done
+ G0035_s_Graphic562_Box_Food // done
+ G0036_s_Graphic562_Box_Water // done
+ G0037_s_Graphic562_Box_Poisoned // done
+ F0351_INVENTORY_DrawChampionSkillsAndStatistics // skip -----------------
+ F0347_INVENTORY_DrawPanel // done
+ F0342_INVENTORY_DrawPanel_Object // done
+ F0341_INVENTORY_DrawPanel_Scroll // done
+ F0340_INVENTORY_DrawPanel_ScrollTextLine // done
+ F0333_INVENTORY_OpenAndDrawChest // done
+ F0303_CHAMPION_GetSkillLevel // done
+ F0332_INVENTORY_DrawIconToViewport // done
+ F0336_INVENTORY_DrawPanel_BuildObjectAttributesString // done
+ F0335_INVENTORY_DrawPanel_ObjectDescriptionString // done
+ G0421_i_ObjectDescriptionTextX // done
+ G0422_i_ObjectDescriptionTextY // done
+ F0339_INVENTORY_DrawPanel_ArrowOrEye // done
+ G0430_apc_DirectionNames // done
+ G0034_s_Graphic562_Box_ObjectDescriptionCircle // done
+ G0032_s_Graphic562_Box_Panel // done
+ G0352_apc_ObjectNames // done
+ G0237_as_Graphic559_ObjectInfo // done
+ G0422_i_ObjectDescriptionTextY // done
+
+ F0346_INVENTORY_DrawPanel_ResurrectReincarnate // done
+ F0291_CHAMPION_DrawSlot // done
+ F0038_OBJECT_DrawIconInSlotBox // done
+ F0140_DUNGEON_GetObjectWeight // done
+ G0238_as_Graphic559_WeaponInfo // done
+ WEAPON_INFO // done
+ G0239_as_Graphic559_ArmourInfo // done
+ ARMOUR_INFO // done
+ G0241_auc_Graphic559_JunkInfo // done
+ JUNK_INFO // done
+ G0411_i_LeaderIndex // done
+ G0299_ui_CandidateChampionOrdinal // done
+ F0388_MENUS_ClearActingChampion // done
+ F0292_CHAMPION_DrawState // done
+ G0508_B_RefreshActionArea // done
+ G0506_ui_ActingChampionOrdinal // done
+ F0386_MENUS_DrawActionIcon // done
+ F0141_DUNGEON_GetObjectInfoIndex // done
+ F0033_OBJECT_GetIconIndex // done
+ F0032_OBJECT_GetType // done
+ G0237_as_Graphic559_ObjectInfo // done
+ OBJECT_INFO // done
+ G0029_auc_Graphic562_ChargeCountToTorchType // done
+ F0134_VIDEO_FillBitmap // done
+ D24_FillScreenBox // done
+ F0036_OBJECT_ExtractIconFromBitmap // done
+ G0026_ai_Graphic562_IconGraphicFirstIconIndex // done
+ F0129_VIDEO_BlitShrinkWithPaletteChanges // eeeh
+ F0136_VIDEO_ShadeScreenBox // skip
+ G0498_auc_Graphic560_PaletteChanges_ActionAreaObjectIcon // done
+ G0237_as_Graphic559_ObjectInfo // done
+ G0509_B_ActionAreaContainsIcons // done
+ F0301_CHAMPION_AddObjectInSlot // done
+ F0299_CHAMPION_ApplyObjectModifiersToStatistics // done
+ F0296_CHAMPION_DrawChangedObjectIcons // done
+ F0068_MOUSE_SetPointerToObject // skip
+ F0077_MOUSE_HidePointer_CPSE // skip
+ F0078_MOUSE_ShowPointer // skip
+ F0034_OBJECT_DrawLeaderHandObjectName // done
+ F0386_MENUS_DrawActionIcon // done
+ F0295_CHAMPION_HasObjectIconInSlotBoxChanged // done
+ F0039_OBJECT_GetIconIndexInSlotBox // done
+ M70_HAND_SLOT_INDEX // done
+ G0420_B_MousePointerHiddenToDrawChangedObjectIconOnScreen // done
+ G0412_puc_Bitmap_ObjectIconForMousePointer // done
+ G0413_i_LeaderHandObjectIconIndex // done
+ G0414_T_LeaderHandObject // done
+ F0337_INVENTORY_SetDungeonViewPalette // skip
+ G0407_s_Party // done
+ G0039_ai_Graphic562_LightPowerToLightAmount // skip
+
+ F0355_INVENTORY_Toggle_CPSE // done
+ F0292_CHAMPION_DrawState // done
+ F0334_INVENTORY_CloseChest // done
+ F0163_DUNGEON_LinkThingToList // done
+ G0426_T_OpenChest // done
+ G0425_aT_ChestSlots // done
+ F0395_MENUS_DrawMovementArrows // done
+ F0357_COMMAND_DiscardAllInput // skip
+ F0098_DUNGEONVIEW_DrawFloorAndCeiling // wat
+ F0136_VIDEO_ShadeScreenBox // skip
+ D25_F0135_VIDEO_FillBox // done
+ G0423_i_InventoryChampionOrdinal
+ G0326_B_RefreshMousePointerInMainLoop // lol you wat m8
+ G0002_s_Graphic562_Box_MovementArrows // done
+ G0041_s_Graphic562_Box_ViewportFloppyZzzCross // done
+ G0296_puc_Bitmap_Viewport // done
+ G0598_B_MousePointerBitmapUpdated // done
+ F0456_START_DrawDisabledMenus // done
+ G0415_B_LeaderEmptyHanded // done
+ G0305_ui_PartyChampionCount // done
+ G0578_B_UseByteBoxCoordinates // done
+ G0047_s_Graphic562_Box_ChampionPortrait // done
+ G0308_i_PartyDirection // done
+ G0306_i_PartyMapX // done
+ G0307_i_PartyMapY // done
+ G0299_ui_CandidateChampionOrdinal // done
+ G0508_B_RefreshActionArea // done
+ G0233_ai_Graphic559_DirectionToStepEastCount // done
+ G0234_ai_Graphic559_DirectionToStepNorthCount // done
+ G0237_as_Graphic559_ObjectInfo // done
+ G0038_ai_Graphic562_SlotMasks // done
+
+
+F0462_START_StartGame_CPSF
+ F0003_MAIN_ProcessNewPartyMap_CPSE // partially done
+ F0278_CHAMPION_ResetDataToStartGame // paritally done
+ G0331_B_PressingEye // dm // done
+ G0332_B_StopPressingEye // dm // done
+ G0333_B_PressingMouth // dm // done
+ G0334_B_StopPressingMouth // dm // done
+ G0340_B_HighlightBoxInversionRequested // dm, useless // done
+ G0341_B_HighlightBoxEnabled // eventman // done
+ G0300_B_PartyIsSleeping // champion // done
+ G0506_ui_ActingChampionOrdinal // champion // done
+ G0509_B_ActionAreaContainsIcons // menus // done
+ G0599_ui_UseChampionIconOrdinalAsMousePointerBitmap // eventman // done
+
+
+F0463_START_InitializeGame_CPSADEF // partially done
+ F0267_MOVE_GetMoveResult_CPSCE // skip, really though
+ F0357_COMMAND_DiscardAllInput // done
+
+
+C013_GRAPHIC_MOVEMENT_ARROWS
+ F0395_MENUS_DrawMovementArrows
+ F0355_INVENTORY_Toggle_CPSE
+ F0462_START_StartGame_CPSF
+ F0457_START_DrawEnabledMenus_CPSF
+ F0314_CHAMPION_WakeUp
+ F0282_CHAMPION_ProcessCommands160To162_ClickInResurrectReincarnatePanel
+ F0380_COMMAND_ProcessQueue_CPSC
+ F0433_STARTEND_ProcessCommand140_SaveGame_CPSCDF
diff --git a/engines/dm/TODOs/todo.txt b/engines/dm/TODOs/todo.txt
new file mode 100644
index 0000000000..4309c984b5
--- /dev/null
+++ b/engines/dm/TODOs/todo.txt
@@ -0,0 +1,21 @@
+Bugs:
+ Display:
+ Spellcasting tabs are displayed inproperly, switching between them is possible tho
+ Cursor icons are drawn twice
+
+ Logic:
+ Items thrown on the right side end up on the left side
+ Restarting the game after the party is dead segfaults
+
+Todo:
+ Add wiki entry for DM
+
+ Double check enums with hex literals
+ Double check strcat, strstr usages
+ I forgot to add a bunch of warning for show/hide mouse pointer and other mouse functions
+
+Code stuff todo:
+ Complete stub methods(blitShrink, blitmask)
+ Add proper save header, add error handling to it
+ Add translations to f433_processCommand140_saveGame 'LOAD'
+ \ No newline at end of file
diff --git a/engines/dm/champion.cpp b/engines/dm/champion.cpp
new file mode 100644
index 0000000000..6b895063ea
--- /dev/null
+++ b/engines/dm/champion.cpp
@@ -0,0 +1,2607 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "dm/champion.h"
+#include "dm/dungeonman.h"
+#include "dm/eventman.h"
+#include "dm/menus.h"
+#include "dm/inventory.h"
+#include "dm/objectman.h"
+#include "dm/text.h"
+#include "dm/timeline.h"
+#include "dm/projexpl.h"
+#include "dm/group.h"
+#include "dm/movesens.h"
+#include "dm/sounds.h"
+
+
+namespace DM {
+
+void Champion::resetToZero() {
+ for (int16 i = 0; i < 30; ++i)
+ _slots[i] = Thing::_none;
+ for (int16 i = 0; i < 20; ++i)
+ _skills[i].resetToZero();
+ _attributes = _wounds = 0;
+ memset(_statistics, 0, 7 * 3);
+ memset(_name, '\0', 8);
+ memset(_title, '\0', 20);
+ _dir = kDirNorth;
+ _cell = k0_ViewCellFronLeft;
+ _actionIndex = kDMActionN;
+ _symbolStep = 0;
+ memset(_symbols, '\0', 5);
+ _directionMaximumDamageReceived = _maximumDamageReceived = _poisonEventCount = _enableActionEventIndex = 0;
+ _hideDamageReceivedIndex = _currHealth = _maxHealth = _currStamina = _maxStamina = _currMana = _maxMana = 0;
+ _actionDefense = _food = _water = _load = _shieldDefense = 0;
+ memset(_portrait, 0, 464);
+}
+
+void Champion::setWoundsFlag(ChampionWound flag, bool value) {
+ if (value)
+ _wounds |= flag;
+ else
+ _wounds &= ~flag;
+}
+
+void Champion::setAttributeFlag(ChampionAttribute flag, bool value) {
+ if (value)
+ _attributes |= flag;
+ else
+ _attributes &= ~flag;
+}
+
+void ChampionMan::initConstants() {
+ static const char *g417_baseSkillName_EN_ANY[4] = {"FIGHTER", "NINJA", "PRIEST", "WIZARD"};
+ static const char *g417_baseSkillName_DE_DEU[4] = {"KAEMPFER", "NINJA", "PRIESTER", "MAGIER"};
+ static const char *g417_baseSkillName_FR_FRA[4] = {"GUERRIER", "NINJA", "PRETRE", "SORCIER"};
+ static Box boxChampionIcons[4] = {
+ Box(281, 299, 0, 13),
+ Box(301, 319, 0, 13),
+ Box(301, 319, 15, 28),
+ Box(281, 299, 15, 28)
+ };
+
+ static Color championColor[4] = {(Color)7, (Color)11, (Color)8, (Color)14};
+ int16 lightPowerToLightAmount[16] = {0, 5, 12, 24, 33, 40, 46, 51, 59, 68, 76, 82, 89, 94, 97, 100};
+ uint16 slotMasks[38] = { // @ G0038_ai_Graphic562_SlotMasks
+ /* 30 for champion inventory, 8 for chest */
+ 0xFFFF, /* Ready Hand Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Action Hand Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0x0002, /* Head Head */
+ 0x0008, /* Torso Torso */
+ 0x0010, /* Legs Legs */
+ 0x0020, /* Feet Feet */
+ 0x0100, /* Pouch 2 Pouch */
+ 0x0080, /* Quiver Line2 1 Quiver 2 */
+ 0x0080, /* Quiver Line1 2 Quiver 2 */
+ 0x0080, /* Quiver Line2 2 Quiver 2 */
+ 0x0004, /* Neck Neck */
+ 0x0100, /* Pouch 1 Pouch */
+ 0x0040, /* Quiver Line1 1 Quiver 1 */
+ 0xFFFF, /* Backpack Line1 1 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line2 2 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line2 3 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line2 4 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line2 5 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line2 6 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line2 7 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line2 8 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line2 9 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line1 2 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line1 3 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line1 4 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line1 5 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line1 6 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line1 7 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line1 8 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0xFFFF, /* Backpack Line1 9 Mouth/Head/Neck/Torso/Legs/Feet/Quiver 1/Quiver 2/Pouch/Hands/Chest */
+ 0x0400, /* Chest 1 Chest */
+ 0x0400, /* Chest 2 Chest */
+ 0x0400, /* Chest 3 Chest */
+ 0x0400, /* Chest 4 Chest */
+ 0x0400, /* Chest 5 Chest */
+ 0x0400, /* Chest 6 Chest */
+ 0x0400, /* Chest 7 Chest */
+ 0x0400 /* Chest 8 Chest */
+ };
+
+ _boxChampionPortrait = Box(0, 31, 0, 28); // @ G0047_s_Graphic562_Box_ChampionPortrait
+
+ const char **baseSkillName;
+ switch (_vm->getGameLanguage()) { // localized
+ case Common::EN_ANY:
+ baseSkillName = g417_baseSkillName_EN_ANY;
+ break;
+ case Common::DE_DEU:
+ baseSkillName = g417_baseSkillName_DE_DEU;
+ break;
+ case Common::FR_FRA:
+ baseSkillName = g417_baseSkillName_FR_FRA;
+ break;
+ default:
+ error("Unexpected language used");
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ _baseSkillName[i] = baseSkillName[i];
+ _championColor[i] = championColor[i];
+ _boxChampionIcons[i] = boxChampionIcons[i];
+ }
+
+ for (int i = 0; i < 16; i++)
+ _lightPowerToLightAmount[i] = lightPowerToLightAmount[i];
+
+ for (int i = 0; i < 38; i++)
+ _slotMasks[i] = slotMasks[i];
+}
+
+ChampionMan::ChampionMan(DMEngine *vm) : _vm(vm) {
+ for (uint16 i = 0; i < 4; ++i) {
+ _championPendingDamage[i] = 0;
+ _championPendingWounds[i] = 0;
+ _champions[i].resetToZero();
+ }
+ _partyChampionCount = 0;
+ _partyDead = false;
+ _leaderHandObject = Thing(0);
+ _leaderIndex = kDMChampionNone;
+ _candidateChampionOrdinal = 0;
+ _partyIsSleeping = false;
+ _actingChampionOrdinal = 0;
+ _leaderHandObjectIconIndex = (IconIndice)0;
+ _leaderEmptyHanded = false;
+ _party.resetToZero();
+ _magicCasterChampionIndex = kDMChampionNone;
+ _mousePointerHiddenToDrawChangedObjIconOnScreen = false;
+
+ initConstants();
+}
+
+bool ChampionMan::isLeaderHandObjectThrown(int16 side) {
+ if (_leaderIndex == kDMChampionNone)
+ return false;
+
+ return isObjectThrown(_leaderIndex, kDMSlotLeaderHand, side);
+}
+
+bool ChampionMan::isObjectThrown(uint16 champIndex, int16 slotIndex, int16 side) {
+ bool throwingLeaderHandObjectFl = false;
+ Thing curThing;
+ Champion *curChampion = nullptr;
+ Thing actionHandThing;
+
+ if (slotIndex < 0) { /* Throw object in leader hand, which is temporarily placed in action hand */
+ if (_leaderEmptyHanded)
+ return false;
+
+ curThing = getObjectRemovedFromLeaderHand();
+ curChampion = &_champions[champIndex];
+ actionHandThing = curChampion->getSlot(kDMSlotActionHand);
+ curChampion->setSlot(kDMSlotActionHand, curThing);
+ slotIndex = kDMSlotActionHand;
+ throwingLeaderHandObjectFl = true;
+ }
+
+ int16 kineticEnergy = getStrength(champIndex, slotIndex);
+ if (throwingLeaderHandObjectFl) {
+ // In this case, curChampion and actionHandThing are set.
+ curChampion->setSlot((ChampionSlot)slotIndex, actionHandThing);
+ } else {
+ curThing = getObjectRemovedFromSlot(champIndex, slotIndex);
+ if (curThing == Thing::_none)
+ return false;
+ }
+
+ _vm->_sound->requestPlay(k16_soundCOMBAT_ATTACK_SKELETON_ANIMATED_ARMOUR_DETH_KNIGHT, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, k1_soundModePlayIfPrioritized);
+ decrementStamina(champIndex, getThrowingStaminaCost(curThing));
+ disableAction(champIndex, 4);
+ int16 experience = 8;
+ int16 weaponKineticEnergy = 1;
+ if (curThing.getType() == k5_WeaponThingType) {
+ experience += 4;
+ WeaponInfo *curWeapon = _vm->_dungeonMan->getWeaponInfo(curThing);
+ if (curWeapon->_class <= k12_WeaponClassPoisinDart) {
+ weaponKineticEnergy = curWeapon->_kineticEnergy;
+ experience += weaponKineticEnergy >> 2;
+ }
+ }
+ addSkillExperience(champIndex, kDMSkillThrow, experience);
+ kineticEnergy += weaponKineticEnergy;
+ int16 skillLevel = getSkillLevel((ChampionIndex)champIndex, kDMSkillThrow);
+ kineticEnergy += _vm->getRandomNumber(16) + (kineticEnergy >> 1) + skillLevel;
+ int16 attack = getBoundedValue((uint16)40, (uint16)((skillLevel << 3) + _vm->getRandomNumber(32)), (uint16)200);
+ int16 stepEnergy = MAX(5, 11 - skillLevel);
+ _vm->_projexpl->createProjectile(curThing, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY,
+ normalizeModulo4(_vm->_dungeonMan->_partyDir + side),
+ _vm->_dungeonMan->_partyDir, kineticEnergy, attack, stepEnergy);
+ _vm->_projectileDisableMovementTicks = 4;
+ _vm->_lastProjectileDisabledMovementDirection = _vm->_dungeonMan->_partyDir;
+ drawChampionState((ChampionIndex)champIndex);
+ return true;
+}
+
+uint16 ChampionMan::getChampionPortraitX(uint16 index) {
+ return ((index) & 0x7) << 5;
+}
+
+uint16 ChampionMan::getChampionPortraitY(uint16 index) {
+ return ((index) >> 3) * 29;
+}
+
+int16 ChampionMan::getDecodedValue(char *string, uint16 characterCount) {
+ int val = 0;
+ for (uint16 i = 0; i < characterCount; ++i) {
+ val = (val << 4) + (string[i] - 'A');
+ }
+ return val;
+}
+
+void ChampionMan::drawHealthOrStaminaOrManaValue(int16 posY, int16 currVal, int16 maxVal) {
+ Common::String tmp = getStringFromInteger(currVal, true, 3);
+ _vm->_textMan->printToViewport(55, posY, k13_ColorLightestGray, tmp.c_str());
+ _vm->_textMan->printToViewport(73, posY, k13_ColorLightestGray, "/");
+ tmp = getStringFromInteger(maxVal, true, 3);
+ _vm->_textMan->printToViewport(79, posY, k13_ColorLightestGray, tmp.c_str());
+}
+
+uint16 ChampionMan::getHandSlotIndex(uint16 slotBoxIndex) {
+ return slotBoxIndex & 0x1;
+}
+
+
+Common::String ChampionMan::getStringFromInteger(uint16 val, bool padding, uint16 paddingCharCount) {
+ Common::String valToStr = Common::String::format("%d", val);
+ Common::String result;
+
+ if (padding) {
+ for (int16 i = 0, end = paddingCharCount - valToStr.size(); i < end; ++i)
+ result += ' ';
+ }
+
+ return result += valToStr;
+}
+
+void ChampionMan::applyModifiersToStatistics(Champion *champ, int16 slotIndex, int16 iconIndex, int16 modifierFactor, Thing thing) {
+ int16 statIndex = kDMStatLuck;
+ int16 modifier = 0;
+ ThingType thingType = thing.getType();
+
+ bool cursed = false;
+ if (((thingType == k5_WeaponThingType) || (thingType == k6_ArmourThingType))
+ && (slotIndex >= kDMSlotReadyHand) && (slotIndex <= kDMSlotQuiverLine1_1)) {
+ if (thingType == k5_WeaponThingType) {
+ Weapon *weapon = (Weapon *)_vm->_dungeonMan->getThingData(thing);
+ cursed = weapon->getCursed();
+ } else {
+ // k6_ArmourThingType
+ Armour *armour = (Armour *)_vm->_dungeonMan->getThingData(thing);
+ cursed = armour->getCursed();
+ }
+
+ if (cursed) {
+ statIndex = kDMStatLuck;
+ modifier = -3;
+ }
+ }
+
+ if (!cursed) {
+ statIndex = (ChampionStatType)thingType; // variable sharing
+
+ if ((iconIndex == kDMIconIndiceJunkRabbitsFoot) && (slotIndex < kDMSlotChest1)) {
+ statIndex = kDMStatLuck;
+ modifier = 10;
+ } else if (slotIndex == kDMSlotActionHand) {
+ if (iconIndex == kDMIconIndiceWeaponMaceOfOrder) {
+ statIndex = kDMStatStrength;
+ modifier = 5;
+ } else {
+ statIndex = kDMStatMana;
+ if ((iconIndex >= kDMIconIndiceWeaponStaffOfClawsEmpty) && (iconIndex <= kDMIconIndiceWeaponStaffOfClawsFull)) {
+ modifier = 4;
+ } else {
+ switch (iconIndex) {
+ case kDMIconIndiceWeaponDeltaSideSplitter:
+ modifier = 1;
+ break;
+ case kDMIconIndiceWeaponTheInquisitorDragonFang:
+ modifier = 2;
+ break;
+ case kDMIconIndiceWeaponVorpalBlade:
+ modifier = 4;
+ break;
+ case kDMIconIndiceWeaponStaff:
+ modifier = 2;
+ break;
+ case kDMIconIndiceWeaponWand:
+ modifier = 1;
+ break;
+ case kDMIconIndiceWeaponTeowand:
+ modifier = 6;
+ break;
+ case kDMIconIndiceWeaponYewStaff:
+ modifier = 4;
+ break;
+ case kDMIconIndiceWeaponStaffOfManarStaffOfIrra:
+ modifier = 10;
+ break;
+ case kDMIconIndiceWeaponSnakeStaffCrossOfNeta:
+ modifier = 8;
+ break;
+ case kDMIconIndiceWeaponTheConduitSerpentStaff:
+ modifier = 16;
+ break;
+ case kDMIconIndiceWeaponDragonSpit:
+ modifier = 7;
+ break;
+ case kDMIconIndiceWeaponSceptreOfLyf:
+ modifier = 5;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ } else if (slotIndex == kDMSlotLegs) {
+ if (iconIndex == kDMIconIndiceArmourPowertowers) {
+ statIndex = kDMStatStrength;
+ modifier = 10;
+ }
+ } else if (slotIndex == kDMSlotHead) {
+ switch (iconIndex) {
+ case kDMIconIndiceArmourCrownOfNerra:
+ statIndex = kDMStatWisdom;
+ modifier = 10;
+ break;
+ case kDMIconIndiceArmourDexhelm:
+ statIndex = kDMStatDexterity;
+ modifier = 10;
+ break;
+ default:
+ break;
+ }
+ } else if (slotIndex == kDMSlotTorso) {
+ switch (iconIndex) {
+ case kDMIconIndiceArmourFlamebain:
+ statIndex = kDMStatAntifire;
+ modifier = 12;
+ break;
+ case kDMIconIndiceArmourCloakOfNight:
+ statIndex = kDMStatDexterity;
+ modifier = 8;
+ break;
+ default:
+ break;
+ }
+ } else if (slotIndex == kDMSlotNeck) {
+ switch (iconIndex) {
+ case kDMIconIndiceJunkJewelSymalUnequipped:
+ case kDMIconIndiceJunkJewelSymalEquipped:
+ statIndex = kDMStatAntimagic;
+ modifier = 15;
+ break;
+ case kDMIconIndiceArmourCloakOfNight:
+ statIndex = kDMStatDexterity;
+ modifier = 8;
+ break;
+ case kDMIconIndiceJunkMoonstone:
+ statIndex = kDMStatMana;
+ modifier = 3;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (modifier) {
+ modifier *= modifierFactor;
+ //statIndex is set when modifier is set
+ if (statIndex == kDMStatMana) {
+ champ->_maxMana += modifier;
+ } else if (statIndex < kDMStatAntifire + 1) {
+ for (uint16 statValIndex = kDMStatMaximum; statValIndex <= kDMStatMinimum; ++statValIndex) {
+ champ->getStatistic((ChampionStatType)statIndex, (ChampionStatValue)statValIndex) += modifier;
+ }
+ }
+ }
+}
+
+bool ChampionMan::hasObjectIconInSlotBoxChanged(int16 slotBoxIndex, Thing thing) {
+ ObjectMan &objMan = *_vm->_objectMan;
+
+ IconIndice currIconIndex = objMan.getIconIndexInSlotBox(slotBoxIndex);
+ if (((currIconIndex < kDMIconIndiceWeaponDagger) && (currIconIndex >= kDMIconIndiceJunkCompassNorth))
+ || ((currIconIndex >= kDMIconIndicePotionMaPotionMonPotion) && (currIconIndex <= kDMIconIndicePotionWaterFlask))
+ || (currIconIndex == kDMIconIndicePotionEmptyFlask)) {
+ IconIndice newIconIndex = objMan.getIconIndex(thing);
+ if (newIconIndex != currIconIndex) {
+ if ((slotBoxIndex < k8_SlotBoxInventoryFirstSlot) && !_mousePointerHiddenToDrawChangedObjIconOnScreen) {
+ _mousePointerHiddenToDrawChangedObjIconOnScreen = true;
+ _vm->_eventMan->hideMouse();
+ }
+ objMan.drawIconInSlotBox(slotBoxIndex, newIconIndex);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void ChampionMan::drawChangedObjectIcons() {
+ InventoryMan &invMan = *_vm->_inventoryMan;
+ ObjectMan &objMan = *_vm->_objectMan;
+ MenuMan &menuMan = *_vm->_menuMan;
+
+ uint16 invChampOrdinal = invMan._inventoryChampionOrdinal;
+ if (_candidateChampionOrdinal && !invChampOrdinal)
+ return;
+
+ _mousePointerHiddenToDrawChangedObjIconOnScreen = false;
+ IconIndice leaderHandObjIconIndex = _leaderHandObjectIconIndex;
+
+ if (((leaderHandObjIconIndex < kDMIconIndiceWeaponDagger) && (leaderHandObjIconIndex >= kDMIconIndiceJunkCompassNorth)) // < instead of <= is correct
+ || ((leaderHandObjIconIndex >= kDMIconIndicePotionMaPotionMonPotion) && (leaderHandObjIconIndex <= kDMIconIndicePotionWaterFlask))
+ || (leaderHandObjIconIndex == kDMIconIndicePotionEmptyFlask)) {
+ IconIndice iconIndex = objMan.getIconIndex(_leaderHandObject);
+ if (iconIndex != leaderHandObjIconIndex) {
+ _mousePointerHiddenToDrawChangedObjIconOnScreen = true;
+ _vm->_eventMan->hideMouse();
+ objMan.extractIconFromBitmap(iconIndex, objMan._objectIconForMousePointer);
+ _vm->_eventMan->setPointerToObject(_vm->_objectMan->_objectIconForMousePointer);
+ _leaderHandObjectIconIndex = iconIndex;
+ objMan.drawLeaderObjectName(_leaderHandObject);
+ }
+ }
+
+ for (uint16 slotBoxIndex = 0; slotBoxIndex < (_partyChampionCount * 2); ++slotBoxIndex) {
+ int16 champIndex = slotBoxIndex >> 1;
+ if (invChampOrdinal == _vm->indexToOrdinal(champIndex))
+ continue;
+
+ if (hasObjectIconInSlotBoxChanged(slotBoxIndex, _champions[champIndex].getSlot((ChampionSlot)getHandSlotIndex(slotBoxIndex)))
+ && (getHandSlotIndex(slotBoxIndex) == kDMSlotActionHand)) {
+
+ menuMan.drawActionIcon((ChampionIndex)champIndex);
+ }
+ }
+
+ if (invChampOrdinal) {
+ Champion *champ = &_champions[_vm->ordinalToIndex(invChampOrdinal)];
+ Thing *thing = &champ->getSlot(kDMSlotReadyHand);
+ uint16 drawViewport = 0;
+
+ for (uint16 slotIndex = kDMSlotReadyHand; slotIndex < kDMSlotChest1; slotIndex++, thing++) {
+ uint16 objIconChanged = hasObjectIconInSlotBoxChanged(slotIndex + k8_SlotBoxInventoryFirstSlot, *thing) ? 1 : 0;
+ drawViewport |= objIconChanged;
+ if (objIconChanged && (slotIndex == kDMSlotActionHand)) {
+ menuMan.drawActionIcon((ChampionIndex)_vm->ordinalToIndex(invChampOrdinal));
+ }
+ }
+
+ if (invMan._panelContent == k4_PanelContentChest) {
+ thing = invMan._chestSlots;
+ for (int16 slotIndex = 0; slotIndex < 8; ++slotIndex, thing++) {
+ drawViewport |= (hasObjectIconInSlotBoxChanged(slotIndex + k38_SlotBoxChestFirstSlot, *thing) ? 1 : 0);
+ }
+ }
+
+ if (drawViewport) {
+ champ->setAttributeFlag(kDMAttributeViewport, true);
+ drawChampionState((ChampionIndex)_vm->ordinalToIndex(invChampOrdinal));
+ }
+ }
+
+ if (_mousePointerHiddenToDrawChangedObjIconOnScreen)
+ _vm->_eventMan->showMouse();
+}
+
+void ChampionMan::addObjectInSlot(ChampionIndex champIndex, Thing thing, ChampionSlot slotIndex) {
+ InventoryMan &invMan = *_vm->_inventoryMan;
+ DungeonMan &dunMan = *_vm->_dungeonMan;
+ ObjectMan &objMan = *_vm->_objectMan;
+ MenuMan &menuMan = *_vm->_menuMan;
+
+ if (thing == Thing::_none)
+ return;
+
+ Champion *champ = &_champions[champIndex];
+
+ if (slotIndex >= kDMSlotChest1) {
+ invMan._chestSlots[slotIndex - kDMSlotChest1] = thing;
+ } else {
+ champ->setSlot(slotIndex, thing);
+ }
+
+ champ->_load += dunMan.getObjectWeight(thing);
+ champ->setAttributeFlag(kDMAttributeLoad, true);
+ IconIndice iconIndex = objMan.getIconIndex(thing);
+ bool isInventoryChampion = (_vm->indexToOrdinal(champIndex) == invMan._inventoryChampionOrdinal);
+ applyModifiersToStatistics(champ, slotIndex, iconIndex, 1, thing);
+ uint16 *rawObjPtr = dunMan.getThingData(thing);
+
+ if (slotIndex < kDMSlotHead) {
+ if (slotIndex == kDMSlotActionHand) {
+ champ->setAttributeFlag(kDMAttributeActionHand, true);
+ if (_actingChampionOrdinal == _vm->indexToOrdinal(champIndex))
+ menuMan.clearActingChampion();
+
+ if ((iconIndex >= kDMIconIndiceScrollOpen) && (iconIndex <= kDMIconIndiceScrollClosed)) {
+ ((Scroll *)rawObjPtr)->setClosed(false);
+ drawChangedObjectIcons();
+ }
+ }
+
+ if (iconIndex == kDMIconIndiceWeaponTorchUnlit) {
+ ((Weapon *)rawObjPtr)->setLit(true);
+ _vm->_inventoryMan->setDungeonViewPalette();
+ drawChangedObjectIcons();
+ } else if (isInventoryChampion && (slotIndex == kDMSlotActionHand) &&
+ ((iconIndex == kDMIconIndiceContainerChestClosed) || ((iconIndex >= kDMIconIndiceScrollOpen) && (iconIndex <= kDMIconIndiceScrollClosed)))) {
+ champ->setAttributeFlag(kDMAttributePanel, true);
+ }
+ } else if (slotIndex == kDMSlotNeck) {
+ if ((iconIndex >= kDMIconIndiceJunkIllumuletUnequipped) && (iconIndex <= kDMIconIndiceJunkIllumuletEquipped)) {
+ ((Junk *)rawObjPtr)->setChargeCount(1);
+ _party._magicalLightAmount += _lightPowerToLightAmount[2];
+ _vm->_inventoryMan->setDungeonViewPalette();
+ iconIndex = (IconIndice)(iconIndex + 1);
+ } else if ((iconIndex >= kDMIconIndiceJunkJewelSymalUnequipped) && (iconIndex <= kDMIconIndiceJunkJewelSymalEquipped)) {
+ ((Junk *)rawObjPtr)->setChargeCount(1);
+ iconIndex = (IconIndice)(iconIndex + 1);
+ }
+ }
+
+ drawSlot(champIndex, slotIndex);
+ if (isInventoryChampion)
+ champ->setAttributeFlag(kDMAttributeViewport, true);
+}
+
+int16 ChampionMan::getScentOrdinal(int16 mapX, int16 mapY) {
+ int16 scentIndex = _party._scentCount;
+
+ if (scentIndex) {
+ Scent searchedScent;
+ searchedScent.setMapX(mapX);
+ searchedScent.setMapY(mapY);
+ searchedScent.setMapIndex(_vm->_dungeonMan->_currMapIndex);
+ uint16 searchedScentRedEagle = searchedScent.toUint16();
+ Scent *scent = &_party._scents[scentIndex--];
+ do {
+ if ((*(--scent)).toUint16() == searchedScentRedEagle) {
+ return _vm->indexToOrdinal(scentIndex);
+ }
+ } while (scentIndex--);
+ }
+ return 0;
+}
+
+Thing ChampionMan::getObjectRemovedFromLeaderHand() {
+ _leaderEmptyHanded = true;
+ Thing leaderHandObject = _leaderHandObject;
+
+ if (leaderHandObject != Thing::_none) {
+ _leaderHandObject = Thing::_none;
+ _leaderHandObjectIconIndex = kDMIconIndiceNone;
+ _vm->_eventMan->showMouse();
+ _vm->_objectMan->clearLeaderObjectName();
+ _vm->_eventMan->setMousePointer();
+ _vm->_eventMan->hideMouse();
+ if (_leaderIndex != kDMChampionNone) {
+ _champions[_leaderIndex]._load -= _vm->_dungeonMan->getObjectWeight(leaderHandObject);
+ setFlag(_champions[_leaderIndex]._attributes, kDMAttributeLoad);
+ drawChampionState(_leaderIndex);
+ }
+ }
+ return leaderHandObject;
+}
+
+uint16 ChampionMan::getStrength(int16 champIndex, int16 slotIndex) {
+ Champion *curChampion = &_champions[champIndex];
+ int16 strength = _vm->getRandomNumber(16) + curChampion->_statistics[kDMStatStrength][kDMStatCurrent];
+ Thing curThing = curChampion->_slots[slotIndex];
+ uint16 objectWeight = _vm->_dungeonMan->getObjectWeight(curThing);
+ uint16 oneSixteenthMaximumLoad = getMaximumLoad(curChampion) >> 4;
+
+ if (objectWeight <= oneSixteenthMaximumLoad) {
+ strength += objectWeight - 12;
+ } else {
+ int16 loadThreshold = oneSixteenthMaximumLoad + ((oneSixteenthMaximumLoad - 12) >> 1);
+ if (objectWeight <= loadThreshold) {
+ strength += (objectWeight - oneSixteenthMaximumLoad) >> 1;
+ } else {
+ strength -= (objectWeight - loadThreshold) << 1;
+ }
+ }
+ if (curThing.getType() == k5_WeaponThingType) {
+ WeaponInfo *weaponInfo = _vm->_dungeonMan->getWeaponInfo(curThing);
+ strength += weaponInfo->_strength;
+ uint16 skillLevel = 0;
+ uint16 weaponClass = weaponInfo->_class;
+ if ((weaponClass == k0_WeaponClassSwingWeapon) || (weaponClass == k2_WeaponClassDaggerAndAxes)) {
+ skillLevel = getSkillLevel(champIndex, kDMSkillSwing);
+ }
+ if ((weaponClass != k0_WeaponClassSwingWeapon) && (weaponClass < k16_WeaponClassFirstBow)) {
+ skillLevel += getSkillLevel(champIndex, kDMSkillThrow);
+ }
+ if ((weaponClass >= k16_WeaponClassFirstBow) && (weaponClass < k112_WeaponClassFirstMagicWeapon)) {
+ skillLevel += getSkillLevel(champIndex, kDMSkillShoot);
+ }
+ strength += skillLevel << 1;
+ }
+ strength = getStaminaAdjustedValue(curChampion, strength);
+ if (getFlag(curChampion->_wounds, (slotIndex == kDMSlotReadyHand) ? kDMWoundReadHand : kDMWoundActionHand)) {
+ strength >>= 1;
+ }
+ return getBoundedValue(0, strength >> 1, 100);
+}
+
+Thing ChampionMan::getObjectRemovedFromSlot(uint16 champIndex, uint16 slotIndex) {
+ Champion *curChampion = &_champions[champIndex];
+ Thing curThing;
+
+ if (slotIndex >= kDMSlotChest1) {
+ curThing = _vm->_inventoryMan->_chestSlots[slotIndex - kDMSlotChest1];
+ _vm->_inventoryMan->_chestSlots[slotIndex - kDMSlotChest1] = Thing::_none;
+ } else {
+ curThing = curChampion->_slots[slotIndex];
+ curChampion->_slots[slotIndex] = Thing::_none;
+ }
+
+ if (curThing == Thing::_none)
+ return Thing::_none;
+
+ bool isInventoryChampion = (_vm->indexToOrdinal(champIndex) == _vm->_inventoryMan->_inventoryChampionOrdinal);
+ int16 curIconIndex = _vm->_objectMan->getIconIndex(curThing);
+ // Remove object modifiers
+ applyModifiersToStatistics(curChampion, slotIndex, curIconIndex, -1, curThing);
+
+ Weapon *curWeapon = (Weapon *)_vm->_dungeonMan->getThingData(curThing);
+ if (slotIndex == kDMSlotNeck) {
+ if ((curIconIndex >= kDMIconIndiceJunkIllumuletUnequipped) && (curIconIndex <= kDMIconIndiceJunkIllumuletEquipped)) {
+ ((Junk *)curWeapon)->setChargeCount(0);
+ _party._magicalLightAmount -= _lightPowerToLightAmount[2];
+ _vm->_inventoryMan->setDungeonViewPalette();
+ } else if ((curIconIndex >= kDMIconIndiceJunkJewelSymalUnequipped) && (curIconIndex <= kDMIconIndiceJunkJewelSymalEquipped)) {
+ ((Junk *)curWeapon)->setChargeCount(0);
+ }
+ }
+
+ drawSlot(champIndex, slotIndex);
+ if (isInventoryChampion)
+ setFlag(curChampion->_attributes, kDMAttributeViewport);
+
+ if (slotIndex < kDMSlotHead) {
+ if (slotIndex == kDMSlotActionHand) {
+ setFlag(curChampion->_attributes, kDMAttributeActionHand);
+ if (_actingChampionOrdinal == _vm->indexToOrdinal(champIndex))
+ _vm->_menuMan->clearActingChampion();
+
+ if ((curIconIndex >= kDMIconIndiceScrollOpen) && (curIconIndex <= kDMIconIndiceScrollClosed)) {
+ ((Scroll *)curWeapon)->setClosed(true);
+ drawChangedObjectIcons();
+ }
+ }
+
+ if ((curIconIndex >= kDMIconIndiceWeaponTorchUnlit) && (curIconIndex <= kDMIconIndiceWeaponTorchLit)) {
+ curWeapon->setLit(false);
+ _vm->_inventoryMan->setDungeonViewPalette();
+ drawChangedObjectIcons();
+ }
+
+ if (isInventoryChampion && (slotIndex == kDMSlotActionHand)) {
+ switch (curIconIndex) {
+ case kDMIconIndiceContainerChestClosed:
+ _vm->_inventoryMan->closeChest();
+ // No break on purpose
+ case kDMIconIndiceScrollOpen:
+ case kDMIconIndiceScrollClosed:
+ setFlag(curChampion->_attributes, kDMAttributePanel);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ curChampion->_load -= _vm->_dungeonMan->getObjectWeight(curThing);
+ setFlag(curChampion->_attributes, kDMAttributeLoad);
+ return curThing;
+}
+
+void ChampionMan::decrementStamina(int16 championIndex, int16 decrement) {
+ if (championIndex == kDMChampionNone)
+ return;
+
+ Champion *curChampion = &_champions[championIndex];
+ curChampion->_currStamina -= decrement;
+
+ int16 stamina = curChampion->_currStamina;
+ if (stamina <= 0) {
+ curChampion->_currStamina = 0;
+ addPendingDamageAndWounds_getDamage(championIndex, (-stamina) >> 1, kDMWoundNone, kDMAttackTypeNormal);
+ } else if (stamina > curChampion->_maxStamina) {
+ curChampion->_currStamina = curChampion->_maxStamina;
+ }
+
+ setFlag(curChampion->_attributes, kDMAttributeLoad | kDMAttributeStatistics);
+}
+
+int16 ChampionMan::addPendingDamageAndWounds_getDamage(int16 champIndex, int16 attack, int16 allowedWounds, uint16 attackType) {
+ if (attack <= 0)
+ return 0;
+
+ Champion *curChampion = &_champions[champIndex];
+ if (!curChampion->_currHealth)
+ return 0;
+
+ if (attackType != kDMAttackTypeNormal) {
+ uint16 defense = 0;
+ uint16 woundCount = 0;
+ for (int16 woundIndex = kDMSlotReadyHand; woundIndex <= kDMSlotFeet; woundIndex++) {
+ if (allowedWounds & (1 << woundIndex)) {
+ woundCount++;
+ defense += getWoundDefense(champIndex, woundIndex | ((attackType == kDMAttackTypeSharp) ? kDMMaskSharpDefense : kDMMaskNoSharpDefense));
+ }
+ }
+ if (woundCount)
+ defense /= woundCount;
+
+ bool skipScaling = false;
+ switch (attackType) {
+ case kDMAttackTypePsychic:
+ {
+ int16 wisdomFactor = 115 - curChampion->_statistics[kDMStatWisdom][kDMStatCurrent];
+ if (wisdomFactor <= 0) {
+ attack = 0;
+ } else {
+ attack = _vm->getScaledProduct(attack, 6, wisdomFactor);
+ }
+
+ skipScaling = true;
+ }
+ break;
+ case kDMAttackTypeMagic:
+ attack = getStatisticAdjustedAttack(curChampion, kDMStatAntimagic, attack);
+ attack -= _party._spellShieldDefense;
+ skipScaling = true;
+ break;
+ case kDMAttackTypeFire:
+ attack = getStatisticAdjustedAttack(curChampion, kDMStatAntifire, attack);
+ attack -= _party._fireShieldDefense;
+ break;
+ case kDMAttackTypeSelf:
+ defense >>= 1;
+ break;
+ default:
+ break;
+ }
+
+ if (!skipScaling) {
+ if (attack <= 0)
+ return 0;
+
+ attack = _vm->getScaledProduct(attack, 6, 130 - defense);
+ }
+ /* BUG0_44
+ A champion may take much more damage than expected after a Black Flame attack or an impact
+ with a Fireball projectile. If the party has a fire shield defense value higher than the fire
+ attack value then the resulting intermediary attack value is negative and damage should be 0.
+ However, the negative value is still used for further computations and the result may be a very
+ high positive attack value which may kill a champion. This can occur only for k1_attackType_FIRE
+ and if attack is negative before calling F0030_MAIN_GetScaledProduct
+ */
+
+ if (attack <= 0)
+ return 0;
+
+ int16 adjustedAttack = getStatisticAdjustedAttack(curChampion, kDMStatVitality, _vm->getRandomNumber(128) + 10);
+ if (attack > adjustedAttack) {
+ /* BUG0_45
+ This bug is not perceptible because of BUG0_41 that ignores Vitality while determining the
+ probability of being wounded. However if it was fixed, the behavior would be the opposite
+ of what it should: the higher the vitality of a champion, the lower the result of
+ F0307_CHAMPION_GetStatisticAdjustedAttack and the more likely the champion could get
+ wounded (because of more iterations in the loop below)
+ */
+ do {
+ setFlag(*(uint16 *)&_championPendingWounds[champIndex], (1 << _vm->getRandomNumber(8)) & allowedWounds);
+ } while ((attack > (adjustedAttack <<= 1)) && adjustedAttack);
+ }
+
+ if (_partyIsSleeping)
+ wakeUp();
+ }
+ _championPendingDamage[champIndex] += attack;
+ return attack;
+}
+
+int16 ChampionMan::getWoundDefense(int16 champIndex, uint16 woundIndex) {
+ static const byte woundDefenseFactor[6] = {5, 5, 4, 6, 3, 1}; // @ G0050_auc_Graphic562_WoundDefenseFactor
+
+ Champion *curChampion = &_champions[champIndex];
+ bool useSharpDefense = getFlag(woundIndex, kDMMaskSharpDefense);
+ if (useSharpDefense)
+ clearFlag(woundIndex, kDMMaskSharpDefense);
+
+ uint16 armorShieldDefense = 0;
+ for (int16 slotIndex = kDMSlotReadyHand; slotIndex <= kDMSlotActionHand; slotIndex++) {
+ Thing curThing = curChampion->_slots[slotIndex];
+ if (curThing.getType() == k6_ArmourThingType) {
+ ArmourInfo *armorInfo = (ArmourInfo *)_vm->_dungeonMan->getThingData(curThing);
+ armorInfo = &_vm->_dungeonMan->_armourInfos[((Armour *)armorInfo)->getType()];
+ if (getFlag(armorInfo->_attributes, k0x0080_ArmourAttributeIsAShield))
+ armorShieldDefense += ((getStrength(champIndex, slotIndex) + _vm->_dungeonMan->getArmourDefense(armorInfo, useSharpDefense)) * woundDefenseFactor[woundIndex]) >> ((slotIndex == woundIndex) ? 4 : 5);
+ }
+ }
+
+ int16 woundDefense = _vm->getRandomNumber((curChampion->_statistics[kDMStatVitality][kDMStatCurrent] >> 3) + 1);
+ if (useSharpDefense)
+ woundDefense >>= 1;
+
+ woundDefense += curChampion->_actionDefense + curChampion->_shieldDefense + _party._shieldDefense + armorShieldDefense;
+ if (woundIndex > kDMSlotActionHand) {
+ Thing curThing = curChampion->_slots[woundIndex];
+ if (curThing.getType() == k6_ArmourThingType) {
+ ArmourInfo *armourInfo = (ArmourInfo *)_vm->_dungeonMan->getThingData(curThing);
+ woundDefense += _vm->_dungeonMan->getArmourDefense(&_vm->_dungeonMan->_armourInfos[((Armour *)armourInfo)->getType()], useSharpDefense);
+ }
+ }
+
+ if (getFlag(curChampion->_wounds, 1 << woundIndex))
+ woundDefense -= 8 + _vm->getRandomNumber(4);
+
+ if (_partyIsSleeping)
+ woundDefense >>= 1;
+
+ return getBoundedValue(0, woundDefense >> 1, 100);
+}
+
+uint16 ChampionMan::getStatisticAdjustedAttack(Champion *champ, uint16 statIndex, uint16 attack) {
+ int16 factor = 170 - champ->_statistics[statIndex][kDMStatCurrent];
+
+ /* BUG0_41
+ The Antifire and Antimagic statistics are completely ignored. The Vitality statistic is ignored
+ against poison and to determine the probability of being wounded. Vitality is still used normally
+ to compute the defense against wounds and the speed of health regeneration. A bug in the Megamax C
+ compiler produces wrong machine code for this statement. It always returns 0 for the current statistic
+ value so that factor = 170 in all cases
+ */
+ if (factor < 16)
+ return attack >> 3;
+
+ return _vm->getScaledProduct(attack, 7, factor);
+}
+
+void ChampionMan::wakeUp() {
+ _vm->_stopWaitingForPlayerInput = true;
+ _partyIsSleeping = false;
+ _vm->_waitForInputMaxVerticalBlankCount = 10;
+ _vm->delay(10);
+ _vm->_displayMan->drawFloorAndCeiling();
+ _vm->_eventMan->_primaryMouseInput = _vm->_eventMan->_primaryMouseInputInterface;
+ _vm->_eventMan->_secondaryMouseInput = _vm->_eventMan->_secondaryMouseInputMovement;
+ _vm->_eventMan->_primaryKeyboardInput = _vm->_eventMan->_primaryKeyboardInputInterface;
+ _vm->_eventMan->_secondaryKeyboardInput = _vm->_eventMan->_secondaryKeyboardInputMovement;
+ _vm->_eventMan->discardAllInput();
+ _vm->_menuMan->drawEnabledMenus();
+}
+
+int16 ChampionMan::getThrowingStaminaCost(Thing thing) {
+ int16 weight = _vm->_dungeonMan->getObjectWeight(thing) >> 1;
+ int16 staminaCost = getBoundedValue<int16>(1, weight, 10);
+
+ while ((weight -= 10) > 0)
+ staminaCost += weight >> 1;
+
+ return staminaCost;
+}
+
+void ChampionMan::disableAction(uint16 champIndex, uint16 ticks) {
+ Champion *curChampion = &_champions[champIndex];
+ int32 updatedEnableActionEventTime = _vm->_gameTime + ticks;
+
+ TimelineEvent curEvent;
+ curEvent._type = k11_TMEventTypeEnableChampionAction;
+ curEvent._priority = champIndex;
+ curEvent._B._slotOrdinal = 0;
+
+ int16 eventIndex = curChampion->_enableActionEventIndex;
+ if (eventIndex >= 0) {
+ int32 currentEnableActionEventTime = filterTime(_vm->_timeline->_events[eventIndex]._mapTime);
+ if (updatedEnableActionEventTime >= currentEnableActionEventTime) {
+ updatedEnableActionEventTime += (currentEnableActionEventTime - _vm->_gameTime) >> 1;
+ } else {
+ updatedEnableActionEventTime = currentEnableActionEventTime + (ticks >> 1);
+ }
+ _vm->_timeline->deleteEvent(eventIndex);
+ } else {
+ setFlag(curChampion->_attributes, kDMAttributeActionHand | kDMAttributeDisableAction);
+ drawChampionState((ChampionIndex)champIndex);
+ }
+ setMapAndTime(curEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, updatedEnableActionEventTime);
+ curChampion->_enableActionEventIndex = _vm->_timeline->addEventGetEventIndex(&curEvent);
+}
+
+void ChampionMan::addSkillExperience(uint16 champIndex, uint16 skillIndex, uint16 exp) {
+ if ((skillIndex >= kDMSkillSwing) && (skillIndex <= kDMSkillShoot) && (_vm->_projexpl->_lastCreatureAttackTime < _vm->_gameTime - 150))
+ exp >>= 1;
+
+ if (exp) {
+ if (_vm->_dungeonMan->_currMap->_difficulty)
+ exp *= _vm->_dungeonMan->_currMap->_difficulty;
+
+ Champion *curChampion = &_champions[champIndex];
+ uint16 baseSkillIndex;
+ if (skillIndex >= kDMSkillSwing)
+ baseSkillIndex = (skillIndex - kDMSkillSwing) >> 2;
+ else
+ baseSkillIndex = skillIndex;
+
+ uint16 skillLevelBefore = getSkillLevel(champIndex, baseSkillIndex | (kDMIgnoreObjectModifiers | kDMIgnoreTemporaryExperience));
+
+ if ((skillIndex >= kDMSkillSwing) && (_vm->_projexpl->_lastCreatureAttackTime > _vm->_gameTime - 25))
+ exp <<= 1;
+
+ Skill *curSkill = &curChampion->_skills[skillIndex];
+ curSkill->_experience += exp;
+ if (curSkill->_temporaryExperience < 32000)
+ curSkill->_temporaryExperience += getBoundedValue(1, exp >> 3, 100);
+
+ curSkill = &curChampion->_skills[baseSkillIndex];
+ if (skillIndex >= kDMSkillSwing)
+ curSkill->_experience += exp;
+
+ uint16 skillLevelAfter = getSkillLevel(champIndex, baseSkillIndex | (kDMIgnoreObjectModifiers | kDMIgnoreTemporaryExperience));
+ if (skillLevelAfter > skillLevelBefore) {
+ int16 newBaseSkillLevel = skillLevelAfter;
+ int16 minorStatIncrease = _vm->getRandomNumber(2);
+ int16 majorStatIncrease = 1 + _vm->getRandomNumber(2);
+ uint16 vitalityAmount = _vm->getRandomNumber(2); /* For Priest skill, the amount is 0 or 1 for all skill levels */
+ if (baseSkillIndex != kDMSkillPriest) {
+ vitalityAmount &= skillLevelAfter; /* For non Priest skills the amount is 0 for even skill levels. The amount is 0 or 1 for odd skill levels */
+ }
+ curChampion->_statistics[kDMStatVitality][kDMStatMaximum] += vitalityAmount;
+ uint16 staminaAmount = curChampion->_maxStamina;
+ curChampion->_statistics[kDMStatAntifire][kDMStatMaximum] += _vm->getRandomNumber(2) & ~skillLevelAfter; /* The amount is 0 for odd skill levels. The amount is 0 or 1 for even skill levels */
+ bool increaseManaFl = false;
+ switch (baseSkillIndex) {
+ case kDMSkillFighter:
+ staminaAmount >>= 4;
+ skillLevelAfter *= 3;
+ curChampion->_statistics[kDMStatStrength][kDMStatMaximum] += majorStatIncrease;
+ curChampion->_statistics[kDMStatDexterity][kDMStatMaximum] += minorStatIncrease;
+ break;
+ case kDMSkillNinja:
+ staminaAmount /= 21;
+ skillLevelAfter <<= 1;
+ curChampion->_statistics[kDMStatStrength][kDMStatMaximum] += minorStatIncrease;
+ curChampion->_statistics[kDMStatDexterity][kDMStatMaximum] += majorStatIncrease;
+ break;
+ case kDMSkillWizard:
+ staminaAmount >>= 5;
+ curChampion->_maxMana += skillLevelAfter + (skillLevelAfter >> 1);
+ curChampion->_statistics[kDMStatWisdom][kDMStatMaximum] += majorStatIncrease;
+ increaseManaFl = true;
+ break;
+ case kDMSkillPriest:
+ staminaAmount /= 25;
+ curChampion->_maxMana += skillLevelAfter;
+ skillLevelAfter += (skillLevelAfter + 1) >> 1;
+ curChampion->_statistics[kDMStatWisdom][kDMStatMaximum] += minorStatIncrease;
+ increaseManaFl = true;
+ break;
+ default:
+ break;
+ }
+ if (increaseManaFl) {
+ if ((curChampion->_maxMana += MIN(_vm->getRandomNumber(4), (uint16)(newBaseSkillLevel - 1))) > 900)
+ curChampion->_maxMana = 900;
+ curChampion->_statistics[kDMStatAntimagic][kDMStatMaximum] += _vm->getRandomNumber(3);
+ }
+
+ if ((curChampion->_maxHealth += skillLevelAfter + _vm->getRandomNumber((skillLevelAfter >> 1) + 1)) > 999)
+ curChampion->_maxHealth = 999;
+
+ if ((curChampion->_maxStamina += staminaAmount + _vm->getRandomNumber((staminaAmount >> 1) + 1)) > 9999)
+ curChampion->_maxStamina = 9999;
+
+ setFlag(curChampion->_attributes, kDMAttributeStatistics);
+ drawChampionState((ChampionIndex)champIndex);
+ _vm->_textMan->printLineFeed();
+ Color curChampionColor = _championColor[champIndex];
+ _vm->_textMan->printMessage(curChampionColor, curChampion->_name);
+
+ switch (_vm->getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY: _vm->_textMan->printMessage(curChampionColor, " JUST GAINED A "); break;
+ case Common::DE_DEU: _vm->_textMan->printMessage(curChampionColor, " HAT SOEBEN STUFE"); break;
+ case Common::FR_FRA: _vm->_textMan->printMessage(curChampionColor, " VIENT DE DEVENIR "); break;
+ }
+
+ _vm->_textMan->printMessage(curChampionColor, _baseSkillName[baseSkillIndex]);
+
+ switch (_vm->getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY: _vm->_textMan->printMessage(curChampionColor, "!"); break;
+ case Common::DE_DEU: _vm->_textMan->printMessage(curChampionColor, " LEVEL!"); break;
+ case Common::FR_FRA: _vm->_textMan->printMessage(curChampionColor, " ERREICHT!"); break;
+ }
+ }
+ }
+}
+
+int16 ChampionMan::getDamagedChampionCount(uint16 attack, int16 wounds, int16 attackType) {
+ int16 randomMax = (attack >> 3) + 1;
+ uint16 reducedAttack = attack - randomMax;
+ randomMax <<= 1;
+
+ int16 damagedChampionCount = 0;
+ for (int16 championIndex = kDMChampionFirst; championIndex < _partyChampionCount; championIndex++) {
+ // Actual attack is attack +/- (attack / 8)
+ if (addPendingDamageAndWounds_getDamage(championIndex, MAX(1, reducedAttack + _vm->getRandomNumber(randomMax)), wounds, attackType))
+ damagedChampionCount++;
+ }
+
+ return damagedChampionCount;
+}
+
+int16 ChampionMan::getTargetChampionIndex(int16 mapX, int16 mapY, uint16 cell) {
+ if (_partyChampionCount && (getDistance(mapX, mapY, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY) <= 1)) {
+ signed char orderedCellsToAttack[4];
+ _vm->_groupMan->setOrderedCellsToAttack(orderedCellsToAttack, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, mapX, mapY, cell);
+ for (uint16 i = 0; i < 4; i++) {
+ int16 championIndex = getIndexInCell(orderedCellsToAttack[i]);
+ if (championIndex >= 0)
+ return championIndex;
+ }
+ }
+ return kDMChampionNone;
+}
+
+int16 ChampionMan::getDexterity(Champion *champ) {
+ int16 dexterity = _vm->getRandomNumber(8) + champ->_statistics[kDMStatDexterity][kDMStatCurrent];
+ dexterity -= ((int32)(dexterity >> 1) * (int32)champ->_load) / getMaximumLoad(champ);
+ if (_partyIsSleeping)
+ dexterity >>= 1;
+
+ return getBoundedValue(1 + _vm->getRandomNumber(8), dexterity >> 1, 100 - _vm->getRandomNumber(8));
+}
+
+bool ChampionMan::isLucky(Champion *champ, uint16 percentage) {
+ if (_vm->getRandomNumber(2) && (_vm->getRandomNumber(100) > percentage))
+ return true;
+
+ unsigned char *curStat = champ->_statistics[kDMStatLuck];
+ bool isLucky = (_vm->getRandomNumber(curStat[kDMStatCurrent]) > percentage);
+ curStat[kDMStatCurrent] = getBoundedValue<char>(curStat[kDMStatMinimum], curStat[kDMStatCurrent] + (isLucky ? -2 : 2), curStat[kDMStatMaximum]);
+ return isLucky;
+}
+
+void ChampionMan::championPoison(int16 champIndex, uint16 attack) {
+ if ((champIndex == kDMChampionNone) || (_vm->indexToOrdinal(champIndex) == _candidateChampionOrdinal))
+ return;
+
+ Champion *curChampion = &_champions[champIndex];
+ addPendingDamageAndWounds_getDamage(champIndex, MAX(1, attack >> 6), kDMWoundNone, kDMAttackTypeNormal);
+ setFlag(curChampion->_attributes, kDMAttributeStatistics);
+ if ((_vm->indexToOrdinal(champIndex) == _vm->_inventoryMan->_inventoryChampionOrdinal) && (_vm->_inventoryMan->_panelContent == k0_PanelContentFoodWaterPoisoned)) {
+ setFlag(curChampion->_attributes, kDMAttributePanel);
+ }
+
+ if (--attack) {
+ curChampion->_poisonEventCount++;
+ TimelineEvent newEvent;
+ newEvent._type = k75_TMEventTypePoisonChampion;
+ newEvent._priority = champIndex;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + 36);
+ newEvent._B._attack = attack;
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ }
+
+ drawChampionState((ChampionIndex)champIndex);
+}
+
+void ChampionMan::setPartyDirection(int16 dir) {
+ if (dir == _vm->_dungeonMan->_partyDir)
+ return;
+
+ int16 L0834_i_Delta = dir - _vm->_dungeonMan->_partyDir;
+ if (L0834_i_Delta < 0)
+ L0834_i_Delta += 4;
+
+ Champion *curChampion = _champions;
+ for (int16 i = kDMChampionFirst; i < _partyChampionCount; i++) {
+ curChampion->_cell = (ViewCell)normalizeModulo4(curChampion->_cell + L0834_i_Delta);
+ curChampion->_dir = (Direction)normalizeModulo4(curChampion->_dir + L0834_i_Delta);
+ curChampion++;
+ }
+
+ _vm->_dungeonMan->_partyDir = (Direction)dir;
+ drawChangedObjectIcons();
+}
+
+void ChampionMan::deleteScent(uint16 scentIndex) {
+ uint16 count = --_party._scentCount - scentIndex;
+
+ if (count) {
+ for (uint16 i = 0; i < count; ++i) {
+ _party._scents[scentIndex + i] = _party._scents[scentIndex + i + 1];
+ _party._scentStrengths[scentIndex + i] = _party._scentStrengths[scentIndex + i + 1];
+ }
+ }
+
+ if (scentIndex < _party._firstScentIndex)
+ _party._firstScentIndex--;
+
+ if (scentIndex < _party._lastScentIndex)
+ _party._lastScentIndex--;
+}
+
+void ChampionMan::addScentStrength(int16 mapX, int16 mapY, int32 cycleCount) {
+ int16 scentIndex = _party._scentCount;
+ if (scentIndex) {
+ bool mergeFl = getFlag(cycleCount, kDMMaskMergeCycles);
+ if (mergeFl)
+ clearFlag(cycleCount, kDMMaskMergeCycles);
+
+ Scent newScent; /* BUG0_00 Useless code */
+ newScent.setMapX(mapX); /* BUG0_00 Useless code */
+ newScent.setMapY(mapY); /* BUG0_00 Useless code */
+ newScent.setMapIndex(_vm->_dungeonMan->_currMapIndex); /* BUG0_00 Useless code */
+
+ Scent *curScent = _party._scents; /* BUG0_00 Useless code */
+ bool cycleCountDefined = false;
+ while (scentIndex--) {
+ if (&*curScent++ == &newScent) {
+ if (!cycleCountDefined) {
+ cycleCountDefined = true;
+ if (mergeFl) {
+ cycleCount = MAX<int32>(_party._scentStrengths[scentIndex], cycleCount);
+ } else {
+ cycleCount = MIN<int32>(80, _party._scentStrengths[scentIndex] + cycleCount);
+ }
+ }
+ _party._scentStrengths[scentIndex] = cycleCount;
+ }
+ }
+ }
+}
+
+void ChampionMan::putObjectInLeaderHand(Thing thing, bool setMousePointer) {
+ if (thing == Thing::_none)
+ return;
+
+ _leaderEmptyHanded = false;
+ _vm->_objectMan->extractIconFromBitmap(_leaderHandObjectIconIndex = _vm->_objectMan->getIconIndex(_leaderHandObject = thing), _vm->_objectMan->_objectIconForMousePointer);
+ _vm->_eventMan->showMouse();
+ _vm->_objectMan->drawLeaderObjectName(thing);
+
+ if (setMousePointer)
+ _vm->_setMousePointerToObjectInMainLoop = true;
+ else
+ _vm->_eventMan->setPointerToObject(_vm->_objectMan->_objectIconForMousePointer);
+
+ _vm->_eventMan->hideMouse();
+ if (_leaderIndex != kDMChampionNone) {
+ _champions[_leaderIndex]._load += _vm->_dungeonMan->getObjectWeight(thing);
+ setFlag(_champions[_leaderIndex]._attributes, kDMAttributeLoad);
+ drawChampionState(_leaderIndex);
+ }
+}
+
+int16 ChampionMan::getMovementTicks(Champion *champ) {
+ uint16 maximumLoad = getMaximumLoad(champ);
+ uint16 curLoad = champ->_load;
+ uint16 woundTicks;
+ int16 ticks;
+ /* BUG0_72 - Fixed
+ The party moves very slowly even though no champion 'Load' value is drawn in red.
+ When the Load of a champion has exactly the maximum value he can carry then the Load
+ is drawn in yellow but the speed is the same as when the champion is overloaded
+ (when the Load is drawn in red). The comparison operator should be >= instead of >
+ */
+ if (maximumLoad >= curLoad) {
+ ticks = 2;
+ if (((int32)curLoad << 3) > ((int32)maximumLoad * 5))
+ ticks++;
+
+ woundTicks = 1;
+ } else {
+ ticks = 4 + (((curLoad - maximumLoad) << 2) / maximumLoad);
+ woundTicks = 2;
+ }
+
+ if (getFlag(champ->_wounds, kDMWoundFeet))
+ ticks += woundTicks;
+
+ if (_vm->_objectMan->getIconIndex(champ->_slots[kDMSlotFeet]) == kDMIconIndiceArmourBootOfSpeed)
+ ticks--;
+
+ return ticks;
+}
+
+bool ChampionMan::isAmmunitionCompatibleWithWeapon(uint16 champIndex, uint16 weaponSlotIndex, uint16 ammunitionSlotIndex) {
+ Champion *curChampion = &_champions[champIndex];
+ Thing curThing = curChampion->_slots[weaponSlotIndex];
+ if (curThing.getType() != k5_WeaponThingType)
+ return false;
+
+ WeaponInfo *weaponInfo = _vm->_dungeonMan->getWeaponInfo(curThing);
+ int16 weaponClass = kM1_WeaponClassNone;
+
+ if ((weaponInfo->_class >= k16_WeaponClassFirstBow) && (weaponInfo->_class <= k31_WeaponClassLastBow))
+ weaponClass = k10_WeaponClassBowAmmunition;
+ else if ((weaponInfo->_class >= k32_WeaponClassFirstSling) && (weaponInfo->_class <= k47_WeaponClassLastSling))
+ weaponClass = k11_WeaponClassSlingAmmunition;
+
+ if (weaponClass == kM1_WeaponClassNone)
+ return false;
+
+ curThing = curChampion->_slots[ammunitionSlotIndex];
+ weaponInfo = _vm->_dungeonMan->getWeaponInfo(curThing);
+ return ((curThing.getType() == k5_WeaponThingType) && (weaponInfo->_class == weaponClass));
+}
+
+void ChampionMan::drawAllChampionStates() {
+ for (int16 i = kDMChampionFirst; i < _partyChampionCount; i++)
+ drawChampionState((ChampionIndex)i);
+}
+
+void ChampionMan::viAltarRebirth(uint16 champIndex) {
+ Champion *curChampion = &_champions[champIndex];
+ if (getIndexInCell(curChampion->_cell) != kDMChampionNone) {
+ uint16 numCell = k0_CellNorthWest;
+ while (getIndexInCell(numCell) != kDMChampionNone)
+ numCell++;
+
+ curChampion->_cell = (ViewCell)numCell;
+ }
+
+ uint16 maximumHealth = curChampion->_maxHealth;
+ curChampion->_maxHealth = MAX(25, maximumHealth - (maximumHealth >> 6) - 1);
+ curChampion->_currHealth = curChampion->_maxHealth >> 1;
+ _vm->_menuMan->drawSpellAreaControls(_magicCasterChampionIndex);
+ curChampion->_dir = _vm->_dungeonMan->_partyDir;
+ setFlag(curChampion->_attributes, kDMAttributeActionHand | kDMAttributeStatusBox | kDMAttributeIcon);
+ drawChampionState((ChampionIndex)champIndex);
+}
+
+void ChampionMan::clickOnSlotBox(uint16 slotBoxIndex) {
+ uint16 champIndex;
+ uint16 slotIndex;
+
+ if (slotBoxIndex < k8_SlotBoxInventoryFirstSlot) {
+ if (_candidateChampionOrdinal)
+ return;
+
+ champIndex = slotBoxIndex >> 1;
+ if ((champIndex >= _partyChampionCount) || (_vm->indexToOrdinal(champIndex) == _vm->_inventoryMan->_inventoryChampionOrdinal) || !_champions[champIndex]._currHealth)
+ return;
+
+ slotIndex = getHandSlotIndex(slotBoxIndex);
+ } else {
+ champIndex = _vm->ordinalToIndex(_vm->_inventoryMan->_inventoryChampionOrdinal);
+ slotIndex = slotBoxIndex - k8_SlotBoxInventoryFirstSlot;
+ }
+
+ Thing leaderHandObject = _leaderHandObject;
+ Thing slotThing;
+ if (slotIndex >= kDMSlotChest1) {
+ slotThing = _vm->_inventoryMan->_chestSlots[slotIndex - kDMSlotChest1];
+ } else {
+ slotThing = _champions[champIndex]._slots[slotIndex];
+ }
+
+ if ((slotThing == Thing::_none) && (leaderHandObject == Thing::_none))
+ return;
+
+ if ((leaderHandObject != Thing::_none) && (!(_vm->_dungeonMan->_objectInfos[_vm->_dungeonMan->getObjectInfoIndex(leaderHandObject)]._allowedSlots & _slotMasks[slotIndex])))
+ return;
+
+ _vm->_eventMan->showMouse();
+ if (leaderHandObject != Thing::_none)
+ getObjectRemovedFromLeaderHand();
+
+ if (slotThing != Thing::_none) {
+ getObjectRemovedFromSlot(champIndex, slotIndex);
+ putObjectInLeaderHand(slotThing, false);
+ }
+
+ if (leaderHandObject != Thing::_none)
+ addObjectInSlot((ChampionIndex)champIndex, leaderHandObject, (ChampionSlot)slotIndex);
+
+ drawChampionState((ChampionIndex)champIndex);
+ _vm->_eventMan->hideMouse();
+}
+
+bool ChampionMan::isProjectileSpellCast(uint16 champIndex, Thing thing, int16 kineticEnergy, uint16 requiredManaAmount) {
+ Champion *curChampion = &_champions[champIndex];
+ if (curChampion->_currMana < requiredManaAmount)
+ return false;
+
+ curChampion->_currMana -= requiredManaAmount;
+ setFlag(curChampion->_attributes, kDMAttributeStatistics);
+ int16 stepEnergy = 10 - MIN(8, curChampion->_maxMana >> 3);
+ if (kineticEnergy < (stepEnergy << 2)) {
+ kineticEnergy += 3;
+ stepEnergy--;
+ }
+
+ championShootProjectile(curChampion, thing, kineticEnergy, 90, stepEnergy);
+ return true; // fix BUG_01
+}
+
+void ChampionMan::championShootProjectile(Champion *champ, Thing thing, int16 kineticEnergy, int16 attack, int16 stepEnergy) {
+ Direction newDirection = champ->_dir;
+ _vm->_projexpl->createProjectile(thing, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, normalizeModulo4((((champ->_cell - newDirection + 1) & 0x0002) >> 1) + newDirection), newDirection, kineticEnergy, attack, stepEnergy);
+ _vm->_projectileDisableMovementTicks = 4;
+ _vm->_lastProjectileDisabledMovementDirection = newDirection;
+}
+
+void ChampionMan::applyAndDrawPendingDamageAndWounds() {
+ Champion *championPtr = _champions;
+ for (uint16 championIndex = kDMChampionFirst; championIndex < _partyChampionCount; championIndex++, championPtr++) {
+ int16 pendingWounds = _championPendingWounds[championIndex];
+ setFlag(championPtr->_wounds, pendingWounds);
+ _championPendingWounds[championIndex] = 0;
+ uint16 pendingDamage = _championPendingDamage[championIndex];
+ if (!pendingDamage)
+ continue;
+
+ _championPendingDamage[championIndex] = 0;
+ int16 curHealth = championPtr->_currHealth;
+ if (!curHealth)
+ continue;
+
+ // DEBUG CODE
+ if (_vm->_console->_debugGodmodeHP == false)
+ curHealth -= pendingDamage;
+
+ if (curHealth <= 0) {
+ championKill(championIndex);
+ } else {
+ championPtr->_currHealth = curHealth;
+ setFlag(championPtr->_attributes, kDMAttributeStatistics);
+ if (pendingWounds) {
+ setFlag(championPtr->_attributes, kDMAttributeWounds);
+ }
+
+ int16 textPosX = championIndex * k69_ChampionStatusBoxSpacing;
+ int16 textPosY;
+
+ Box blitBox;
+ blitBox._y1 = 0;
+ _vm->_eventMan->showMouse();
+
+ if (_vm->indexToOrdinal(championIndex) == _vm->_inventoryMan->_inventoryChampionOrdinal) {
+ blitBox._y2 = 28;
+ blitBox._x1 = textPosX + 7;
+ blitBox._x2 = blitBox._x1 + 31; /* Box is over the champion portrait in the status box */
+ _vm->_displayMan->blitToScreen(_vm->_displayMan->getNativeBitmapOrGraphic(k16_damageToChampionBig), &blitBox, k16_byteWidth, k10_ColorFlesh, 29);
+ // Check the number of digits and sets the position accordingly.
+ if (pendingDamage < 10) // 1 digit
+ textPosX += 21;
+ else if (pendingDamage < 100) // 2 digits
+ textPosX += 18;
+ else // 3 digits
+ textPosX += 15;
+
+ textPosY = 16;
+ } else {
+ blitBox._y2 = 6;
+ blitBox._x1 = textPosX;
+ blitBox._x2 = blitBox._x1 + 47; /* Box is over the champion name in the status box */
+ _vm->_displayMan->blitToScreen(_vm->_displayMan->getNativeBitmapOrGraphic(k15_damageToChampionSmallIndice), &blitBox, k24_byteWidth, k10_ColorFlesh, 7);
+ // Check the number of digits and sets the position accordingly.
+ if (pendingDamage < 10) // 1 digit
+ textPosX += 19;
+ else if (pendingDamage < 100) // 2 digits
+ textPosX += 16;
+ else //3 digits
+ textPosX += 13;
+
+ textPosY = 5;
+ }
+ _vm->_textMan->printToLogicalScreen(textPosX, textPosY, k15_ColorWhite, k8_ColorRed, getStringFromInteger(pendingDamage, false, 3).c_str());
+
+ int16 eventIndex = championPtr->_hideDamageReceivedIndex;
+ if (eventIndex == -1) {
+ TimelineEvent newEvent;
+ newEvent._type = k12_TMEventTypeHideDamageReceived;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + 5);
+ newEvent._priority = championIndex;
+ championPtr->_hideDamageReceivedIndex = _vm->_timeline->addEventGetEventIndex(&newEvent);
+ } else {
+ TimelineEvent *curEvent = &_vm->_timeline->_events[eventIndex];
+ setMapAndTime(curEvent->_mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + 5);
+ _vm->_timeline->fixChronology(_vm->_timeline->getIndex(eventIndex));
+ }
+ drawChampionState((ChampionIndex)championIndex);
+ _vm->_eventMan->hideMouse();
+ }
+ }
+}
+
+void ChampionMan::championKill(uint16 champIndex) {
+ Champion *curChampion = &_champions[champIndex];
+ curChampion->_currHealth = 0;
+ setFlag(curChampion->_attributes, kDMAttributeStatusBox);
+ if (_vm->indexToOrdinal(champIndex) == _vm->_inventoryMan->_inventoryChampionOrdinal) {
+ if (_vm->_pressingEye) {
+ _vm->_pressingEye = false;
+ _vm->_eventMan->_ignoreMouseMovements = false;
+ if (!_leaderEmptyHanded) {
+ _vm->_objectMan->drawLeaderObjectName(_leaderHandObject);
+ }
+ _vm->_eventMan->_hideMousePointerRequestCount = 1;
+ _vm->_eventMan->hideMouse();
+ } else if (_vm->_pressingMouth) {
+ _vm->_pressingMouth = false;
+ _vm->_eventMan->_ignoreMouseMovements = false;
+ _vm->_eventMan->_hideMousePointerRequestCount = 1;
+ _vm->_eventMan->hideMouse();
+ }
+ _vm->_inventoryMan->toggleInventory(kDMChampionCloseInventory);
+ }
+ dropAllObjects(champIndex);
+ Thing unusedThing = _vm->_dungeonMan->getUnusedThing(k0x8000_championBones | k10_JunkThingType);
+ uint16 curCell = 0;
+ if (unusedThing != Thing::_none) {
+ Junk *L0966_ps_Junk = (Junk *)_vm->_dungeonMan->getThingData(unusedThing);
+ L0966_ps_Junk->setType(k5_JunkTypeBones);
+ L0966_ps_Junk->setDoNotDiscard(true);
+ L0966_ps_Junk->setChargeCount(champIndex);
+ curCell = curChampion->_cell;
+ _vm->_moveSens->getMoveResult(thingWithNewCell(unusedThing, curCell), kM1_MapXNotOnASquare, 0, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY);
+ }
+ curChampion->_symbolStep = 0;
+ curChampion->_symbols[0] = '\0';
+ curChampion->_dir = _vm->_dungeonMan->_partyDir;
+ curChampion->_maximumDamageReceived = 0;
+ uint16 curChampionIconIndex = getChampionIconIndex(curCell, _vm->_dungeonMan->_partyDir);
+ if (_vm->indexToOrdinal(curChampionIconIndex) == _vm->_eventMan->_useChampionIconOrdinalAsMousePointerBitmap) {
+ _vm->_eventMan->_mousePointerBitmapUpdated = true;
+ _vm->_eventMan->_useChampionIconOrdinalAsMousePointerBitmap = _vm->indexToOrdinal(kDMChampionNone);
+ }
+
+ if (curChampion->_poisonEventCount)
+ unpoison(champIndex);
+
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ _vm->_displayMan->fillScreenBox(_boxChampionIcons[curChampionIconIndex], k0_ColorBlack);
+ drawChampionState((ChampionIndex)champIndex);
+
+ ChampionIndex aliveChampionIndex;
+ int idx = 0;
+ for (curChampion = _champions; (idx < _partyChampionCount) && (curChampion->_currHealth == 0); idx++, curChampion++)
+ ;
+
+ aliveChampionIndex = (ChampionIndex)idx;
+ if (aliveChampionIndex == _partyChampionCount) { /* BUG0_43 The game does not end if the last living champion in the party is killed while looking at a candidate champion in a portrait. The condition to end the game when the whole party is killed is not true because the code considers the candidate champion as alive (in the loop above) */
+ _partyDead = true;
+ return;
+ }
+
+ if (champIndex == _leaderIndex)
+ _vm->_eventMan->commandSetLeader(aliveChampionIndex);
+
+ if (champIndex == _magicCasterChampionIndex)
+ _vm->_menuMan->setMagicCasterAndDrawSpellArea(aliveChampionIndex);
+ else
+ _vm->_menuMan->drawSpellAreaControls(_magicCasterChampionIndex);
+}
+
+void ChampionMan::dropAllObjects(uint16 champIndex) {
+ static const int16 slotDropOrder[30] = {
+ kDMSlotFeet,
+ kDMSlotLegs,
+ kDMSlotQuiverLine2_2,
+ kDMSlotQuiverLine1_2,
+ kDMSlotQuiverLine2_1,
+ kDMSlotQuiverLine1_1,
+ kDMSlotPouch_2,
+ kDMSlotPouch1,
+ kDMSlotTorso,
+ kDMSlotBackpackLine1_1,
+ kDMSlotBackpackLine2_2,
+ kDMSlotBackpackLine2_3,
+ kDMSlotBackpackLine2_4,
+ kDMSlotBackpackLine2_5,
+ kDMSlotBackpackLine2_6,
+ kDMSlotBackpackLine2_7,
+ kDMSlotBackpackLine2_8,
+ kDMSlotBackpackLine2_9,
+ kDMSlotBackpackLine1_2,
+ kDMSlotBackpackLine1_3,
+ kDMSlotBackpackLine1_4,
+ kDMSlotBackpackLine1_5,
+ kDMSlotBackpackLine1_6,
+ kDMSlotBackpackLine1_7,
+ kDMSlotBackpackLine1_8,
+ kDMSlotBackpackLine1_9,
+ kDMSlotNeck,
+ kDMSlotHead,
+ kDMSlotReadyHand,
+ kDMSlotActionHand
+ };
+
+ uint16 curCell = _champions[champIndex]._cell;
+ for (uint16 slotIndex = kDMSlotReadyHand; slotIndex < kDMSlotChest1; slotIndex++) {
+ Thing curThing = getObjectRemovedFromSlot(champIndex, slotDropOrder[slotIndex]);
+ if (curThing != Thing::_none)
+ _vm->_moveSens->getMoveResult(thingWithNewCell(curThing, curCell), kM1_MapXNotOnASquare, 0, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY);
+ }
+}
+
+void ChampionMan::unpoison(int16 champIndex) {
+ if (champIndex == kDMChampionNone)
+ return;
+
+ TimelineEvent *eventPtr = _vm->_timeline->_events;
+ for (uint16 eventIndex = 0; eventIndex < _vm->_timeline->_eventMaxCount; eventPtr++, eventIndex++) {
+ if ((eventPtr->_type == k75_TMEventTypePoisonChampion) && (eventPtr->_priority == champIndex))
+ _vm->_timeline->deleteEvent(eventIndex);
+ }
+ _champions[champIndex]._poisonEventCount = 0;
+}
+
+void ChampionMan::applyTimeEffects() {
+ if (!_partyChampionCount)
+ return;
+
+ Scent checkScent;
+ checkScent.setMapX(_vm->_dungeonMan->_partyMapX);
+ checkScent.setMapY(_vm->_dungeonMan->_partyMapY);
+ checkScent.setMapIndex(_vm->_dungeonMan->_partyMapIndex);
+
+ for (byte loopScentIndex = 0; loopScentIndex + 1 < _party._scentCount; loopScentIndex++) {
+ if (&_party._scents[loopScentIndex] != &checkScent) {
+ _party._scentStrengths[loopScentIndex] = MAX(0, _party._scentStrengths[loopScentIndex] - 1);
+ if (!_party._scentStrengths[loopScentIndex] && !loopScentIndex) {
+ deleteScent(0);
+ continue;
+ }
+ }
+ }
+
+ uint16 gameTime = _vm->_gameTime & 0xFFFF;
+ uint16 timeCriteria = (((gameTime & 0x0080) + ((gameTime & 0x0100) >> 2)) + ((gameTime & 0x0040) << 2)) >> 2;
+ Champion *championPtr = _champions;
+ for (uint16 championIndex = kDMChampionFirst; championIndex < _partyChampionCount; championIndex++, championPtr++) {
+ if (championPtr->_currHealth && (_vm->indexToOrdinal(championIndex) != _candidateChampionOrdinal)) {
+ uint16 wizardSkillLevel = getSkillLevel(championIndex, kDMSkillWizard) + getSkillLevel(championIndex, kDMSkillPriest);
+ if ((championPtr->_currMana < championPtr->_maxMana)
+ && (timeCriteria < championPtr->_statistics[kDMStatWisdom][kDMStatCurrent] + wizardSkillLevel)) {
+ int16 manaGain = championPtr->_maxMana / 40;
+ if (_partyIsSleeping)
+ manaGain <<= 1;
+
+ manaGain++;
+ decrementStamina(championIndex, manaGain * MAX(7, 16 - wizardSkillLevel));
+ championPtr->_currMana += MIN<int16>(manaGain, championPtr->_maxMana - championPtr->_currMana);
+ } else if (championPtr->_currMana > championPtr->_maxMana)
+ championPtr->_currMana--;
+
+ for (int16 idx = kDMSkillWater; idx >= kDMSkillFighter; idx--) {
+ if (championPtr->_skills[idx]._temporaryExperience > 0)
+ championPtr->_skills[idx]._temporaryExperience--;
+ }
+ uint16 staminaGainCycleCount = 4;
+ int16 staminaMagnitude = championPtr->_maxStamina;
+ while (championPtr->_currStamina < (staminaMagnitude >>= 1))
+ staminaGainCycleCount += 2;
+
+ int16 staminaLoss = 0;
+ int16 staminaAmount = getBoundedValue(1, (championPtr->_maxStamina >> 8) - 1, 6);
+ if (_partyIsSleeping)
+ staminaAmount <<= 1;
+
+ int32 compDelay = _vm->_gameTime - _vm->_projexpl->_lastPartyMovementTime;
+ if (compDelay > 80) {
+ staminaAmount++;
+ if (compDelay > 250)
+ staminaAmount++;
+ }
+ do {
+ bool staminaAboveHalf = (staminaGainCycleCount <= 4);
+ if (championPtr->_food < -512) {
+ if (staminaAboveHalf) {
+ staminaLoss += staminaAmount;
+ championPtr->_food -= 2;
+ }
+ } else {
+ if (championPtr->_food >= 0)
+ staminaLoss -= staminaAmount;
+
+ championPtr->_food -= staminaAboveHalf ? 2 : staminaGainCycleCount >> 1;
+ }
+ if (championPtr->_water < -512) {
+ if (staminaAboveHalf) {
+ staminaLoss += staminaAmount;
+ championPtr->_water -= 1;
+ }
+ } else {
+ if (championPtr->_water >= 0)
+ staminaLoss -= staminaAmount;
+
+ championPtr->_water -= staminaAboveHalf ? 1 : staminaGainCycleCount >> 2;
+ }
+ } while (--staminaGainCycleCount && ((championPtr->_currStamina - staminaLoss) < championPtr->_maxStamina));
+ decrementStamina(championIndex, staminaLoss);
+ if (championPtr->_food < -1024)
+ championPtr->_food = -1024;
+
+ if (championPtr->_water < -1024)
+ championPtr->_water = -1024;
+
+ if ((championPtr->_currHealth < championPtr->_maxHealth) && (championPtr->_currStamina >= (championPtr->_maxStamina >> 2)) && (timeCriteria < (championPtr->_statistics[kDMStatVitality][kDMStatCurrent] + 12))) {
+ int16 healthGain = (championPtr->_maxHealth >> 7) + 1;
+ if (_partyIsSleeping)
+ healthGain <<= 1;
+
+ if (_vm->_objectMan->getIconIndex(championPtr->_slots[kDMSlotNeck]) == kDMIconIndiceJunkEkkhardCross)
+ healthGain += (healthGain >> 1) + 1;
+
+ championPtr->_currHealth += MIN(healthGain, (int16)(championPtr->_maxHealth - championPtr->_currHealth));
+ }
+ if (!((int)_vm->_gameTime & (_partyIsSleeping ? 63 : 255))) {
+ for (uint16 i = kDMStatLuck; i <= kDMStatAntifire; i++) {
+ byte *curStatistic = championPtr->_statistics[i];
+ uint16 statisticMaximum = curStatistic[kDMStatMaximum];
+ if (curStatistic[kDMStatCurrent] < statisticMaximum)
+ curStatistic[kDMStatCurrent]++;
+ else if (curStatistic[kDMStatCurrent] > statisticMaximum)
+ curStatistic[kDMStatCurrent] -= curStatistic[kDMStatCurrent] / statisticMaximum;
+ }
+ }
+ if (!_partyIsSleeping && (championPtr->_dir != _vm->_dungeonMan->_partyDir) && (_vm->_projexpl->_lastCreatureAttackTime + 60 < _vm->_gameTime)) {
+ championPtr->_dir = _vm->_dungeonMan->_partyDir;
+ championPtr->_maximumDamageReceived = 0;
+ setFlag(championPtr->_attributes, kDMAttributeIcon);
+ }
+ setFlag(championPtr->_attributes, kDMAttributeStatistics);
+ if (_vm->indexToOrdinal(championIndex) == _vm->_inventoryMan->_inventoryChampionOrdinal) {
+ if (_vm->_pressingMouth || _vm->_pressingEye || (_vm->_inventoryMan->_panelContent == k0_PanelContentFoodWaterPoisoned)) {
+ setFlag(championPtr->_attributes, kDMAttributePanel);
+ }
+ }
+ }
+ }
+ drawAllChampionStates();
+}
+
+void ChampionMan::savePartyPart2(Common::OutSaveFile *file) {
+ for (uint16 i = 0; i < 4; ++i) {
+ Champion *champ = &_champions[i];
+ file->writeUint16BE(champ->_attributes);
+ file->writeUint16BE(champ->_wounds);
+ for (uint16 y = 0; y < 7; ++y)
+ for (uint16 x = 0; x < 3; ++x)
+ file->writeByte(champ->_statistics[y][x]);
+ for (uint16 j = 0; j < 30; ++j)
+ file->writeUint16BE(champ->_slots[j].toUint16());
+ for (uint16 j = 0; j < 20; ++j) {
+ file->writeSint16BE(champ->_skills[j]._temporaryExperience);
+ file->writeSint32BE(champ->_skills[j]._experience);
+ }
+ for (uint16 j = 0; j < 8; ++j)
+ file->writeByte(champ->_name[j]);
+ for (uint16 j = 0; j < 20; ++j)
+ file->writeByte(champ->_title[j]);
+ file->writeUint16BE(champ->_dir);
+ file->writeUint16BE(champ->_cell);
+ file->writeUint16BE(champ->_actionIndex);
+ file->writeUint16BE(champ->_symbolStep);
+ for (uint16 j = 0; j < 5; ++j)
+ file->writeByte(champ->_symbols[j]);
+ file->writeUint16BE(champ->_directionMaximumDamageReceived);
+ file->writeUint16BE(champ->_maximumDamageReceived);
+ file->writeUint16BE(champ->_poisonEventCount);
+ file->writeSint16BE(champ->_enableActionEventIndex);
+ file->writeSint16BE(champ->_hideDamageReceivedIndex);
+ file->writeSint16BE(champ->_currHealth);
+ file->writeSint16BE(champ->_maxHealth);
+ file->writeSint16BE(champ->_currStamina);
+ file->writeSint16BE(champ->_maxStamina);
+ file->writeSint16BE(champ->_currMana);
+ file->writeSint16BE(champ->_maxMana);
+ file->writeSint16BE(champ->_actionDefense);
+ file->writeSint16BE(champ->_food);
+ file->writeSint16BE(champ->_water);
+ file->writeUint16BE(champ->_load);
+ file->writeSint16BE(champ->_shieldDefense);
+ for (uint16 j = 0; j < 928; ++j)
+ file->writeByte(champ->_portrait[j]);
+ }
+
+ Party &party = _party;
+ file->writeSint16BE(party._magicalLightAmount);
+ file->writeByte(party._event73Count_ThievesEye);
+ file->writeByte(party._event79Count_Footprints);
+ file->writeSint16BE(party._shieldDefense);
+ file->writeSint16BE(party._fireShieldDefense);
+ file->writeSint16BE(party._spellShieldDefense);
+ file->writeByte(party._scentCount);
+ file->writeByte(party._freezeLifeTicks);
+ file->writeByte(party._firstScentIndex);
+ file->writeByte(party._lastScentIndex);
+ for (uint16 i = 0; i < 24; ++i)
+ file->writeUint16BE(party._scents[i].toUint16());
+ for (uint16 i = 0; i < 24; ++i)
+ file->writeByte(party._scentStrengths[i]);
+ file->writeByte(party._event71Count_Invisibility);
+}
+
+void ChampionMan::loadPartyPart2(Common::InSaveFile *file) {
+ for (uint16 i = 0; i < 4; ++i) {
+ Champion *champ = &_champions[i];
+ champ->_attributes = file->readUint16BE();
+ champ->_wounds = file->readUint16BE();
+ for (uint16 y = 0; y < 7; ++y)
+ for (uint16 x = 0; x < 3; ++x)
+ champ->_statistics[y][x] = file->readByte();
+ for (uint16 j = 0; j < 30; ++j)
+ champ->_slots[j] = Thing(file->readUint16BE());
+ for (uint16 j = 0; j < 20; ++j) {
+ champ->_skills[j]._temporaryExperience = file->readSint16BE();
+ champ->_skills[j]._experience = file->readSint32BE();
+ }
+ for (uint16 j = 0; j < 8; ++j)
+ champ->_name[j] = file->readByte();
+ for (uint16 j = 0; j < 20; ++j)
+ champ->_title[j] = file->readByte();
+ champ->_dir = (Direction)file->readUint16BE();
+ champ->_cell = (ViewCell)file->readUint16BE();
+ champ->_actionIndex = (ChampionAction)file->readUint16BE();
+ champ->_symbolStep = file->readUint16BE();
+ for (uint16 j = 0; j < 5; ++j)
+ champ->_symbols[j] = file->readByte();
+ champ->_directionMaximumDamageReceived = file->readUint16BE();
+ champ->_maximumDamageReceived = file->readUint16BE();
+ champ->_poisonEventCount = file->readUint16BE();
+ champ->_enableActionEventIndex = file->readSint16BE();
+ champ->_hideDamageReceivedIndex = file->readSint16BE();
+ champ->_currHealth = file->readSint16BE();
+ champ->_maxHealth = file->readSint16BE();
+ champ->_currStamina = file->readSint16BE();
+ champ->_maxStamina = file->readSint16BE();
+ champ->_currMana = file->readSint16BE();
+ champ->_maxMana = file->readSint16BE();
+ champ->_actionDefense = file->readSint16BE();
+ champ->_food = file->readSint16BE();
+ champ->_water = file->readSint16BE();
+ champ->_load = file->readUint16BE();
+ champ->_shieldDefense = file->readSint16BE();
+ for (uint16 j = 0; j < 928; ++j)
+ champ->_portrait[j] = file->readByte();
+ }
+
+ Party &party = _party;
+ party._magicalLightAmount = file->readSint16BE();
+ party._event73Count_ThievesEye = file->readByte();
+ party._event79Count_Footprints = file->readByte();
+ party._shieldDefense = file->readSint16BE();
+ party._fireShieldDefense = file->readSint16BE();
+ party._spellShieldDefense = file->readSint16BE();
+ party._scentCount = file->readByte();
+ party._freezeLifeTicks = file->readByte();
+ party._firstScentIndex = file->readByte();
+ party._lastScentIndex = file->readByte();
+ for (uint16 i = 0; i < 24; ++i)
+ party._scents[i] = Scent(file->readUint16BE());
+ for (uint16 i = 0; i < 24; ++i)
+ party._scentStrengths[i] = file->readByte();
+ party._event71Count_Invisibility = file->readByte();
+}
+
+ChampionIndex ChampionMan::getIndexInCell(int16 cell) {
+ for (uint16 i = 0; i < _partyChampionCount; ++i) {
+ if ((_champions[i]._cell == cell) && _champions[i]._currHealth)
+ return (ChampionIndex)i;
+ }
+
+ return kDMChampionNone;
+}
+
+void ChampionMan::resetDataToStartGame() {
+ if (!_vm->_newGameFl) {
+ Thing handThing = _leaderHandObject;
+ if (handThing == Thing::_none) {
+ _leaderEmptyHanded = true;
+ _leaderHandObjectIconIndex = kDMIconIndiceNone;
+ _vm->_eventMan->setMousePointer();
+ } else
+ putObjectInLeaderHand(handThing, true); /* This call will add the weight of the leader hand object to the Load of the leader a first time */
+
+ Champion *curChampion = _champions;
+ for (int16 idx = 0; idx < _partyChampionCount; idx++, curChampion++) {
+ clearFlag(curChampion->_attributes, kDMAttributeNameTitle | kDMAttributeStatistics | kDMAttributeLoad | kDMAttributeIcon | kDMAttributePanel | kDMAttributeStatusBox | kDMAttributeWounds | kDMAttributeViewport | kDMAttributeActionHand);
+ setFlag(curChampion->_attributes, kDMAttributeActionHand | kDMAttributeStatusBox | kDMAttributeIcon);
+ }
+ drawAllChampionStates();
+
+ ChampionIndex championIndex = _leaderIndex;
+ if (championIndex != kDMChampionNone) {
+ _leaderIndex = kDMChampionNone;
+ _vm->_eventMan->commandSetLeader(championIndex);
+ }
+
+ championIndex = _magicCasterChampionIndex;
+ if (championIndex != kDMChampionNone) {
+ _magicCasterChampionIndex = kDMChampionNone;
+ _vm->_menuMan->setMagicCasterAndDrawSpellArea(championIndex);
+ }
+ return;
+ }
+
+ _leaderHandObject = Thing::_none;
+ _leaderHandObjectIconIndex = kDMIconIndiceNone;
+ _leaderEmptyHanded = true;
+}
+
+void ChampionMan::addCandidateChampionToParty(uint16 championPortraitIndex) {
+ if (!_leaderEmptyHanded)
+ return;
+
+ if (_partyChampionCount == 4)
+ return;
+
+ uint16 previousPartyChampionCount = _partyChampionCount;
+ Champion *championPtr = &_champions[previousPartyChampionCount];
+ championPtr->resetToZero();
+ // Strangerke - TODO: Check if the new code is possible to run on the older version (example: the portraits could be missing in the data)
+ _vm->_displayMan->_useByteBoxCoordinates = true;
+ _vm->_displayMan->blitToBitmap(_vm->_displayMan->getNativeBitmapOrGraphic(k26_ChampionPortraitsIndice), championPtr->_portrait, _boxChampionPortrait, getChampionPortraitX(championPortraitIndex), getChampionPortraitY(championPortraitIndex), k128_byteWidth, k16_byteWidth, kM1_ColorNoTransparency, 87, 29);
+ championPtr->_actionIndex = kDMActionNone;
+ championPtr->_enableActionEventIndex = -1;
+ championPtr->_hideDamageReceivedIndex = -1;
+ championPtr->_dir = _vm->_dungeonMan->_partyDir;
+ uint16 viewCell = k0_ViewCellFronLeft;
+ while (getIndexInCell(normalizeModulo4(viewCell + _vm->_dungeonMan->_partyDir)) != kDMChampionNone)
+ viewCell++;
+
+ championPtr->_cell = (ViewCell)normalizeModulo4(viewCell + _vm->_dungeonMan->_partyDir);
+ championPtr->_attributes = kDMAttributeIcon;
+ championPtr->_directionMaximumDamageReceived = _vm->_dungeonMan->_partyDir;
+ championPtr->_food = 1500 + _vm->getRandomNumber(256);
+ championPtr->_water = 1500 + _vm->getRandomNumber(256);
+ for (int16 slotIdx = kDMSlotReadyHand; slotIdx < kDMSlotChest1; slotIdx++)
+ championPtr->_slots[slotIdx] = Thing::_none;
+
+ Thing curThing = _vm->_dungeonMan->getSquareFirstThing(_vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY);
+ while (curThing.getType() != k2_TextstringType)
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+
+ char L0807_ac_DecodedChampionText[77];
+ char *decodedStringPtr = L0807_ac_DecodedChampionText;
+ _vm->_dungeonMan->decodeText(decodedStringPtr, curThing, (TextType)(k2_TextTypeScroll | k0x8000_DecodeEvenIfInvisible));
+
+ uint16 charIdx = 0;
+ char tmpChar;
+ while ((tmpChar = *decodedStringPtr++) != '\n')
+ championPtr->_name[charIdx++] = tmpChar;
+
+ championPtr->_name[charIdx] = '\0';
+ charIdx = 0;
+ bool championTitleCopiedFl = false;
+ for (;;) { /*_Infinite loop_*/
+ tmpChar = *decodedStringPtr++;
+ if (tmpChar == '\n') { /* New line */
+ if (championTitleCopiedFl)
+ break;
+ championTitleCopiedFl = true;
+ } else
+ championPtr->_title[charIdx++] = tmpChar;
+ }
+ championPtr->_title[charIdx] = '\0';
+ if (*decodedStringPtr++ == 'M')
+ setFlag(championPtr->_attributes, kDMAttributeMale);
+
+ decodedStringPtr++;
+ championPtr->_currHealth = championPtr->_maxHealth = getDecodedValue(decodedStringPtr, 4);
+ decodedStringPtr += 4;
+ championPtr->_currStamina = championPtr->_maxStamina = getDecodedValue(decodedStringPtr, 4);
+ decodedStringPtr += 4;
+ championPtr->_currMana = championPtr->_maxMana = getDecodedValue(decodedStringPtr, 4);
+ decodedStringPtr += 4;
+ decodedStringPtr++;
+ for (int16 statIdx = kDMStatLuck; statIdx <= kDMStatAntifire; statIdx++) {
+ championPtr->_statistics[statIdx][kDMStatMinimum] = 30;
+ championPtr->_statistics[statIdx][kDMStatCurrent] = championPtr->_statistics[statIdx][kDMStatMaximum] = getDecodedValue(decodedStringPtr, 2);
+ decodedStringPtr += 2;
+ }
+ championPtr->_statistics[kDMStatLuck][kDMStatMinimum] = 10;
+ decodedStringPtr++;
+ for (uint16 skillIdx = kDMSkillSwing; skillIdx <= kDMSkillWater; skillIdx++) {
+ int skillValue = *decodedStringPtr++ - 'A';
+ if (skillValue > 0)
+ championPtr->_skills[skillIdx]._experience = 125L << skillValue;
+ }
+ for (uint16 skillIdx = kDMSkillFighter; skillIdx <= kDMSkillWizard; skillIdx++) {
+ int32 baseSkillExperience = 0;
+ int16 hiddenSkillIndex = (skillIdx + 1) << 2;
+ for (uint16 hiddenIdx = 0; hiddenIdx < 4; hiddenIdx++)
+ baseSkillExperience += championPtr->_skills[hiddenSkillIndex + hiddenIdx]._experience;
+
+ championPtr->_skills[skillIdx]._experience = baseSkillExperience;
+ }
+ _candidateChampionOrdinal = previousPartyChampionCount + 1;
+ if (++_partyChampionCount == 1) {
+ _vm->_eventMan->commandSetLeader(kDMChampionFirst);
+ _vm->_menuMan->_refreshActionArea = true;
+ } else {
+ _vm->_menuMan->clearActingChampion();
+ _vm->_menuMan->drawActionIcon((ChampionIndex)(_partyChampionCount - 1));
+ }
+
+ int16 curMapX = _vm->_dungeonMan->_partyMapX;
+ int16 curMapY = _vm->_dungeonMan->_partyMapY;
+ uint16 championObjectsCell = returnOppositeDir(_vm->_dungeonMan->_partyDir);
+ curMapX += _vm->_dirIntoStepCountEast[_vm->_dungeonMan->_partyDir], curMapY += _vm->_dirIntoStepCountNorth[_vm->_dungeonMan->_partyDir];
+ curThing = _vm->_dungeonMan->getSquareFirstThing(curMapX, curMapY);
+ int16 slotIdx = kDMSlotBackpackLine1_1;
+ while (curThing != Thing::_endOfList) {
+ ThingType thingType = curThing.getType();
+ if ((thingType > k3_SensorThingType) && (curThing.getCell() == championObjectsCell)) {
+ int16 objectAllowedSlots = _vm->_dungeonMan->_objectInfos[_vm->_dungeonMan->getObjectInfoIndex(curThing)]._allowedSlots;
+ uint16 curSlotIndex = kDMSlotReadyHand;
+ switch (thingType) {
+ case k6_ArmourThingType: {
+ bool skipCheck = false;
+ for (curSlotIndex = kDMSlotHead; curSlotIndex <= kDMSlotFeet; curSlotIndex++) {
+ if (objectAllowedSlots & _slotMasks[curSlotIndex]) {
+ skipCheck = true;
+ break;
+ }
+ }
+
+ if (skipCheck)
+ break;
+
+ if ((objectAllowedSlots & _slotMasks[kDMSlotNeck]) && (championPtr->_slots[kDMSlotNeck] == Thing::_none))
+ curSlotIndex = kDMSlotNeck;
+ else
+ curSlotIndex = slotIdx++;
+
+ break;
+ }
+ case k5_WeaponThingType:
+ if (championPtr->_slots[kDMSlotActionHand] == Thing::_none)
+ curSlotIndex = kDMSlotActionHand;
+ else if ((objectAllowedSlots & _slotMasks[kDMSlotNeck]) && (championPtr->_slots[kDMSlotNeck] == Thing::_none))
+ curSlotIndex = kDMSlotNeck;
+ else
+ curSlotIndex = slotIdx++;
+ break;
+ case k7_ScrollThingType:
+ case k8_PotionThingType:
+ if (championPtr->_slots[kDMSlotPouch1] == Thing::_none)
+ curSlotIndex = kDMSlotPouch1;
+ else if (championPtr->_slots[kDMSlotPouch_2] == Thing::_none)
+ curSlotIndex = kDMSlotPouch_2;
+ else if ((objectAllowedSlots & _slotMasks[kDMSlotNeck]) && (championPtr->_slots[kDMSlotNeck] == Thing::_none))
+ curSlotIndex = kDMSlotNeck;
+ else
+ curSlotIndex = slotIdx++;
+ break;
+ case k9_ContainerThingType:
+ case k10_JunkThingType:
+ if ((objectAllowedSlots & _slotMasks[kDMSlotNeck]) && (championPtr->_slots[kDMSlotNeck] == Thing::_none))
+ curSlotIndex = kDMSlotNeck;
+ else
+ curSlotIndex = slotIdx++;
+
+ break;
+ default:
+ break;
+ }
+
+ while (championPtr->_slots[curSlotIndex] != Thing::_none) {
+ if ((objectAllowedSlots & _slotMasks[kDMSlotNeck]) && (championPtr->_slots[kDMSlotNeck] == Thing::_none))
+ curSlotIndex = kDMSlotNeck;
+ else
+ curSlotIndex = slotIdx++;
+ }
+ addObjectInSlot((ChampionIndex)previousPartyChampionCount, curThing, (ChampionSlot)curSlotIndex);
+ }
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+ _vm->_inventoryMan->toggleInventory((ChampionIndex)previousPartyChampionCount);
+ _vm->_menuMan->drawDisabledMenu();;
+}
+
+void ChampionMan::drawChampionBarGraphs(ChampionIndex champIndex) {
+ int16 barGraphHeights[3];
+ Champion *champ = &_champions[champIndex];
+ int16 barGraphIdx = 0;
+ if (champ->_currHealth > 0) {
+ int32 barGraphHeight = (((int32)champ->_currHealth << 10) * 25) / champ->_maxHealth;
+ barGraphHeights[barGraphIdx++] = (barGraphHeight >> 10) + ((barGraphHeight & 0x000003FF) ? 1 : 0);
+ } else
+ barGraphHeights[barGraphIdx++] = 0;
+
+ if (champ->_currStamina > 0) {
+ int32 barGraphHeight = (((int32)champ->_currStamina << 10) * 25) / champ->_maxStamina;
+ barGraphHeights[barGraphIdx++] = (barGraphHeight >> 10) + ((barGraphHeight & 0x000003FF) ? 1 : 0);
+ } else
+ barGraphHeights[barGraphIdx++] = 0;
+
+ if (champ->_currMana > 0) {
+ if (champ->_currMana > champ->_maxMana)
+ barGraphHeights[barGraphIdx] = 25;
+ else {
+ int32 barGraphHeight = (((int32)champ->_currMana << 10) * 25) / champ->_maxMana;
+ barGraphHeights[barGraphIdx] = (barGraphHeight >> 10) + ((barGraphHeight & 0x000003FF) ? 1 : 0);
+ }
+ } else {
+ barGraphHeights[barGraphIdx] = 0;
+ }
+ _vm->_eventMan->showMouse();
+
+ // Strangerke - TO CHECK: if portraits, maybe the old (assembly) code is required for older versions
+ Box box;
+ box._x1 = champIndex * k69_ChampionStatusBoxSpacing + 46;
+ box._x2 = box._x1 + 3;
+ box._y1 = 2;
+ box._y2 = 26;
+ for (int16 barGraphIndex = 0; barGraphIndex < 3; barGraphIndex++) {
+ int16 barGraphHeight = barGraphHeights[barGraphIndex];
+ if (barGraphHeight < 25) {
+ box._y1 = 2;
+ box._y2 = 27 - barGraphHeight;
+ _vm->_displayMan->fillScreenBox(box, k12_ColorDarkestGray);
+ }
+ if (barGraphHeight) {
+ box._y1 = 27 - barGraphHeight;
+ box._y2 = 26;
+ _vm->_displayMan->fillScreenBox(box, _championColor[champIndex]);
+ }
+ box._x1 += 7;
+ box._x2 += 7;
+ }
+ _vm->_eventMan->hideMouse();
+}
+
+
+uint16 ChampionMan::getStaminaAdjustedValue(Champion *champ, int16 val) {
+ int16 currStamina = champ->_currStamina;
+ int16 halfMaxStamina = champ->_maxStamina / 2;
+ if (currStamina < halfMaxStamina) {
+ val /= 2;
+ return val + ((uint32)val * (uint32)currStamina) / halfMaxStamina;
+ }
+ return val;
+}
+
+uint16 ChampionMan::getMaximumLoad(Champion *champ) {
+ uint16 maximumLoad = champ->getStatistic(kDMStatStrength, kDMStatCurrent) * 8 + 100;
+ maximumLoad = getStaminaAdjustedValue(champ, maximumLoad);
+ int16 wounds = champ->getWounds();
+ if (wounds)
+ maximumLoad -= maximumLoad >> (champ->getWoundsFlag(kDMWoundLegs) ? 2 : 3);
+
+ if (_vm->_objectMan->getIconIndex(champ->getSlot(kDMSlotFeet)) == kDMIconIndiceArmourElvenBoots)
+ maximumLoad += maximumLoad * 16;
+
+ maximumLoad += 9;
+ maximumLoad -= maximumLoad % 10;
+ return maximumLoad;
+}
+
+void ChampionMan::drawChampionState(ChampionIndex champIndex) {
+ static Box boxMouth = Box(55, 72, 12, 29); // @ G0048_s_Graphic562_Box_Mouth
+ static Box boxEye = Box(11, 28, 12, 29); // @ G0049_s_Graphic562_Box_Eye
+
+ int16 championStatusBoxX = champIndex * k69_ChampionStatusBoxSpacing;
+ Champion *curChampion = &_champions[champIndex];
+ uint16 championAttributes = curChampion->_attributes;
+ if (!getFlag(championAttributes, kDMAttributeNameTitle | kDMAttributeStatistics | kDMAttributeLoad | kDMAttributeIcon | kDMAttributePanel | kDMAttributeStatusBox | kDMAttributeWounds | kDMAttributeViewport | kDMAttributeActionHand))
+ return;
+
+ bool isInventoryChampion = (_vm->indexToOrdinal(champIndex) == _vm->_inventoryMan->_inventoryChampionOrdinal);
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ _vm->_eventMan->showMouse();
+ if (getFlag(championAttributes, kDMAttributeStatusBox)) {
+ Box box;
+ box._y1 = 0;
+ box._y2 = 28;
+ box._x1 = championStatusBoxX;
+ box._x2 = box._x1 + 66;
+ if (curChampion->_currHealth) {
+ _vm->_displayMan->fillScreenBox(box, k12_ColorDarkestGray);
+ int16 nativeBitmapIndices[3];
+ for (uint16 i = 0; i < 3; ++i)
+ nativeBitmapIndices[i] = 0;
+
+ uint16 borderCount = 0;
+ if (_party._fireShieldDefense > 0)
+ nativeBitmapIndices[borderCount++] = k38_BorderPartyFireshieldIndice;
+
+ if (_party._spellShieldDefense > 0)
+ nativeBitmapIndices[borderCount++] = k39_BorderPartySpellshieldIndice;
+
+ if ((_party._shieldDefense > 0) || curChampion->_shieldDefense)
+ nativeBitmapIndices[borderCount++] = k37_BorderPartyShieldIndice;
+
+ while (borderCount--)
+ _vm->_displayMan->blitToScreen(_vm->_displayMan->getNativeBitmapOrGraphic(nativeBitmapIndices[borderCount]), &box, k40_byteWidth, k10_ColorFlesh, 29);
+
+ if (isInventoryChampion) {
+ _vm->_inventoryMan->drawStatusBoxPortrait(champIndex);
+ setFlag(championAttributes, kDMAttributeStatistics);
+ } else
+ setFlag(championAttributes, kDMAttributeNameTitle | kDMAttributeStatistics | kDMAttributeWounds | kDMAttributeActionHand);
+ } else {
+ _vm->_displayMan->blitToScreen(_vm->_displayMan->getNativeBitmapOrGraphic(k8_StatusBoxDeadChampion), &box, k40_byteWidth, kM1_ColorNoTransparency, 29);
+ _vm->_textMan->printToLogicalScreen(championStatusBoxX + 1, 5, k13_ColorLightestGray, k1_ColorDarkGary, curChampion->_name);
+ _vm->_menuMan->drawActionIcon(champIndex);
+
+ clearFlag(curChampion->_attributes, kDMAttributeNameTitle | kDMAttributeStatistics | kDMAttributeLoad | kDMAttributeIcon | kDMAttributePanel | kDMAttributeStatusBox | kDMAttributeWounds | kDMAttributeViewport | kDMAttributeActionHand);
+ _vm->_eventMan->hideMouse();
+ return;
+ }
+ }
+ if (!(curChampion->_currHealth)) {
+ clearFlag(curChampion->_attributes, kDMAttributeNameTitle | kDMAttributeStatistics | kDMAttributeLoad | kDMAttributeIcon | kDMAttributePanel | kDMAttributeStatusBox | kDMAttributeWounds | kDMAttributeViewport | kDMAttributeActionHand);
+ _vm->_eventMan->hideMouse();
+ return;
+ }
+
+ if (getFlag(championAttributes, kDMAttributeNameTitle)) {
+ Color nameColor = (champIndex == _leaderIndex) ? k9_ColorGold : k13_ColorLightestGray;
+ if (isInventoryChampion) {
+ char *championName = curChampion->_name;
+ _vm->_textMan->printToViewport(3, 7, nameColor, championName);
+ int16 championTitleX = 6 * strlen(championName) + 3;
+ char titleFirstCharacter = curChampion->_title[0];
+ if ((titleFirstCharacter != ',') && (titleFirstCharacter != ';') && (titleFirstCharacter != '-'))
+ championTitleX += 6;
+
+ _vm->_textMan->printToViewport(championTitleX, 7, nameColor, curChampion->_title);
+ setFlag(championAttributes, kDMAttributeViewport);
+ } else {
+ Box box;
+ box._y1 = 0;
+ box._y2 = 6;
+ box._x1 = championStatusBoxX;
+ box._x2 = box._x1 + 42;
+ _vm->_displayMan->fillScreenBox(box, k1_ColorDarkGary);
+ _vm->_textMan->printToLogicalScreen(championStatusBoxX + 1, 5, nameColor, k1_ColorDarkGary, curChampion->_name);
+ }
+ }
+ if (getFlag(championAttributes, kDMAttributeStatistics)) {
+ drawChampionBarGraphs(champIndex);
+ if (isInventoryChampion) {
+ drawHealthStaminaManaValues(curChampion);
+ int16 nativeBitmapIndex;
+ if ((curChampion->_food < 0) || (curChampion->_water < 0) || (curChampion->_poisonEventCount))
+ nativeBitmapIndex = k34_SlotBoxWoundedIndice;
+ else
+ nativeBitmapIndex = k33_SlotBoxNormalIndice;
+
+ _vm->_displayMan->blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(nativeBitmapIndex), boxMouth, k16_byteWidth, k12_ColorDarkestGray, 18);
+ nativeBitmapIndex = k33_SlotBoxNormalIndice;
+ for (int i = kDMStatStrength; i <= kDMStatAntifire; i++) {
+ if ((curChampion->_statistics[i][kDMStatCurrent] < curChampion->_statistics[i][kDMStatMaximum])) {
+ nativeBitmapIndex = k34_SlotBoxWoundedIndice;
+ break;
+ }
+ }
+ _vm->_displayMan->blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(nativeBitmapIndex), boxEye, k16_byteWidth, k12_ColorDarkestGray, 18);
+ setFlag(championAttributes, kDMAttributeViewport);
+ }
+ }
+ if (getFlag(championAttributes, kDMAttributeWounds)) {
+ for (int i = isInventoryChampion ? kDMSlotFeet : kDMSlotActionHand; i >= kDMSlotReadyHand; i--)
+ drawSlot(champIndex, i);
+
+ if (isInventoryChampion)
+ setFlag(championAttributes, kDMAttributeViewport);
+ }
+ if (getFlag(championAttributes, kDMAttributeLoad) && isInventoryChampion) {
+ uint16 maxLoad = getMaximumLoad(curChampion);
+ Color loadColor;
+ if (curChampion->_load > maxLoad)
+ loadColor = k8_ColorRed;
+ else if (((long)curChampion->_load << 3) > ((long)maxLoad * 5))
+ loadColor = k11_ColorYellow;
+ else
+ loadColor = k13_ColorLightestGray;
+
+ switch (_vm->getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY: _vm->_textMan->printToViewport(104, 132, loadColor, "LOAD "); break;
+ case Common::DE_DEU: _vm->_textMan->printToViewport(104, 132, loadColor, "LAST "); break;
+ case Common::FR_FRA: _vm->_textMan->printToViewport(104, 132, loadColor, "CHARGE "); break;
+ }
+
+ maxLoad = curChampion->_load / 10;
+ strcpy(_vm->_stringBuildBuffer, getStringFromInteger(maxLoad, true, 3).c_str());
+
+ switch (_vm->getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY: strcat(_vm->_stringBuildBuffer, "."); break;
+ case Common::DE_DEU: strcat(_vm->_stringBuildBuffer, ","); break;
+ case Common::FR_FRA: strcat(_vm->_stringBuildBuffer, "KG,"); break;
+ }
+
+ maxLoad = curChampion->_load - (maxLoad * 10);
+ strcat(_vm->_stringBuildBuffer, getStringFromInteger(maxLoad, false, 1).c_str());
+ strcat(_vm->_stringBuildBuffer, "/");
+ maxLoad = (getMaximumLoad(curChampion) + 5) / 10;
+ strcat(_vm->_stringBuildBuffer, getStringFromInteger(maxLoad, true, 3).c_str());
+ strcat(_vm->_stringBuildBuffer, " KG");
+ _vm->_textMan->printToViewport(148, 132, loadColor, _vm->_stringBuildBuffer);
+ setFlag(championAttributes, kDMAttributeViewport);
+ }
+ uint16 championIconIndex = getChampionIconIndex(curChampion->_cell, _vm->_dungeonMan->_partyDir);
+ if (getFlag(championAttributes, kDMAttributeIcon) && (_vm->_eventMan->_useChampionIconOrdinalAsMousePointerBitmap != _vm->indexToOrdinal(championIconIndex))) {
+ _vm->_displayMan->fillScreenBox(_boxChampionIcons[championIconIndex], _championColor[champIndex]);
+ _vm->_displayMan->blitToBitmap(_vm->_displayMan->getNativeBitmapOrGraphic(k28_ChampionIcons), _vm->_displayMan->_bitmapScreen, _boxChampionIcons[championIconIndex], getChampionIconIndex(curChampion->_dir, _vm->_dungeonMan->_partyDir) * 19, 0, k40_byteWidth, k160_byteWidthScreen, k12_ColorDarkestGray, 14, k200_heightScreen);
+ }
+ if (getFlag(championAttributes, kDMAttributePanel) && isInventoryChampion) {
+ if (_vm->_pressingMouth)
+ _vm->_inventoryMan->drawPanelFoodWaterPoisoned();
+ else if (_vm->_pressingEye) {
+ if (_leaderEmptyHanded)
+ _vm->_inventoryMan->drawChampionSkillsAndStatistics();
+ } else
+ _vm->_inventoryMan->drawPanel();
+
+ setFlag(championAttributes, kDMAttributeViewport);
+ }
+ if (getFlag(championAttributes, kDMAttributeActionHand)) {
+ drawSlot(champIndex, kDMSlotActionHand);
+ _vm->_menuMan->drawActionIcon(champIndex);
+ if (isInventoryChampion)
+ setFlag(championAttributes, kDMAttributeViewport);
+ }
+ if (getFlag(championAttributes, kDMAttributeViewport))
+ _vm->_displayMan->drawViewport(k0_viewportNotDungeonView);
+
+ clearFlag(curChampion->_attributes, kDMAttributeNameTitle | kDMAttributeStatistics | kDMAttributeLoad | kDMAttributeIcon | kDMAttributePanel | kDMAttributeStatusBox | kDMAttributeWounds | kDMAttributeViewport | kDMAttributeActionHand);
+ _vm->_eventMan->hideMouse();
+}
+
+uint16 ChampionMan::getChampionIconIndex(int16 val, Direction dir) {
+ return ((val + 4 - dir) & 0x3);
+}
+
+void ChampionMan::drawHealthStaminaManaValues(Champion *champ) {
+ drawHealthOrStaminaOrManaValue(116, champ->_currHealth, champ->_maxHealth);
+ drawHealthOrStaminaOrManaValue(124, champ->_currStamina, champ->_maxStamina);
+ drawHealthOrStaminaOrManaValue(132, champ->_currMana, champ->_maxMana);
+}
+
+void ChampionMan::drawSlot(uint16 champIndex, int16 slotIndex) {
+ int16 nativeBitmapIndex = -1;
+ Champion *champ = &_champions[champIndex];
+ bool isInventoryChamp = (_vm->_inventoryMan->_inventoryChampionOrdinal == _vm->indexToOrdinal(champIndex));
+
+ uint16 slotBoxIndex;
+ if (!isInventoryChamp) {
+ // If drawing a slot for a champion other than the champion whose inventory is open
+ if ((slotIndex > kDMSlotActionHand) || (_candidateChampionOrdinal == _vm->indexToOrdinal(champIndex)))
+ return;
+ slotBoxIndex = (champIndex << 1) + slotIndex;
+ } else
+ slotBoxIndex = k8_SlotBoxInventoryFirstSlot + slotIndex;
+
+ Thing thing;
+ if (slotIndex >= kDMSlotChest1)
+ thing = _vm->_inventoryMan->_chestSlots[slotIndex - kDMSlotChest1];
+ else
+ thing = champ->getSlot((ChampionSlot)slotIndex);
+
+ SlotBox *slotBox = &_vm->_objectMan->_slotBoxes[slotBoxIndex];
+ Box box;
+ box._x1 = slotBox->_x - 1;
+ box._y1 = slotBox->_y - 1;
+ box._x2 = box._x1 + 17;
+ box._y2 = box._y1 + 17;
+
+ if (!isInventoryChamp)
+ _vm->_eventMan->hideMouse();
+
+ int16 iconIndex;
+ if (thing == Thing::_none) {
+ if (slotIndex <= kDMSlotFeet) {
+ iconIndex = kDMIconIndiceReadyHand + (slotIndex << 1);
+ if (champ->getWoundsFlag((ChampionWound)(1 << slotIndex))) {
+ iconIndex++;
+ nativeBitmapIndex = k34_SlotBoxWoundedIndice;
+ } else
+ nativeBitmapIndex = k33_SlotBoxNormalIndice;
+ } else {
+ if ((slotIndex >= kDMSlotNeck) && (slotIndex <= kDMSlotBackpackLine1_1))
+ iconIndex = kDMIconIndiceNeck + (slotIndex - kDMSlotNeck);
+ else
+ iconIndex = kDMIconIndiceEmptyBox;
+ }
+ } else {
+ iconIndex = _vm->_objectMan->getIconIndex(thing); // BUG0_35
+ if (isInventoryChamp && (slotIndex == kDMSlotActionHand) && ((iconIndex == kDMIconIndiceContainerChestClosed) || (iconIndex == kDMIconIndiceScrollOpen))) {
+ iconIndex++;
+ } // BUG2_00
+ if (slotIndex <= kDMSlotFeet) {
+ if (champ->getWoundsFlag((ChampionWound)(1 << slotIndex)))
+ nativeBitmapIndex = k34_SlotBoxWoundedIndice;
+ else
+ nativeBitmapIndex = k33_SlotBoxNormalIndice;
+ }
+ }
+
+ if ((slotIndex == kDMSlotActionHand) && (_vm->indexToOrdinal(champIndex) == _actingChampionOrdinal))
+ nativeBitmapIndex = k35_SlotBoxActingHandIndice;
+
+ if (nativeBitmapIndex != -1) {
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ if (isInventoryChamp) {
+ _vm->_displayMan->blitToBitmap(_vm->_displayMan->getNativeBitmapOrGraphic(nativeBitmapIndex),
+ _vm->_displayMan->_bitmapViewport, box, 0, 0, 16, k112_byteWidthViewport,
+ k12_ColorDarkestGray, _vm->_displayMan->getPixelHeight(nativeBitmapIndex), k136_heightViewport);
+ } else {
+ _vm->_displayMan->blitToBitmap(_vm->_displayMan->getNativeBitmapOrGraphic(nativeBitmapIndex),
+ _vm->_displayMan->_bitmapScreen, box, 0, 0, 16, k160_byteWidthScreen,
+ k12_ColorDarkestGray, _vm->_displayMan->getPixelHeight(nativeBitmapIndex), k136_heightViewport);
+ }
+ }
+
+ _vm->_objectMan->drawIconInSlotBox(slotBoxIndex, iconIndex);
+
+ if (!isInventoryChamp)
+ _vm->_eventMan->showMouse();
+}
+
+void ChampionMan::renameChampion(Champion *champ) {
+#define k1_RENAME_CHAMPION_NAME 1
+#define k2_RENAME_CHAMPION_TITLE 2
+ static const char underscoreCharacterString[2] = "_";
+ static char renameChampionInputCharacterString[2] = " ";
+ static const char reincarnateSpecialCharacters[6] = {',', '.', ';', ':', ' '};
+
+ Box displayBox;
+ displayBox._y1 = 3;
+ displayBox._y2 = 8;
+ displayBox._x1 = 3;
+ displayBox._x2 = displayBox._x1 + 167;
+
+ _vm->_displayMan->fillBoxBitmap(_vm->_displayMan->_bitmapViewport, displayBox, k12_ColorDarkestGray, k112_byteWidthViewport, k136_heightViewport);
+ _vm->_displayMan->blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k27_PanelRenameChampionIndice), _vm->_inventoryMan->_boxPanel, k72_byteWidth, k4_ColorCyan, 73);
+ _vm->_textMan->printToViewport(177, 58, k13_ColorLightestGray, "_______");
+ _vm->_textMan->printToViewport(105, 76, k13_ColorLightestGray, "___________________");
+ _vm->_eventMan->showMouse();
+ _vm->_displayMan->drawViewport(k0_viewportNotDungeonView);
+ _vm->_eventMan->setMousePointerToNormal(k0_pointerArrow);
+ _vm->_eventMan->hideMouse();
+ uint16 curCharacterIndex = 0;
+ champ->_name[curCharacterIndex] = '\0';
+ champ->_title[0] = '\0';
+ int16 renamedChampionStringMode = k1_RENAME_CHAMPION_NAME;
+ char *renamedChampionString = champ->_name;
+ int16 textPosX = 177;
+ int16 textPosY = 91;
+
+ for (;;) { /*_Infinite loop_*/
+ bool championTitleIsFull = ((renamedChampionStringMode == k2_RENAME_CHAMPION_TITLE) && (curCharacterIndex == 19));
+ if (!championTitleIsFull) {
+ _vm->_eventMan->showMouse();
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapScreen, k160_byteWidthScreen, textPosX, textPosY, k9_ColorGold, k12_ColorDarkestGray, underscoreCharacterString, k200_heightScreen);
+ _vm->_eventMan->hideMouse();
+ }
+
+ int16 curCharacter = 256;
+ while (curCharacter == 256) {
+ Common::Event event;
+ Common::EventType eventType = _vm->_eventMan->processInput(&event, &event);
+ _vm->_displayMan->updateScreen();
+ if (_vm->_engineShouldQuit)
+ return;
+ _vm->_displayMan->updateScreen();
+ //_vm->f22_delay(1);
+
+ if (eventType == Common::EVENT_LBUTTONDOWN) {
+ // If left mouse button status has changed
+
+ Common::Point mousePos = _vm->_eventMan->getMousePos();
+ if ((renamedChampionStringMode == k2_RENAME_CHAMPION_TITLE || (curCharacterIndex > 0)) && (mousePos.x >= 197) && (mousePos.x <= 215) && (mousePos.y >= 147) && (mousePos.y <= 155)) { /* Coordinates of 'OK' button */
+ int16 characterIndexBackup = curCharacterIndex;
+ char L0821_ac_ChampionNameBackupString[8];
+ renamedChampionString = champ->_name;
+ strcpy(L0821_ac_ChampionNameBackupString, renamedChampionString);
+ curCharacterIndex = strlen(renamedChampionString);
+ // Replace space characters on the right of the champion name by '\0' characters
+ while (renamedChampionString[--curCharacterIndex] == ' ')
+ renamedChampionString[curCharacterIndex] = '\0';
+
+ bool found = false;
+ for (uint16 idx = kDMChampionFirst; idx < _partyChampionCount - 1; idx++) {
+ if (!strcmp(_champions[idx]._name, renamedChampionString)) {
+ // If an existing champion already has the specified name for the new champion
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return;
+
+ if (renamedChampionStringMode == k2_RENAME_CHAMPION_TITLE)
+ renamedChampionString = champ->_title;
+
+ strcpy(renamedChampionString = champ->_name, L0821_ac_ChampionNameBackupString);
+ curCharacterIndex = characterIndexBackup;
+ } else {
+ if ((mousePos.x >= 107) && (mousePos.x <= 175) && (mousePos.y >= 147) && (mousePos.y <= 155)) { /* Coordinates of 'BACKSPACE' button */
+ curCharacter = '\b';
+ break;
+ }
+#if 0
+ if ((mousePos.x < 107) || (mousePos.x > 215) || (mousePos.y < 116) || (mousePos.y > 144)) {/* Coordinates of table of all other characters */
+ //goto T0281023;
+ }
+ if (!((mousePos.x + 4) % 10) || (!((mousePos.y + 5) % 10) && ((mousePos.x < 207) || (mousePos.y != 135)))) {
+ //goto T0281023;
+ }
+#endif
+ curCharacter = 'A' + (11 * ((mousePos.y - 116) / 10)) + ((mousePos.x - 107) / 10);
+ if ((curCharacter == 86) || (curCharacter == 97)) {
+ // The 'Return' button occupies two cells in the table
+ curCharacter = '\r'; /* Carriage return */
+ break;
+ }
+
+ if (curCharacter >= 87)
+ // Compensate for the first cell occupied by 'Return' button
+ curCharacter--;
+
+ if (curCharacter > 'Z')
+ curCharacter = reincarnateSpecialCharacters[(curCharacter - 'Z') - 1];
+
+ break;
+ }
+ } else if (eventType == Common::EVENT_KEYDOWN)
+ curCharacter = event.kbd.ascii;
+ }
+
+ if ((curCharacter >= 'a') && (curCharacter <= 'z'))
+ curCharacter -= 32; // Convert to uppercase
+
+ if (((curCharacter >= 'A') && (curCharacter <= 'Z')) || (curCharacter == '.') || (curCharacter == ',') || (curCharacter == ';') || (curCharacter == ':') || (curCharacter == ' ')) {
+ if ((curCharacter != ' ') || curCharacterIndex != 0) {
+ if (!championTitleIsFull) {
+ renameChampionInputCharacterString[0] = curCharacter;
+ _vm->_eventMan->showMouse();
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapScreen, k160_byteWidthScreen, textPosX, textPosY, k13_ColorLightestGray, k12_ColorDarkestGray, renameChampionInputCharacterString, k200_heightScreen);
+ _vm->_eventMan->hideMouse();
+ renamedChampionString[curCharacterIndex++] = curCharacter;
+ renamedChampionString[curCharacterIndex] = '\0';
+ textPosX += 6;
+ if ((renamedChampionStringMode == k1_RENAME_CHAMPION_NAME) && (curCharacterIndex == 7)) {
+ renamedChampionStringMode = k2_RENAME_CHAMPION_TITLE;
+ renamedChampionString = champ->_title;
+ textPosX = 105;
+ textPosY = 109;
+ curCharacterIndex = 0;
+ }
+ }
+ }
+ } else if (curCharacter == '\r') { // Carriage return
+ if ((renamedChampionStringMode == k1_RENAME_CHAMPION_NAME) && (curCharacterIndex > 0)) {
+ _vm->_eventMan->showMouse();
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapScreen, k160_byteWidthScreen, textPosX, textPosY, k13_ColorLightestGray, k12_ColorDarkestGray, underscoreCharacterString, k200_heightScreen);
+ _vm->_eventMan->hideMouse();
+ renamedChampionStringMode = k2_RENAME_CHAMPION_TITLE;
+ renamedChampionString = champ->_title;
+ textPosX = 105;
+ textPosY = 109;
+ curCharacterIndex = 0;
+ }
+ } else if (curCharacter == '\b') { // Backspace
+ if ((renamedChampionStringMode == k1_RENAME_CHAMPION_NAME) && (curCharacterIndex == 0))
+ continue;
+
+ if (!championTitleIsFull) {
+ _vm->_eventMan->showMouse();
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapScreen, k160_byteWidthScreen, textPosX, textPosY, k13_ColorLightestGray, k12_ColorDarkestGray, underscoreCharacterString, k200_heightScreen);
+ _vm->_eventMan->hideMouse();
+ }
+ if (curCharacterIndex == 0) {
+ renamedChampionString = champ->_name;
+ curCharacterIndex = strlen(renamedChampionString) - 1;
+ renamedChampionStringMode = k1_RENAME_CHAMPION_NAME;
+ textPosX = 177 + (curCharacterIndex * 6);
+ textPosY = 91;
+ } else {
+ curCharacterIndex--;
+ textPosX -= 6;
+ }
+ renamedChampionString[curCharacterIndex] = '\0';
+ }
+ }
+}
+
+uint16 ChampionMan::getSkillLevel(int16 champIndex, uint16 skillIndex) {
+ if (_partyIsSleeping)
+ return 1;
+
+ bool ignoreTmpExp = getFlag(skillIndex, kDMIgnoreTemporaryExperience);
+ bool ignoreObjModifiers = getFlag(skillIndex, kDMIgnoreObjectModifiers);
+ clearFlag(skillIndex, kDMIgnoreTemporaryExperience | kDMIgnoreObjectModifiers);
+ Champion *champ = &_champions[champIndex];
+ Skill *skill = &champ->_skills[skillIndex];
+ int32 exp = skill->_experience;
+ if (!ignoreTmpExp)
+ exp += skill->_temporaryExperience;
+
+ if (skillIndex > kDMSkillWizard) {
+ // Hidden skill
+ skill = &champ->_skills[(skillIndex - kDMSkillSwing) >> 2];
+ exp += skill->_experience; // Add experience in the base skill
+ if (!ignoreTmpExp)
+ exp += skill->_temporaryExperience;
+
+ exp >>= 1; // Halve experience to get average of base skill + hidden skill experience
+ }
+ int16 skillLevel = 1;
+ while (exp >= 500) {
+ exp >>= 1;
+ skillLevel++;
+ }
+ if (!ignoreObjModifiers) {
+ int16 actionHandIconIndex = _vm->_objectMan->getIconIndex(champ->_slots[kDMSlotActionHand]);
+ if (actionHandIconIndex == kDMIconIndiceWeaponTheFirestaff)
+ skillLevel++;
+ else if (actionHandIconIndex == kDMIconIndiceWeaponTheFirestaffComplete)
+ skillLevel += 2;
+
+ int16 neckIconIndex = _vm->_objectMan->getIconIndex(champ->_slots[kDMSlotNeck]);
+ switch (skillIndex) {
+ case kDMSkillWizard:
+ if (neckIconIndex == kDMIconIndiceJunkPendantFeral)
+ skillLevel += 1;
+ break;
+ case kDMSkillHeal:
+ // The skill modifiers of these two objects are not cumulative
+ if ((neckIconIndex == kDMIconIndiceJunkGemOfAges) || (actionHandIconIndex == kDMIconIndiceWeaponSceptreOfLyf))
+ skillLevel += 1;
+ break;
+ case kDMSkillInfluence:
+ if (neckIconIndex == kDMIconIndiceJunkMoonstone)
+ skillLevel += 1;
+ break;
+ case kDMSkillDefend:
+ if (neckIconIndex == kDMIconIndiceJunkEkkhardCross)
+ skillLevel += 1;
+ break;
+ default:
+ break;
+ }
+ }
+ return skillLevel;
+}
+
+}
diff --git a/engines/dm/champion.h b/engines/dm/champion.h
new file mode 100644
index 0000000000..2eb4d28299
--- /dev/null
+++ b/engines/dm/champion.h
@@ -0,0 +1,576 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_CHAMPION_H
+#define DM_CHAMPION_H
+
+#include "common/str.h"
+
+#include "dm/dm.h"
+#include "dm/gfx.h"
+
+namespace DM {
+
+#define kDMIgnoreObjectModifiers 0x4000 // @ MASK0x4000_IGNORE_OBJECT_MODIFIERS
+#define kDMIgnoreTemporaryExperience 0x8000 // @ MASK0x8000_IGNORE_TEMPORARY_EXPERIENCE
+
+class Scent {
+ uint16 _scent;
+public:
+ explicit Scent(uint16 scent = 0): _scent(scent) {}
+
+ uint16 getMapX() { return _scent & 0x1F; }
+ uint16 getMapY() { return (_scent >> 5) & 0x1F; }
+ uint16 getMapIndex() { return (_scent >> 10) & 0x3F; }
+
+ void setMapX(uint16 val) { _scent = (_scent & ~0x1F) & (val & 0x1F); }
+ void setMapY(uint16 val) { _scent = (_scent & ~(0x1F << 5)) & (val & 0x1F); }
+ void setMapIndex(uint16 val) { _scent = (_scent & ~(0x1F << 10)) & (val & 0x3F); }
+ void setVal(uint16 val) { _scent = val; }
+
+ uint16 toUint16() { return _scent; }
+}; // @ SCENT
+
+class Party {
+public:
+ Party() {
+ resetToZero();
+ }
+ int16 _magicalLightAmount;
+ byte _event73Count_ThievesEye;
+ byte _event79Count_Footprints;
+ int16 _shieldDefense;
+
+ int16 _fireShieldDefense;
+ int16 _spellShieldDefense;
+ byte _scentCount;
+ byte _freezeLifeTicks;
+ byte _firstScentIndex;
+
+ byte _lastScentIndex;
+ Scent _scents[24]; // if I remember correctly, user defined default constructors are always called
+ byte _scentStrengths[24];
+ byte _event71Count_Invisibility;
+ void resetToZero() {
+ _magicalLightAmount = 0;
+ _event73Count_ThievesEye = 0;
+ _event79Count_Footprints = 0;
+ _shieldDefense = 0;
+
+ _fireShieldDefense = 0;
+ _spellShieldDefense = 0;
+ _scentCount = 0;
+ _freezeLifeTicks = 0;
+ _firstScentIndex = 0;
+
+ _lastScentIndex = 0;
+ for (int16 i = 0; i < 24; ++i) {
+ _scents[i].setVal(0);
+ _scentStrengths[i] = 0;
+ }
+ _event71Count_Invisibility = 0;
+ }
+}; // @ PARTY
+
+enum IconIndice {
+ kDMIconIndiceNone = -1, // @ CM1_ICON_NONE
+ kDMIconIndiceJunkCompassNorth = 0, // @ C000_ICON_JUNK_COMPASS_NORTH
+ kDMIconIndiceJunkCompassWest = 3, // @ C003_ICON_JUNK_COMPASS_WEST
+ kDMIconIndiceWeaponTorchUnlit = 4, // @ C004_ICON_WEAPON_TORCH_UNLIT
+ kDMIconIndiceWeaponTorchLit = 7, // @ C007_ICON_WEAPON_TORCH_LIT
+ kDMIconIndiceJunkWater = 8, // @ C008_ICON_JUNK_WATER
+ kDMIconIndiceJunkWaterSkin = 9, // @ C009_ICON_JUNK_WATERSKIN
+ kDMIconIndiceJunkJewelSymalUnequipped = 10, // @ C010_ICON_JUNK_JEWEL_SYMAL_UNEQUIPPED
+ kDMIconIndiceJunkJewelSymalEquipped = 11, // @ C011_ICON_JUNK_JEWEL_SYMAL_EQUIPPED
+ kDMIconIndiceJunkIllumuletUnequipped = 12, // @ C012_ICON_JUNK_ILLUMULET_UNEQUIPPED
+ kDMIconIndiceJunkIllumuletEquipped = 13, // @ C013_ICON_JUNK_ILLUMULET_EQUIPPED
+ kDMIconIndiceWeaponFlamittEmpty = 14, // @ C014_ICON_WEAPON_FLAMITT_EMPTY
+ kDMIconIndiceWeaponEyeOfTimeEmpty = 16, // @ C016_ICON_WEAPON_EYE_OF_TIME_EMPTY
+ kDMIconIndiceWeaponStormringEmpty = 18, // @ C018_ICON_WEAPON_STORMRING_EMPTY
+ kDMIconIndiceWeaponStaffOfClawsEmpty = 20, // @ C020_ICON_WEAPON_STAFF_OF_CLAWS_EMPTY
+ kDMIconIndiceWeaponStaffOfClawsFull = 22, // @ C022_ICON_WEAPON_STAFF_OF_CLAWS_FULL
+ kDMIconIndiceWeaponBoltBladeStormEmpty = 23, // @ C023_ICON_WEAPON_BOLT_BLADE_STORM_EMPTY
+ kDMIconIndiceWeaponFuryRaBladeEmpty = 25, // @ C025_ICON_WEAPON_FURY_RA_BLADE_EMPTY
+ kDMIconIndiceWeaponTheFirestaff = 27, // @ C027_ICON_WEAPON_THE_FIRESTAFF
+ kDMIconIndiceWeaponTheFirestaffComplete = 28, // @ C028_ICON_WEAPON_THE_FIRESTAFF_COMPLETE
+ kDMIconIndiceScrollOpen = 30, // @ C030_ICON_SCROLL_SCROLL_OPEN
+ kDMIconIndiceScrollClosed = 31, // @ C031_ICON_SCROLL_SCROLL_CLOSED
+ kDMIconIndiceWeaponDagger = 32, // @ C032_ICON_WEAPON_DAGGER
+ kDMIconIndiceWeaponDeltaSideSplitter = 38, // @ C038_ICON_WEAPON_DELTA_SIDE_SPLITTER
+ kDMIconIndiceWeaponDiamondEdge = 39, // @ C039_ICON_WEAPON_DIAMOND_EDGE
+ kDMIconIndiceWeaponVorpalBlade = 40, // @ C040_ICON_WEAPON_VORPAL_BLADE
+ kDMIconIndiceWeaponTheInquisitorDragonFang = 41, // @ C041_ICON_WEAPON_THE_INQUISITOR_DRAGON_FANG
+ kDMIconIndiceWeaponHardcleaveExecutioner = 43, // @ C043_ICON_WEAPON_HARDCLEAVE_EXECUTIONER
+ kDMIconIndiceWeaponMaceOfOrder = 45, // @ C045_ICON_WEAPON_MACE_OF_ORDER
+ kDMIconIndiceWeaponArrow = 51, // @ C051_ICON_WEAPON_ARROW
+ kDMIconIndiceWeaponSlayer = 52, // @ C052_ICON_WEAPON_SLAYER
+ kDMIconIndiceWeaponRock = 54, // @ C054_ICON_WEAPON_ROCK
+ kDMIconIndiceWeaponPoisonDart = 55, // @ C055_ICON_WEAPON_POISON_DART
+ kDMIconIndiceWeaponThrowingStar = 56, // @ C056_ICON_WEAPON_THROWING_STAR
+ kDMIconIndiceWeaponStaff = 58, // @ C058_ICON_WEAPON_STAFF
+ kDMIconIndiceWeaponWand = 59, // @ C059_ICON_WEAPON_WAND
+ kDMIconIndiceWeaponTeowand = 60, // @ C060_ICON_WEAPON_TEOWAND
+ kDMIconIndiceWeaponYewStaff = 61, // @ C061_ICON_WEAPON_YEW_STAFF
+ kDMIconIndiceWeaponStaffOfManarStaffOfIrra = 62, // @ C062_ICON_WEAPON_STAFF_OF_MANAR_STAFF_OF_IRRA
+ kDMIconIndiceWeaponSnakeStaffCrossOfNeta = 63, // @ C063_ICON_WEAPON_SNAKE_STAFF_CROSS_OF_NETA
+ kDMIconIndiceWeaponTheConduitSerpentStaff = 64, // @ C064_ICON_WEAPON_THE_CONDUIT_SERPENT_STAFF
+ kDMIconIndiceWeaponDragonSpit = 65, // @ C065_ICON_WEAPON_DRAGON_SPIT
+ kDMIconIndiceWeaponSceptreOfLyf = 66, // @ C066_ICON_WEAPON_SCEPTRE_OF_LYF
+ kDMIconIndiceArmourCloakOfNight = 81, // @ C081_ICON_ARMOUR_CLOAK_OF_NIGHT
+ kDMIconIndiceArmourCrownOfNerra = 104, // @ C104_ICON_ARMOUR_CROWN_OF_NERRA
+ kDMIconIndiceArmourElvenBoots = 119, // @ C119_ICON_ARMOUR_ELVEN_BOOTS
+ kDMIconIndiceJunkGemOfAges = 120, // @ C120_ICON_JUNK_GEM_OF_AGES
+ kDMIconIndiceJunkEkkhardCross = 121, // @ C121_ICON_JUNK_EKKHARD_CROSS
+ kDMIconIndiceJunkMoonstone = 122, // @ C122_ICON_JUNK_MOONSTONE
+ kDMIconIndiceJunkPendantFeral = 124, // @ C124_ICON_JUNK_PENDANT_FERAL
+ kDMIconIndiceJunkBoulder = 128, // @ C128_ICON_JUNK_BOULDER
+ kDMIconIndiceJunkRabbitsFoot = 137, // @ C137_ICON_JUNK_RABBITS_FOOT
+ kDMIconIndiceArmourDexhelm = 140, // @ C140_ICON_ARMOUR_DEXHELM
+ kDMIconIndiceArmourFlamebain = 141, // @ C141_ICON_ARMOUR_FLAMEBAIN
+ kDMIconIndiceArmourPowertowers = 142, // @ C142_ICON_ARMOUR_POWERTOWERS
+ kDMIconIndiceContainerChestClosed = 144, // @ C144_ICON_CONTAINER_CHEST_CLOSED
+ kDMIconIndiceContainerChestOpen = 145, // @ C145_ICON_CONTAINER_CHEST_OPEN
+ kDMIconIndiceJunkChampionBones = 147, // @ C147_ICON_JUNK_CHAMPION_BONES
+ kDMIconIndicePotionMaPotionMonPotion = 148, // @ C148_ICON_POTION_MA_POTION_MON_POTION
+ kDMIconIndicePotionWaterFlask = 163, // @ C163_ICON_POTION_WATER_FLASK
+ kDMIconIndiceJunkApple = 168, // @ C168_ICON_JUNK_APPLE
+ kDMIconIndiceJunkIronKey = 176, // @ C176_ICON_JUNK_IRON_KEY
+ kDMIconIndiceJunkMasterKey = 191, // @ C191_ICON_JUNK_MASTER_KEY
+ kDMIconIndiceArmourBootOfSpeed = 194, // @ C194_ICON_ARMOUR_BOOT_OF_SPEED
+ kDMIconIndicePotionEmptyFlask = 195, // @ C195_ICON_POTION_EMPTY_FLASK
+ kDMIconIndiceJunkZokathra = 197, // @ C197_ICON_JUNK_ZOKATHRA
+ kDMIconIndiceActionEmptyHand = 201, // @ C201_ICON_ACTION_ICON_EMPTY_HAND
+ kDMIconIndiceEyeNotLooking = 202, // @ C202_ICON_EYE_NOT_LOOKING /* One pixel is different in this bitmap from the eye in C017_GRAPHIC_INVENTORY. This is visible by selecting another champion after clicking the eye */
+ kDMIconIndiceEyeLooking = 203, // @ C203_ICON_EYE_LOOKING
+ kDMIconIndiceEmptyBox = 204, // @ C204_ICON_EMPTY_BOX
+ kDMIconIndiceMouthOpen = 205, // @ C205_ICON_MOUTH_OPEN
+ kDMIconIndiceNeck = 208, // @ C208_ICON_NECK
+ kDMIconIndiceReadyHand = 212 // @ C212_ICON_READY_HAND
+};
+
+enum ChampionIndex {
+ kDMChampionNone = -1, // @ CM1_CHAMPION_NONE
+ kDMChampionFirst = 0, // @ C00_CHAMPION_FIRST
+ kDMChampionSecond = 1,
+ kDMChampionThird = 2,
+ kDMChampionFourth = 3,
+ kDMChampionCloseInventory = 4, // @ C04_CHAMPION_CLOSE_INVENTORY
+ kDMChampionSpecialInventory = 5 // @ C05_CHAMPION_SPECIAL_INVENTORY
+};
+
+enum ChampionAttribute {
+ kDMAttributNone = 0x0000, // @ MASK0x0000_NONE
+ kDMAttributeDisableAction = 0x0008, // @ MASK0x0008_DISABLE_ACTION
+ kDMAttributeMale = 0x0010, // @ MASK0x0010_MALE
+ kDMAttributeNameTitle = 0x0080, // @ MASK0x0080_NAME_TITLE
+ kDMAttributeStatistics = 0x0100, // @ MASK0x0100_STATISTICS
+ kDMAttributeLoad = 0x0200, // @ MASK0x0200_LOAD
+ kDMAttributeIcon = 0x0400, // @ MASK0x0400_ICON
+ kDMAttributePanel = 0x0800, // @ MASK0x0800_PANEL
+ kDMAttributeStatusBox = 0x1000, // @ MASK0x1000_STATUS_BOX
+ kDMAttributeWounds = 0x2000, // @ MASK0x2000_WOUNDS
+ kDMAttributeViewport = 0x4000, // @ MASK0x4000_VIEWPORT
+ kDMAttributeActionHand = 0x8000 // @ MASK0x8000_ACTION_HAND
+};
+
+enum ChampionWound {
+ kDMWoundNone = 0x0000, // @ MASK0x0000_NO_WOUND
+ kDMWoundReadHand = 0x0001, // @ MASK0x0001_READY_HAND
+ kDMWoundActionHand = 0x0002, // @ MASK0x0002_ACTION_HAND
+ kDMWoundHead = 0x0004, // @ MASK0x0004_HEAD
+ kDMWoundTorso = 0x0008, // @ MASK0x0008_TORSO
+ kDMWoundLegs = 0x0010, // @ MASK0x0010_LEGS
+ kDMWoundFeet = 0x0020 // @ MASK0x0020_FEET
+};
+
+enum ChampionStatType {
+ kDMStatLuck = 0, // @ C0_STATISTIC_LUCK
+ kDMStatStrength = 1, // @ C1_STATISTIC_STRENGTH
+ kDMStatDexterity = 2, // @ C2_STATISTIC_DEXTERITY
+ kDMStatWisdom = 3, // @ C3_STATISTIC_WISDOM
+ kDMStatVitality = 4, // @ C4_STATISTIC_VITALITY
+ kDMStatAntimagic = 5, // @ C5_STATISTIC_ANTIMAGIC
+ kDMStatAntifire = 6, // @ C6_STATISTIC_ANTIFIRE
+ kDMStatMana = 8 // @ C8_STATISTIC_MANA /* Used as a fake statistic index for objects granting a Mana bonus */
+};
+
+enum ChampionStatValue {
+ kDMStatMaximum = 0, // @ C0_MAXIMUM
+ kDMStatCurrent = 1, // @ C1_CURRENT
+ kDMStatMinimum = 2 // @ C2_MINIMUM
+};
+
+enum ChampionSkill {
+ kDMSkillFighter = 0, // @ C00_SKILL_FIGHTER
+ kDMSkillNinja = 1, // @ C01_SKILL_NINJA
+ kDMSkillPriest = 2, // @ C02_SKILL_PRIEST
+ kDMSkillWizard = 3, // @ C03_SKILL_WIZARD
+ kDMSkillSwing = 4, // @ C04_SKILL_SWING
+ kDMSkillThrust = 5, // @ C05_SKILL_THRUST
+ kDMSkillClub = 6, // @ C06_SKILL_CLUB
+ kDMSkillParry = 7, // @ C07_SKILL_PARRY
+ kDMSkillSteal = 8, // @ C08_SKILL_STEAL
+ kDMSkillFight = 9, // @ C09_SKILL_FIGHT
+ kDMSkillThrow = 10, // @ C10_SKILL_THROW
+ kDMSkillShoot = 11, // @ C11_SKILL_SHOOT
+ kDMSkillIdentify = 12, // @ C12_SKILL_IDENTIFY
+ kDMSkillHeal = 13, // @ C13_SKILL_HEAL
+ kDMSkillInfluence = 14, // @ C14_SKILL_INFLUENCE
+ kDMSkillDefend = 15, // @ C15_SKILL_DEFEND
+ kDMSkillFire = 16, // @ C16_SKILL_FIRE
+ kDMSkillAir = 17, // @ C17_SKILL_AIR
+ kDMSkillEarth = 18, // @ C18_SKILL_EARTH
+ kDMSkillWater = 19 // @ C19_SKILL_WATER
+};
+
+enum ChampionSlot {
+ kDMSlotLeaderHand = -1, // @ CM1_SLOT_LEADER_HAND
+ kDMSlotReadyHand = 0, // @ C00_SLOT_READY_HAND
+ kDMSlotActionHand = 1, // @ C01_SLOT_ACTION_HAND
+ kDMSlotHead = 2, // @ C02_SLOT_HEAD
+ kDMSlotTorso = 3, // @ C03_SLOT_TORSO
+ kDMSlotLegs = 4, // @ C04_SLOT_LEGS
+ kDMSlotFeet = 5, // @ C05_SLOT_FEET
+ kDMSlotPouch_2 = 6, // @ C06_SLOT_POUCH_2
+ kDMSlotQuiverLine2_1 = 7, // @ C07_SLOT_QUIVER_LINE2_1
+ kDMSlotQuiverLine1_2 = 8, // @ C08_SLOT_QUIVER_LINE1_2
+ kDMSlotQuiverLine2_2 = 9, // @ C09_SLOT_QUIVER_LINE2_2
+ kDMSlotNeck = 10, // @ C10_SLOT_NECK
+ kDMSlotPouch1 = 11, // @ C11_SLOT_POUCH_1
+ kDMSlotQuiverLine1_1 = 12, // @ C12_SLOT_QUIVER_LINE1_1
+ kDMSlotBackpackLine1_1 = 13, // @ C13_SLOT_BACKPACK_LINE1_1
+ kDMSlotBackpackLine2_2 = 14, // @ C14_SLOT_BACKPACK_LINE2_2
+ kDMSlotBackpackLine2_3 = 15, // @ C15_SLOT_BACKPACK_LINE2_3
+ kDMSlotBackpackLine2_4 = 16, // @ C16_SLOT_BACKPACK_LINE2_4
+ kDMSlotBackpackLine2_5 = 17, // @ C17_SLOT_BACKPACK_LINE2_5
+ kDMSlotBackpackLine2_6 = 18, // @ C18_SLOT_BACKPACK_LINE2_6
+ kDMSlotBackpackLine2_7 = 19, // @ C19_SLOT_BACKPACK_LINE2_7
+ kDMSlotBackpackLine2_8 = 20, // @ C20_SLOT_BACKPACK_LINE2_8
+ kDMSlotBackpackLine2_9 = 21, // @ C21_SLOT_BACKPACK_LINE2_9
+ kDMSlotBackpackLine1_2 = 22, // @ C22_SLOT_BACKPACK_LINE1_2
+ kDMSlotBackpackLine1_3 = 23, // @ C23_SLOT_BACKPACK_LINE1_3
+ kDMSlotBackpackLine1_4 = 24, // @ C24_SLOT_BACKPACK_LINE1_4
+ kDMSlotBackpackLine1_5 = 25, // @ C25_SLOT_BACKPACK_LINE1_5
+ kDMSlotBackpackLine1_6 = 26, // @ C26_SLOT_BACKPACK_LINE1_6
+ kDMSlotBackpackLine1_7 = 27, // @ C27_SLOT_BACKPACK_LINE1_7
+ kDMSlotBackpackLine1_8 = 28, // @ C28_SLOT_BACKPACK_LINE1_8
+ kDMSlotBackpackLine1_9 = 29, // @ C29_SLOT_BACKPACK_LINE1_9
+ kDMSlotChest1 = 30, // @ C30_SLOT_CHEST_1
+ kDMSlotChest2 = 31, // @ C31_SLOT_CHEST_2
+ kDMSlotChest3 = 32, // @ C32_SLOT_CHEST_3
+ kDMSlotChest4 = 33, // @ C33_SLOT_CHEST_4
+ kDMSlotChest5 = 34, // @ C34_SLOT_CHEST_5
+ kDMSlotChest6 = 35, // @ C35_SLOT_CHEST_6
+ kDMSlotChest7 = 36, // @ C36_SLOT_CHEST_7
+ kDMSlotChest8 = 37 // @ C37_SLOT_CHEST_8
+};
+
+enum ChampionAction {
+ kDMActionN = 0, // @ C000_ACTION_N
+ kDMActionBlock = 1, // @ C001_ACTION_BLOCK
+ kDMActionChop = 2, // @ C002_ACTION_CHOP
+ kDMActionX = 3, // @ C003_ACTION_X
+ kDMActionBlowHorn = 4, // @ C004_ACTION_BLOW_HORN
+ kDMActionFlip = 5, // @ C005_ACTION_FLIP
+ kDMActionPunch = 6, // @ C006_ACTION_PUNCH
+ kDMActionKick = 7, // @ C007_ACTION_KICK
+ kDMActionWarCry = 8, // @ C008_ACTION_WAR_CRY
+ kDMActionStab9 = 9, // @ C009_ACTION_STAB
+ kDMActionClimbDown = 10, // @ C010_ACTION_CLIMB_DOWN
+ kDMActionFreezeLife = 11, // @ C011_ACTION_FREEZE_LIFE
+ kDMActionHit = 12, // @ C012_ACTION_HIT
+ kDMActionSwing = 13, // @ C013_ACTION_SWING
+ kDMActionStab14 = 14, // @ C014_ACTION_STAB
+ kDMActionThrust = 15, // @ C015_ACTION_THRUST
+ kDMActionJab = 16, // @ C016_ACTION_JAB
+ kDMActionParry = 17, // @ C017_ACTION_PARRY
+ kDMActionHack = 18, // @ C018_ACTION_HACK
+ kDMActionBerzerk = 19, // @ C019_ACTION_BERZERK
+ kDMActionFireball = 20, // @ C020_ACTION_FIREBALL
+ kDMActionDispel = 21, // @ C021_ACTION_DISPELL
+ kDMActionConfuse = 22, // @ C022_ACTION_CONFUSE
+ kDMActionLightning = 23, // @ C023_ACTION_LIGHTNING
+ kDMActionDisrupt = 24, // @ C024_ACTION_DISRUPT
+ kDMActionMelee = 25, // @ C025_ACTION_MELEE
+ kDMActionX_C026 = 26, // @ C026_ACTION_X
+ kDMActionInvoke = 27, // @ C027_ACTION_INVOKE
+ kDMActionSlash = 28, // @ C028_ACTION_SLASH
+ kDMActionCleave = 29, // @ C029_ACTION_CLEAVE
+ kDMActionBash = 30, // @ C030_ACTION_BASH
+ kDMActionStun = 31, // @ C031_ACTION_STUN
+ kDMActionShoot = 32, // @ C032_ACTION_SHOOT
+ kDMActionSpellshield = 33, // @ C033_ACTION_SPELLSHIELD
+ kDMActionFireshield = 34, // @ C034_ACTION_FIRESHIELD
+ kDMActionFluxcage = 35, // @ C035_ACTION_FLUXCAGE
+ kDMActionHeal = 36, // @ C036_ACTION_HEAL
+ kDMActionCalm = 37, // @ C037_ACTION_CALM
+ kDMActionLight = 38, // @ C038_ACTION_LIGHT
+ kDMActionWindow = 39, // @ C039_ACTION_WINDOW
+ kDMActionSpit = 40, // @ C040_ACTION_SPIT
+ kDMActionBrandish = 41, // @ C041_ACTION_BRANDISH
+ kDMActionThrow = 42, // @ C042_ACTION_THROW
+ kDMActionFuse = 43, // @ C043_ACTION_FUSE
+ kDMActionNone = 255 // @ C255_ACTION_NONE
+};
+
+enum AttackType {
+ kDMAttackTypeNormal = 0, // @ C0_ATTACK_NORMAL
+ kDMAttackTypeFire = 1, // @ C1_ATTACK_FIRE
+ kDMAttackTypeSelf = 2, // @ C2_ATTACK_SELF
+ kDMAttackTypeBlunt = 3, // @ C3_ATTACK_BLUNT
+ kDMAttackTypeSharp = 4, // @ C4_ATTACK_SHARP
+ kDMAttackTypeMagic = 5, // @ C5_ATTACK_MAGIC
+ kDMAttackTypePsychic = 6, // @ C6_ATTACK_PSYCHIC
+ kDMAttackTypeLightning = 7 // @ C7_ATTACK_LIGHTNING
+};
+
+enum SpellCastResult {
+ kDMSpellCastFailure = 0, // @ C0_SPELL_CAST_FAILURE
+ kDMSpellCastSuccess = 1, // @ C1_SPELL_CAST_SUCCESS
+ kDMSpellCastFailureNeedsFlask = 3 // @ C3_SPELL_CAST_FAILURE_NEEDS_FLASK
+};
+
+enum SpellFailure {
+ kDMFailureNeedsMorePractice = 0, // @ C00_FAILURE_NEEDS_MORE_PRACTICE
+ kDMFailureMeaninglessSpell = 1, // @ C01_FAILURE_MEANINGLESS_SPELL
+ kDMFailureNeedsFlaskInHand = 10, // @ C10_FAILURE_NEEDS_FLASK_IN_HAND
+ kDMFailureNeedsMagicMapInHand = 11 // @ C11_FAILURE_NEEDS_MAGIC_MAP_IN_HAND
+};
+
+enum SpellKind {
+ kDMSpellKindPotion = 1, // @ C1_SPELL_KIND_POTION
+ kDMSpellKindProjectile = 2, // @ C2_SPELL_KIND_PROJECTILE
+ kDMSpellKindOther = 3, // @ C3_SPELL_KIND_OTHER
+ kDMSpellKindMagicMap = 4 // @ C4_SPELL_KIND_MAGIC_MAP
+};
+
+enum SpellType {
+ kDMSpellTypeProjectileOpenDoor = 4, // @ C4_SPELL_TYPE_PROJECTILE_OPEN_DOOR
+ kDMSpellTypeOtherLight = 0, // @ C0_SPELL_TYPE_OTHER_LIGHT
+ kDMSpellTypeOtherDarkness = 1, // @ C1_SPELL_TYPE_OTHER_DARKNESS
+ kDMSpellTypeOtherThievesEye = 2, // @ C2_SPELL_TYPE_OTHER_THIEVES_EYE
+ kDMSpellTypeOtherInvisibility = 3, // @ C3_SPELL_TYPE_OTHER_INVISIBILITY
+ kDMSpellTypeOtherPartyShield = 4, // @ C4_SPELL_TYPE_OTHER_PARTY_SHIELD
+ kDMSpellTypeOtherMagicTorch = 5, // @ C5_SPELL_TYPE_OTHER_MAGIC_TORCH
+ kDMSpellTypeOtherFootprints = 6, // @ C6_SPELL_TYPE_OTHER_FOOTPRINTS
+ kDMSpellTypeOtherZokathra = 7, // @ C7_SPELL_TYPE_OTHER_ZOKATHRA
+ kDMSpellTypeOtherFireshield = 8, // @ C8_SPELL_TYPE_OTHER_FIRESHIELD
+ kDMSpellTypeMagicMap0 = 0, // @ C0_SPELL_TYPE_MAGIC_MAP
+ kDMSpellTypeMagicMap1 = 1, // @ C1_SPELL_TYPE_MAGIC_MAP
+ kDMSpellTypeMagicMap2 = 2, // @ C2_SPELL_TYPE_MAGIC_MAP
+ kDMSpellTypeMagicMap3 = 3 // @ C3_SPELL_TYPE_MAGIC_MAP
+};
+
+#define kDMMaskNoSharpDefense 0x0000 // @ MASK0x0000_DO_NOT_USE_SHARP_DEFENSE
+#define kDMMaskSharpDefense 0x8000 // @ MASK0x8000_USE_SHARP_DEFENSE
+#define kDMMaskMergeCycles 0x8000 // @ MASK0x8000_MERGE_CYCLES
+
+class Skill {
+public:
+ int16 _temporaryExperience;
+ int32 _experience;
+
+ void resetToZero() { _temporaryExperience = _experience = 0; }
+}; // @ SKILL
+
+class Champion {
+public:
+ uint16 _attributes;
+ uint16 _wounds;
+ byte _statistics[7][3];
+ Thing _slots[30];
+ Skill _skills[20];
+ char _name[8];
+ char _title[20];
+ Direction _dir;
+ ViewCell _cell;
+ ChampionAction _actionIndex;
+ uint16 _symbolStep;
+ char _symbols[5];
+ uint16 _directionMaximumDamageReceived;
+ uint16 _maximumDamageReceived;
+ uint16 _poisonEventCount;
+ int16 _enableActionEventIndex;
+ int16 _hideDamageReceivedIndex;
+ int16 _currHealth;
+ int16 _maxHealth;
+ int16 _currStamina;
+ int16 _maxStamina;
+ int16 _currMana;
+ int16 _maxMana;
+ int16 _actionDefense;
+ int16 _food;
+ int16 _water;
+ uint16 _load;
+ int16 _shieldDefense;
+ byte _portrait[928]; // 32 x 29 pixel portrait
+
+ Thing &getSlot(ChampionSlot slot) { return _slots[slot]; }
+ void setSlot(ChampionSlot slot, Thing val) { _slots[slot] = val; }
+
+ Skill &getSkill(ChampionSkill skill) { return _skills[skill]; }
+ void setSkillExp(ChampionSkill skill, int32 val) { _skills[skill]._experience = val; }
+ void setSkillTempExp(ChampionSkill skill, int16 val) { _skills[skill]._temporaryExperience= val; }
+
+ byte& getStatistic(ChampionStatType type, ChampionStatValue valType) { return _statistics[type][valType]; }
+ void setStatistic(ChampionStatType type, ChampionStatValue valType, byte newVal) { _statistics[type][valType] = newVal; }
+
+ uint16 getAttributes() { return _attributes; }
+ uint16 getAttributes(ChampionAttribute flag) { return _attributes & flag; }
+ void setAttributeFlag(ChampionAttribute flag, bool value);
+ void clearAttributes(ChampionAttribute attribute = kDMAttributNone) { _attributes = attribute; }
+
+ uint16 getWounds() { return _wounds; }
+ void setWoundsFlag(ChampionWound flag, bool value);
+ uint16 getWoundsFlag(ChampionWound wound) { return _wounds & wound; }
+ void clearWounds() { _wounds = kDMWoundNone; }
+ void resetSkillsToZero() {
+ for (int16 i = 0; i < 20; ++i)
+ _skills[i].resetToZero();
+ }
+ void resetToZero();
+
+}; // @ CHAMPION_INCLUDING_PORTRAIT
+
+class Spell {
+public:
+ Spell() {}
+ Spell(int32 symbols, byte baseSkillReq, byte skillIndex, uint16 attributes)
+ : _symbols(symbols), _baseRequiredSkillLevel(baseSkillReq), _skillIndex(skillIndex), _attributes(attributes) {}
+
+ int32 _symbols; /* Most significant byte: 0 (spell definition does not include power symbol) / not 0 (spell definition includes power symbol) */
+ byte _baseRequiredSkillLevel;
+ byte _skillIndex;
+ uint16 _attributes; /* Bits 15-10: Duration, Bits 9-4: Type, Bits 3-0: Kind */
+
+ uint16 getKind() { return _attributes & 0xF; } // @ M67_SPELL_KIND
+ uint16 getType() { return (_attributes >> 4) & 0x3F; } // @ M68_SPELL_TYPE
+ uint16 getDuration() { return (_attributes >> 10) & 0x3F; } // @ M69_SPELL_DURATION
+}; // @ SPELL
+
+class ChampionMan {
+ DMEngine *_vm;
+
+ uint16 getChampionPortraitX(uint16 index); // @ M27_PORTRAIT_X
+ uint16 getChampionPortraitY(uint16 index); // @ M28_PORTRAIT_Y
+
+ int16 getDecodedValue(char *string, uint16 characterCount); // @ F0279_CHAMPION_GetDecodedValue
+ void drawHealthOrStaminaOrManaValue(int16 posy, int16 currVal, int16 maxVal); // @ F0289_CHAMPION_DrawHealthOrStaminaOrManaValue
+ uint16 getHandSlotIndex(uint16 slotBoxIndex);// @ M70_HAND_SLOT_INDEX
+ int16 _championPendingWounds[4]; // @ G0410_ai_ChampionPendingWounds
+ int16 _championPendingDamage[4]; // @ G0409_ai_ChampionPendingDamage
+
+ void initConstants();
+
+public:
+
+ Champion _champions[4]; // @ K0071_as_Champions
+ uint16 _partyChampionCount; // @ G0305_ui_PartyChampionCount
+ bool _partyDead; // @ G0303_B_PartyDead
+ Thing _leaderHandObject; // @ G0414_T_LeaderHandObject
+ ChampionIndex _leaderIndex; // @ G0411_i_LeaderIndex
+ uint16 _candidateChampionOrdinal; // @ G0299_ui_CandidateChampionOrdinal
+ bool _partyIsSleeping; // @ G0300_B_PartyIsSleeping
+ uint16 _actingChampionOrdinal; // @ G0506_ui_ActingChampionOrdinal
+ IconIndice _leaderHandObjectIconIndex; // @ G0413_i_LeaderHandObjectIconIndex
+ bool _leaderEmptyHanded; // @ G0415_B_LeaderEmptyHanded
+ Party _party; // @ G0407_s_Party
+ ChampionIndex _magicCasterChampionIndex; // @ G0514_i_MagicCasterChampionIndex
+ bool _mousePointerHiddenToDrawChangedObjIconOnScreen; // @ G0420_B_MousePointerHiddenToDrawChangedObjectIconOnScreen
+
+ explicit ChampionMan(DMEngine *vm);
+ ChampionIndex getIndexInCell(int16 cell); // @ F0285_CHAMPION_GetIndexInCell
+ bool isLeaderHandObjectThrown(int16 side); // @ F0329_CHAMPION_IsLeaderHandObjectThrown
+ bool isObjectThrown(uint16 champIndex, int16 slotIndex, int16 side); // @ F0328_CHAMPION_IsObjectThrown
+ void resetDataToStartGame(); // @ F0278_CHAMPION_ResetDataToStartGame
+ void addCandidateChampionToParty(uint16 championPortraitIndex); // @ F0280_CHAMPION_AddCandidateChampionToParty
+ void drawChampionBarGraphs(ChampionIndex champIndex); // @ F0287_CHAMPION_DrawBarGraphs
+ uint16 getStaminaAdjustedValue(Champion *champ, int16 val); // @ F0306_CHAMPION_GetStaminaAdjustedValue
+ uint16 getMaximumLoad(Champion *champ); // @ F0309_CHAMPION_GetMaximumLoad
+ void drawChampionState(ChampionIndex champIndex); // @ F0292_CHAMPION_DrawState
+ uint16 getChampionIconIndex(int16 val, Direction dir); // @ M26_CHAMPION_ICON_INDEX
+ void drawHealthStaminaManaValues(Champion *champ); // @ F0290_CHAMPION_DrawHealthStaminaManaValues
+ void drawSlot(uint16 champIndex, int16 slotIndex); // @ F0291_CHAMPION_DrawSlot
+ void renameChampion(Champion *champ); // @ F0281_CHAMPION_Rename
+ uint16 getSkillLevel(int16 champIndex, uint16 skillIndex);// @ F0303_CHAMPION_GetSkillLevel
+ Common::String getStringFromInteger(uint16 val, bool padding, uint16 paddingCharCount); // @ F0288_CHAMPION_GetStringFromInteger
+ void applyModifiersToStatistics(Champion *champ, int16 slotIndex, int16 iconIndex,
+ int16 modifierFactor, Thing thing); // @ F0299_CHAMPION_ApplyObjectModifiersToStatistics
+ bool hasObjectIconInSlotBoxChanged(int16 slotBoxIndex, Thing thing); // @ F0295_CHAMPION_HasObjectIconInSlotBoxChanged
+ void drawChangedObjectIcons(); // @ F0296_CHAMPION_DrawChangedObjectIcons
+ void addObjectInSlot(ChampionIndex champIndex, Thing thing, ChampionSlot slotIndex); // @ F0301_CHAMPION_AddObjectInSlot
+ int16 getScentOrdinal(int16 mapX, int16 mapY); // @ F0315_CHAMPION_GetScentOrdinal
+ Thing getObjectRemovedFromLeaderHand(); // @ F0298_CHAMPION_GetObjectRemovedFromLeaderHand
+ uint16 getStrength(int16 champIndex, int16 slotIndex); // @ F0312_CHAMPION_GetStrength
+ Thing getObjectRemovedFromSlot(uint16 champIndex, uint16 slotIndex); // @ F0300_CHAMPION_GetObjectRemovedFromSlot
+ void decrementStamina(int16 championIndex, int16 decrement); // @ F0325_CHAMPION_DecrementStamina
+ int16 addPendingDamageAndWounds_getDamage(int16 champIndex, int16 attack, int16 allowedWounds,
+ uint16 attackType); // @ F0321_CHAMPION_AddPendingDamageAndWounds_GetDamage
+ int16 getWoundDefense(int16 champIndex, uint16 woundIndex); // @ F0313_CHAMPION_GetWoundDefense
+ uint16 getStatisticAdjustedAttack(Champion *champ, uint16 statIndex, uint16 attack); // @ F0307_CHAMPION_GetStatisticAdjustedAttack
+ void wakeUp(); // @ F0314_CHAMPION_WakeUp
+ int16 getThrowingStaminaCost(Thing thing);// @ F0305_CHAMPION_GetThrowingStaminaCost
+ void disableAction(uint16 champIndex, uint16 ticks); // @ F0330_CHAMPION_DisableAction
+ void addSkillExperience(uint16 champIndex, uint16 skillIndex, uint16 exp);// @ F0304_CHAMPION_AddSkillExperience
+ int16 getDamagedChampionCount(uint16 attack, int16 wounds,
+ int16 attackType); // @ F0324_CHAMPION_DamageAll_GetDamagedChampionCount
+ int16 getTargetChampionIndex(int16 mapX, int16 mapY, uint16 cell); // @ F0286_CHAMPION_GetTargetChampionIndex
+ int16 getDexterity(Champion *champ); // @ F0311_CHAMPION_GetDexterity
+ bool isLucky(Champion *champ, uint16 percentage); // @ F0308_CHAMPION_IsLucky
+ void championPoison(int16 championIndex, uint16 attack); // @ F0322_CHAMPION_Poison
+ void setPartyDirection(int16 dir); // @ F0284_CHAMPION_SetPartyDirection
+ void deleteScent(uint16 scentIndex); // @ F0316_CHAMPION_DeleteScent
+ void addScentStrength(int16 mapX, int16 mapY, int32 cycleCount); // @ F0317_CHAMPION_AddScentStrength
+ void putObjectInLeaderHand(Thing thing, bool setMousePointer); // @ F0297_CHAMPION_PutObjectInLeaderHand
+ int16 getMovementTicks(Champion *champ); // @ F0310_CHAMPION_GetMovementTicks
+ bool isAmmunitionCompatibleWithWeapon(uint16 champIndex, uint16 weaponSlotIndex,
+ uint16 ammunitionSlotIndex); // @ F0294_CHAMPION_IsAmmunitionCompatibleWithWeapon
+ void drawAllChampionStates(); // @ F0293_CHAMPION_DrawAllChampionStates
+ void viAltarRebirth(uint16 champIndex); // @ F0283_CHAMPION_ViAltarRebirth
+ void clickOnSlotBox(uint16 slotBoxIndex); // @ F0302_CHAMPION_ProcessCommands28To65_ClickOnSlotBox
+ bool isProjectileSpellCast(uint16 champIndex, Thing thing, int16 kineticEnergy, uint16 requiredManaAmount); // @ F0327_CHAMPION_IsProjectileSpellCast
+ void championShootProjectile(Champion *champ, Thing thing, int16 kineticEnergy,
+ int16 attack, int16 stepEnergy); // @ F0326_CHAMPION_ShootProjectile
+ void applyAndDrawPendingDamageAndWounds(); // @ F0320_CHAMPION_ApplyAndDrawPendingDamageAndWounds
+ void championKill(uint16 champIndex); // @ F0319_CHAMPION_Kill
+ void dropAllObjects(uint16 champIndex); // @ F0318_CHAMPION_DropAllObjects
+ void unpoison(int16 champIndex); // @ F0323_CHAMPION_Unpoison
+ void applyTimeEffects(); // @ F0331_CHAMPION_ApplyTimeEffects_CPSF
+ void savePartyPart2(Common::OutSaveFile *file);
+ void loadPartyPart2(Common::InSaveFile *file);
+
+ Box _boxChampionIcons[4];
+ Color _championColor[4];
+ int16 _lightPowerToLightAmount[16]; // g039_LightPowerToLightAmount
+ Box _boxChampionPortrait;
+ uint16 _slotMasks[38];
+ const char *_baseSkillName[4];
+};
+
+}
+
+#endif
diff --git a/engines/dm/configure.engine b/engines/dm/configure.engine
new file mode 100644
index 0000000000..50a3d9975c
--- /dev/null
+++ b/engines/dm/configure.engine
@@ -0,0 +1,3 @@
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
+add_engine dm "Dungeon Master" yes
diff --git a/engines/dm/console.cpp b/engines/dm/console.cpp
new file mode 100644
index 0000000000..1ccfe39fc1
--- /dev/null
+++ b/engines/dm/console.cpp
@@ -0,0 +1,283 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "dm/console.h"
+#include "dm/dm.h"
+#include "dm/champion.h"
+#include "dm/dungeonman.h"
+#include "dm/movesens.h"
+#include "dm/objectman.h"
+
+
+namespace DM {
+
+bool cstrEquals(const char* a, const char *b) { return strcmp(a, b) == 0; }
+
+class SingleUseFlag {
+ bool _flag;
+public:
+ SingleUseFlag() : _flag(true) {}
+ bool check() {
+ bool currFlagState = _flag;
+ _flag = false;
+ return currFlagState;
+ }
+};
+
+Console::Console(DM::DMEngine* vm) : _vm(vm) {
+ _debugGodmodeMana = false;
+ _debugGodmodeHP = false;
+ _debugGodmodeStamina = false;
+
+ _debugNoclip = false;
+
+ registerCmd("godmode", WRAP_METHOD(Console, Cmd_godmode));
+ registerCmd("noclip", WRAP_METHOD(Console, Cmd_noclip));
+ registerCmd("pos", WRAP_METHOD(Console, Cmd_pos));
+ registerCmd("map", WRAP_METHOD(Console, Cmd_map));
+ registerCmd("listItems", WRAP_METHOD(Console, Cmd_listItems));
+ registerCmd("gimme", WRAP_METHOD(Console, Cmd_gimme));
+}
+
+bool Console::Cmd_godmode(int argc, const char** argv) {
+ if (argc != 3)
+ goto argumentError;
+
+ bool setFlagTo;
+
+ if (cstrEquals("on", argv[2])) {
+ setFlagTo = true;
+ } else if (cstrEquals("off", argv[2])) {
+ setFlagTo = false;
+ } else
+ goto argumentError;
+
+ if (cstrEquals("all", argv[1])) {
+ _debugGodmodeHP = _debugGodmodeMana = _debugGodmodeStamina = setFlagTo;
+ } else if (cstrEquals("mana", argv[1])) {
+ _debugGodmodeMana = setFlagTo;
+ } else if (cstrEquals("hp", argv[1])) {
+ _debugGodmodeHP = setFlagTo;
+ } else if (cstrEquals("stamina", argv[1])) {
+ _debugGodmodeStamina = setFlagTo;
+ } else
+ goto argumentError;
+
+ debugPrintf("God mode set for %s to %s\n", argv[1], argv[2]);
+ return true;
+
+argumentError:
+ debugPrintf("Usage: %s <all/mana/hp/stamina> <on/off>\n", argv[0]);
+ return true;
+}
+
+bool Console::Cmd_noclip(int argc, const char** argv) {
+ if (argc != 2)
+ goto argumentError;
+
+ if (cstrEquals("on", argv[1])) {
+ _debugNoclip = true;
+ static SingleUseFlag haventWarned;
+ if (haventWarned.check())
+ debugPrintf("Noclip can cause glitches and crashes.\n");
+ } else if (cstrEquals("off", argv[1])) {
+ _debugNoclip = false;
+ } else
+ goto argumentError;
+
+ debugPrintf("Noclip set to %s\n", argv[1]);
+ return true;
+
+argumentError:
+ debugPrintf("Usage: %s <on/off>\n", argv[0]);
+ return true;
+}
+
+bool Console::Cmd_pos(int argc, const char** argv) {
+ DungeonMan &dm = *_vm->_dungeonMan;
+ if (argc == 2 && cstrEquals("get", argv[1])) {
+ debugPrintf("Position: (%d, %d) Direction: %s\n", dm._partyMapX + dm._currMap->_offsetMapX,
+ dm._partyMapY + dm._currMap->_offsetMapY, debugGetDirectionName(_vm->_dungeonMan->_partyDir));
+ } else if (argc == 4 && cstrEquals("set", argv[1])) {
+ int x = atoi(argv[2]);
+ int y = atoi(argv[3]);
+ if ((x == 0 && !cstrEquals("0", argv[2])) || (y == 0 && !cstrEquals("0", argv[3]))) {
+ debugPrintf("Error, supply two numbers to '%s set' command\n", argv[0]);
+ return true;
+ }
+
+ Map &currMap = *_vm->_dungeonMan->_currMap;
+ // not >= because dimensions are inslucsive
+ if (x < currMap._offsetMapX || x > currMap._width + currMap._offsetMapX
+ || y < currMap._offsetMapY || y > currMap._height + currMap._offsetMapY) {
+ debugPrintf("Position (%d, %d) is out of bounds, possible values: ([1-%d],[1-%d])\n", x, y,
+ currMap._width + currMap._offsetMapX, currMap._height + currMap._offsetMapY);
+ return true;
+ }
+
+ static SingleUseFlag haventWarned;
+ if (haventWarned.check())
+ debugPrintf("Setting position directly can cause glitches and crashes.\n");
+ debugPrintf("Position set to (%d, %d)\n", x, y);
+ _vm->_moveSens->getMoveResult(Thing::_party, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY,
+ x - currMap._offsetMapX, y - currMap._offsetMapY);
+ } else
+ goto argumentError;
+
+ return true;
+
+argumentError:
+ debugPrintf("Usage: %s get\n", argv[0]);
+ debugPrintf("Usage: %s set <#> <#>\n", argv[0]);
+ return true;
+}
+
+bool Console::Cmd_map(int argc, const char** argv) {
+ if (argc == 2 && cstrEquals("get", argv[1])) {
+ debugPrintf("Map index: %d\n", _vm->_dungeonMan->_partyMapIndex);
+ } else if (argc == 3 && cstrEquals("set", argv[1])) {
+ int index = atoi(argv[2]);
+ if (index == 0 && !cstrEquals("0", argv[2])) {
+ debugPrintf("Error, supply a number to '%s set' command\n", argv[0]);
+ return true;
+ }
+
+ // not >= because dimensions are inslucsive
+ if (index < 0 || index >= _vm->_dungeonMan->_dungeonFileHeader._mapCount) {
+ debugPrintf("Map index %d is out of bounds, possible values [0, %d]\n", index, _vm->_dungeonMan->_dungeonFileHeader._mapCount - 1);
+ return true;
+ }
+
+ static SingleUseFlag haventWarned;
+ if (haventWarned.check())
+ debugPrintf("Setting map directly can cause glitches and crashes.\n");
+ debugPrintf("Map set to %d\n", index);
+
+ _vm->_moveSens->getMoveResult(Thing::_party, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, kM1_MapXNotOnASquare, 0);
+ _vm->_newPartyMapIndex = _vm->_dungeonMan->getLocationAfterLevelChange(
+ _vm->_dungeonMan->_partyMapIndex, index - _vm->_dungeonMan->_partyMapIndex,
+ &_vm->_dungeonMan->_partyMapX, &_vm->_dungeonMan->_partyMapY);
+ if (_vm->_newPartyMapIndex == -1)
+ _vm->_newPartyMapIndex = index;
+ _vm->_dungeonMan->setCurrentMap(_vm->_newPartyMapIndex);
+ _vm->_championMan->setPartyDirection(_vm->_dungeonMan->getStairsExitDirection(_vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY));
+ _vm->_dungeonMan->setCurrentMap(_vm->_dungeonMan->_partyMapIndex);
+ } else
+ goto argumentError;
+
+ return true;
+
+argumentError:
+ debugPrintf("Usage: %s get\n", argv[0]);
+ debugPrintf("Usage: %s set <#>\n", argv[0]);
+ return true;
+}
+
+bool Console::Cmd_listItems(int argc, const char** argv) {
+ Common::String searchedString = "";
+ for (int16 i = 1; i < argc; ++i) {
+ searchedString += argv[i];
+ searchedString += " ";
+ }
+ searchedString.deleteLastChar();
+
+ bool atleastOneFound = false;
+ int16 namesPrintedInLine = 0;
+
+ if(strstr(_vm->_objectMan->_objectNames[0], searchedString.c_str()) != nullptr)
+ debugPrintf("| %s", _vm->_objectMan->_objectNames[0]);
+
+ for (uint16 i = 1; i < k199_ObjectNameCount; ++i) {
+ const char *name = _vm->_objectMan->_objectNames[i - 1];
+ const char *prevName = _vm->_objectMan->_objectNames[i];
+
+ if (!cstrEquals(name, prevName) && (strstr(name, searchedString.c_str()) != nullptr)) {
+ debugPrintf(" | %s", name);
+ atleastOneFound = true;
+
+ if ((++namesPrintedInLine % 6) == 0) {
+ namesPrintedInLine = 0;
+ debugPrintf("\n");
+ }
+ }
+ }
+
+ if (atleastOneFound) {
+ debugPrintf("\n");
+ } else {
+ debugPrintf("No itemnames found containing '%s'\n", searchedString.c_str());
+ }
+
+ return true;
+}
+
+bool Console::Cmd_gimme(int argc, const char** argv) {
+ if (argc < 2) {
+ debugPrintf("Usage: gimme <item name> // item name can have spaces\n");
+ return true;
+ }
+
+ Common::String requestedItemName;
+ for (int16 i = 1; i < argc; ++i) {
+ requestedItemName += argv[i];
+ requestedItemName += " ";
+ }
+ requestedItemName.deleteLastChar();
+
+ for (int16 thingType = 0; thingType < 16; ++thingType) { // 16 number of item types
+ uint16 *thingDataArray = _vm->_dungeonMan->_thingData[thingType];
+ uint16 thingTypeSize = _vm->_dungeonMan->_thingDataWordCount[thingType];
+ uint16 thingCount = _vm->_dungeonMan->_dungeonFileHeader._thingCounts[thingType];
+
+ Thing dummyThing(0);
+ dummyThing.setType(thingType);
+ for (int16 thingIndex = 0; thingIndex < thingCount; ++thingIndex) {
+ dummyThing.setIndex(thingIndex);
+ int16 iconIndex = _vm->_objectMan->getIconIndex(dummyThing);
+ if (iconIndex != -1) {
+ const char *displayName = _vm->_objectMan->_objectNames[iconIndex];
+ if (cstrEquals(displayName, requestedItemName.c_str())) {
+ uint16 *newThingData = new uint16[(thingCount + 1) * thingTypeSize];
+ memcpy(newThingData, thingDataArray, sizeof(uint16) * thingTypeSize * thingCount);
+ delete[] thingDataArray;
+ for (uint16 i = 0; i < thingTypeSize; ++i)
+ newThingData[thingCount * thingTypeSize + i] = newThingData[thingIndex * thingTypeSize + i];
+ _vm->_dungeonMan->_dungeonFileHeader._thingCounts[thingType]++;
+ _vm->_dungeonMan->_thingData[thingType] = newThingData;
+ _vm->_championMan->addObjectInSlot((ChampionIndex)0, dummyThing, (ChampionSlot)29);
+ debugPrintf("Item gimmed to the first champion, last slot\n");
+ return true;
+ }
+ }
+ }
+ }
+
+ debugPrintf("No item found with name '%s'\n", requestedItemName.c_str());
+ return true;
+}
+
+}
diff --git a/engines/dm/console.h b/engines/dm/console.h
new file mode 100644
index 0000000000..eee72aaf18
--- /dev/null
+++ b/engines/dm/console.h
@@ -0,0 +1,59 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_CONSOLE_H
+#define DM_CONSOLE_H
+
+#include "gui/debugger.h"
+
+namespace DM {
+
+class DMEngine;
+
+class Console : public GUI::Debugger {
+private:
+ DMEngine *_vm;
+
+ bool Cmd_godmode(int argc, const char **argv);
+ bool Cmd_noclip(int argc, const char **argv);
+ bool Cmd_pos(int argc, const char **argv);
+ bool Cmd_map(int argc, const char **argv);
+ bool Cmd_listItems(int argc, const char **argv);
+ bool Cmd_gimme(int argc, const char **argv);
+
+public:
+ explicit Console(DM::DMEngine *vm);
+ virtual ~Console(void) {}
+
+ bool _debugGodmodeMana;
+ bool _debugGodmodeHP;
+ bool _debugGodmodeStamina;
+ bool _debugNoclip;
+};
+}
+
+#endif
diff --git a/engines/dm/detection.cpp b/engines/dm/detection.cpp
new file mode 100644
index 0000000000..71528710fb
--- /dev/null
+++ b/engines/dm/detection.cpp
@@ -0,0 +1,175 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+
+#include "common/config-manager.h"
+#include "common/error.h"
+#include "common/fs.h"
+#include "common/system.h"
+
+#include "engines/advancedDetector.h"
+
+#include "dm/dm.h"
+
+namespace DM {
+static const PlainGameDescriptor DMGames[] = {
+ {"dm", "Dungeon Master"},
+ {0, 0}
+};
+
+static const DMADGameDescription gameDescriptions[] = {
+ {
+ {"dm", "Amiga 2.0v English",
+ {
+ {"graphics.dat", 0, "c2205f6225bde728417de29394f97d55", 411960},
+ {"Dungeon.dat", 0, "43a213da8eda413541dd12f90ce202f6", 25006},
+ AD_LISTEND
+ },
+ Common::EN_ANY, Common::kPlatformAmiga, ADGF_NO_FLAGS, GUIO1(GUIO_NONE)
+ },
+ k_saveTarget_DM21, k_saveFormat_dm_amiga__2_x_pc98_x68000_fm_towns_csb_atari_st, k_savePlatform_amiga,
+ { k_saveTarget_DM21, k_saveTarget_endOfList },
+ { k_saveFormat_dm_amiga__2_x_pc98_x68000_fm_towns_csb_atari_st, k_saveFormat_endOfList},
+ { k_savePlatform_accept_any}
+ },
+ {
+ {"dm", "Atari ???v English",
+ {
+ {"graphics.dat", 0, "6ffff2a17e2df0effa9a12fb4b1bf6b6", 271911},
+ {"Dungeon.dat", 0, "be9468b460515741babec9a70501e2e9", 33286},
+ AD_LISTEND
+ },
+ Common::EN_ANY, Common::kPlatformAtariST, ADGF_NO_FLAGS, GUIO1(GUIO_NONE),
+ },
+ k_saveTarget_DM21, k_saveFormat_dm_amiga__2_x_pc98_x68000_fm_towns_csb_atari_st, k_savePlatform_atari_st,
+ { k_saveTarget_DM21, k_saveTarget_endOfList},
+ { k_saveFormat_dm_amiga__2_x_pc98_x68000_fm_towns_csb_atari_st, k_saveFormat_endOfList},
+ { k_savePlatform_accept_any }
+ },
+
+ {
+ AD_TABLE_END_MARKER, k_saveTarget_none, k_saveFormat_none, k_savePlatform_none,
+ {k_saveTarget_none}, {k_saveFormat_none}, {k_savePlatform_none}
+ }
+};
+
+
+static const ADExtraGuiOptionsMap optionsList[] = {
+ AD_EXTRA_GUI_OPTIONS_TERMINATOR
+};
+
+
+class DMMetaEngine : public AdvancedMetaEngine {
+public:
+ DMMetaEngine() : AdvancedMetaEngine(DM::gameDescriptions, sizeof(DMADGameDescription), DMGames, optionsList) {
+ _singleId = "dm";
+ }
+
+ virtual const char *getName() const {
+ return "Dungeon Master";
+ }
+
+ virtual const char *getOriginalCopyright() const {
+ return "Dungeon Master (C) 1987 FTL Games";
+ }
+
+ virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+ if (desc)
+ *engine = new DM::DMEngine(syst, (const DMADGameDescription*)desc);
+ return desc != nullptr;
+ }
+
+ virtual bool hasFeature(MetaEngineFeature f) const {
+ return
+ (f == kSupportsListSaves) ||
+ (f == kSupportsLoadingDuringStartup) ||
+ (f == kSavesSupportThumbnail) ||
+ (f == kSavesSupportMetaInfo) ||
+ (f == kSavesSupportCreationDate);
+ }
+
+ virtual int getMaximumSaveSlot() const { return 99; }
+
+ virtual SaveStateList listSaves(const char *target) const {
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+ SaveGameHeader header;
+ Common::String pattern = target;
+ pattern += ".###";
+
+ Common::StringArray filenames;
+ filenames = saveFileMan->listSavefiles(pattern.c_str());
+
+ SaveStateList saveList;
+
+ for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
+ // Obtain the last 3 digits of the filename, since they correspond to the save slot
+ int slotNum = atoi(file->c_str() + file->size() - 3);
+
+ if ((slotNum >= 0) && (slotNum <= 999)) {
+ Common::InSaveFile *in = saveFileMan->openForLoading(file->c_str());
+ if (in) {
+ if (DM::readSaveGameHeader(in, &header))
+ saveList.push_back(SaveStateDescriptor(slotNum, header._descr.getDescription()));
+ delete in;
+ }
+ }
+ }
+
+ // Sort saves based on slot number.
+ Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
+ return saveList;
+ }
+
+ SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const {
+ Common::String filename = Common::String::format("%s.%03u", target, slot);
+ Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(filename.c_str());
+
+ if (in) {
+ DM::SaveGameHeader header;
+
+ bool successfulRead = DM::readSaveGameHeader(in, &header);
+ delete in;
+
+ if (successfulRead) {
+ SaveStateDescriptor desc(slot, header._descr.getDescription());
+
+ return header._descr;
+ }
+ }
+
+ return SaveStateDescriptor();
+ }
+
+ virtual void removeSaveState(const char *target, int slot) const {}
+};
+
+}
+#if PLUGIN_ENABLED_DYNAMIC(DM)
+REGISTER_PLUGIN_DYNAMIC(DM, PLUGIN_TYPE_ENGINE, DM::DMMetaEngine);
+#else
+REGISTER_PLUGIN_STATIC(DM, PLUGIN_TYPE_ENGINE, DM::DMMetaEngine);
+#endif
diff --git a/engines/dm/dialog.cpp b/engines/dm/dialog.cpp
new file mode 100644
index 0000000000..dd2e15836c
--- /dev/null
+++ b/engines/dm/dialog.cpp
@@ -0,0 +1,257 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "dm/dialog.h"
+#include "dm/gfx.h"
+#include "dm/text.h"
+#include "dm/eventman.h"
+
+namespace DM {
+
+DialogMan::DialogMan(DMEngine *vm) : _vm(vm) {
+ _selectedDialogChoice = 0;
+}
+
+void DialogMan::dialogDraw(const char *msg1, const char *msg2, const char *choice1, const char *choice2, const char *choice3, const char *choice4, bool screenDialog, bool clearScreen, bool fading) {
+ static Box constBox1 = Box(0, 223, 101, 125);
+ static Box constBox2 = Box(0, 223, 76, 100);
+ static Box constBox3 = Box(0, 223, 51, 75);
+ static Box dialog2ChoicesPatch = Box(102, 122, 89, 125);
+ static Box dialog4ChoicesPatch = Box(102, 122, 62, 97);
+
+ _vm->_displayMan->loadIntoBitmap(k0_dialogBoxGraphicIndice, _vm->_displayMan->_bitmapViewport);
+ //Strangerke: the version should be replaced by a ScummVM/RogueVM (?) string
+ // TODO: replace with ScummVM version string
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, 192, 7, k2_ColorLightGray, k1_ColorDarkGary, "V2.2", k136_heightViewport);
+ int16 choiceCount = 1;
+ if (choice2)
+ choiceCount++;
+
+ if (choice3)
+ choiceCount++;
+
+ if (choice4)
+ choiceCount++;
+
+ if (fading)
+ _vm->_displayMan->startEndFadeToPalette(_vm->_displayMan->_blankBuffer);
+
+ if (clearScreen)
+ _vm->_displayMan->fillScreen(k0_ColorBlack);
+
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ if (choiceCount == 1) {
+ _vm->_displayMan->blitToBitmap(_vm->_displayMan->_bitmapViewport, _vm->_displayMan->_bitmapViewport, constBox1, 0, 64, k112_byteWidthViewport, k112_byteWidthViewport, kM1_ColorNoTransparency, k136_heightViewport, k136_heightViewport);
+ _vm->_displayMan->blitToBitmap(_vm->_displayMan->_bitmapViewport, _vm->_displayMan->_bitmapViewport, constBox2, 0, 39, k112_byteWidthViewport, k112_byteWidthViewport, kM1_ColorNoTransparency, k136_heightViewport, k136_heightViewport);
+ _vm->_displayMan->blitToBitmap(_vm->_displayMan->_bitmapViewport, _vm->_displayMan->_bitmapViewport, constBox3, 0, 14, k112_byteWidthViewport, k112_byteWidthViewport, kM1_ColorNoTransparency, k136_heightViewport, k136_heightViewport);
+ printCenteredChoice(_vm->_displayMan->_bitmapViewport, choice1, 112, 114);
+ } else if (choiceCount == 2) {
+ _vm->_displayMan->blitToBitmap(_vm->_displayMan->_bitmapViewport, _vm->_displayMan->_bitmapViewport, dialog2ChoicesPatch, 102, 52, k112_byteWidthViewport, k112_byteWidthViewport, kM1_ColorNoTransparency, k136_heightViewport, k136_heightViewport);
+ printCenteredChoice(_vm->_displayMan->_bitmapViewport, choice1, 112, 77);
+ printCenteredChoice(_vm->_displayMan->_bitmapViewport, choice2, 112, 114);
+ } else if (choiceCount == 3) {
+ printCenteredChoice(_vm->_displayMan->_bitmapViewport, choice1, 112, 77);
+ printCenteredChoice(_vm->_displayMan->_bitmapViewport, choice2, 59, 114);
+ printCenteredChoice(_vm->_displayMan->_bitmapViewport, choice3, 166, 114);
+ } else if (choiceCount == 4) {
+ _vm->_displayMan->blitToBitmap(_vm->_displayMan->_bitmapViewport, _vm->_displayMan->_bitmapViewport, dialog4ChoicesPatch, 102, 99, k112_byteWidthViewport, k112_byteWidthViewport, kM1_ColorNoTransparency, k136_heightViewport, k136_heightViewport);
+ printCenteredChoice(_vm->_displayMan->_bitmapViewport, choice1, 59, 77);
+ printCenteredChoice(_vm->_displayMan->_bitmapViewport, choice2, 166, 77);
+ printCenteredChoice(_vm->_displayMan->_bitmapViewport, choice3, 59, 114);
+ printCenteredChoice(_vm->_displayMan->_bitmapViewport, choice4, 166, 114);
+ }
+
+ int16 textPosX;
+ int16 textPosY = 29;
+ if (msg1) {
+ char L1312_ac_StringPart1[70];
+ char L1313_ac_StringPart2[70];
+ if (isMessageOnTwoLines(msg1, L1312_ac_StringPart1, L1313_ac_StringPart2)) {
+ textPosY = 21;
+ textPosX = 113 - ((strlen(L1312_ac_StringPart1) * 6) >> 1);
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, textPosX, textPosY, k11_ColorYellow, k5_ColorLightBrown, L1312_ac_StringPart1, k136_heightViewport);
+ textPosY += 8;
+ textPosX = 113 - ((strlen(L1313_ac_StringPart2) * 6) >> 1);
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, textPosX, textPosY, k11_ColorYellow, k5_ColorLightBrown, L1313_ac_StringPart2, k136_heightViewport);
+ textPosY += 8;
+ } else {
+ textPosX = 113 - ((strlen(msg1) * 6) >> 1);
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, textPosX, textPosY, k11_ColorYellow, k5_ColorLightBrown, msg1, k136_heightViewport);
+ textPosY += 8;
+ }
+ }
+ if (msg2) {
+ char L1312_ac_StringPart1[70];
+ char L1313_ac_StringPart2[70];
+ if (isMessageOnTwoLines(msg2, L1312_ac_StringPart1, L1313_ac_StringPart2)) {
+ textPosX = 113 - ((strlen(L1312_ac_StringPart1) * 6) >> 1);
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, textPosX, textPosY, k9_ColorGold, k5_ColorLightBrown, L1312_ac_StringPart1, k136_heightViewport);
+ textPosY += 8;
+ textPosX = 113 - ((strlen(L1313_ac_StringPart2) * 6) >> 1);
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, textPosX, textPosY, k9_ColorGold, k5_ColorLightBrown, L1313_ac_StringPart2, k136_heightViewport);
+ } else {
+ textPosX = 113 - ((strlen(msg2) * 6) >> 1);
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, textPosX, textPosY, k9_ColorGold, k5_ColorLightBrown, msg2, k136_heightViewport);
+ }
+ }
+ if (screenDialog) {
+ Box displayBox;
+ displayBox._y1 = 33;
+ displayBox._y2 = 168;
+ displayBox._x1 = 47;
+ displayBox._x2 = 270;
+ _vm->_eventMan->showMouse();
+ _vm->_displayMan->blitToScreen(_vm->_displayMan->_bitmapViewport, &displayBox, k112_byteWidthViewport, kM1_ColorNoTransparency, k136_heightViewport);
+ _vm->_eventMan->hideMouse();
+ } else {
+ _vm->_displayMan->drawViewport(k0_viewportNotDungeonView);
+ _vm->delay(1);
+ }
+
+ if (fading)
+ _vm->_displayMan->startEndFadeToPalette(_vm->_displayMan->_paletteTopAndBottomScreen);
+
+ _vm->_displayMan->_drawFloorAndCeilingRequested = true;
+ _vm->_displayMan->updateScreen();
+}
+
+void DialogMan::printCenteredChoice(byte *bitmap, const char *str, int16 posX, int16 posY) {
+ if (str) {
+ posX -= (strlen(str) * 6) >> 1;
+ _vm->_textMan->printTextToBitmap(bitmap, k112_byteWidthViewport, posX, posY, k9_ColorGold, k5_ColorLightBrown, str, k136_heightViewport);
+ }
+}
+
+bool DialogMan::isMessageOnTwoLines(const char *str, char *part1, char *part2) {
+ uint16 strLength = strlen(str);
+ if (strLength <= 30)
+ return false;
+
+ strcpy(part1, str);
+ uint16 splitPosition = strLength >> 1;
+ while ((splitPosition < strLength) && (part1[splitPosition] != ' '))
+ splitPosition++;
+
+ part1[splitPosition] = '\0';
+ strcpy(part2, &part1[splitPosition + 1]);
+ return true;
+}
+
+int16 DialogMan::getChoice(uint16 choiceCount, uint16 dialogSetIndex, int16 driveType, int16 automaticChoiceIfFlopyInDrive) {
+ _vm->_eventMan->hideMouse();
+ MouseInput *primaryMouseInputBackup = _vm->_eventMan->_primaryMouseInput;
+ MouseInput *secondaryMouseInputBackup = _vm->_eventMan->_secondaryMouseInput;
+ KeyboardInput *primaryKeyboardInputBackup = _vm->_eventMan->_primaryKeyboardInput;
+ KeyboardInput *secondaryKeyboardInputBackup = _vm->_eventMan->_secondaryKeyboardInput;
+ _vm->_eventMan->_secondaryMouseInput = nullptr;
+ _vm->_eventMan->_primaryKeyboardInput = nullptr;
+ _vm->_eventMan->_secondaryKeyboardInput = nullptr;
+ _vm->_eventMan->_primaryMouseInput = _vm->_eventMan->_primaryMouseInputDialogSets[dialogSetIndex][choiceCount - 1];
+ _vm->_eventMan->discardAllInput();
+ _selectedDialogChoice = 99;
+ do {
+ Common::Event key;
+ Common::EventType eventType = _vm->_eventMan->processInput(&key);
+ _vm->_eventMan->processCommandQueue();
+ _vm->delay(1);
+ _vm->_displayMan->updateScreen();
+ if ((_selectedDialogChoice == 99) && (choiceCount == 1)
+ && (eventType != Common::EVENT_INVALID) && key.kbd.keycode == Common::KEYCODE_RETURN) {
+ /* If a choice has not been made yet with the mouse and the dialog has only one possible choice and carriage return was pressed on the keyboard */
+ _selectedDialogChoice = k1_DIALOG_CHOICE_1;
+ }
+ } while (_selectedDialogChoice == 99);
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ Box boxA = _vm->_eventMan->_primaryMouseInput[_selectedDialogChoice - 1]._hitbox;
+ boxA._x1 -= 3;
+ boxA._x2 += 3;
+ boxA._y1 -= 3;
+ boxA._y2 += 4;
+ _vm->_eventMan->showMouse();
+ _vm->_displayMan->_drawFloorAndCeilingRequested = true;
+ Box boxB(0, 0, boxA._x2 - boxA._x1 + 3, boxA._y2 - boxA._y1 + 3);
+ _vm->_displayMan->blitToBitmap(_vm->_displayMan->_bitmapScreen, _vm->_displayMan->_bitmapViewport,
+ boxB, boxA._x1, boxA._y1, k160_byteWidthScreen, k160_byteWidthScreen, kM1_ColorNoTransparency, 200, 25);
+ _vm->delay(1);
+ boxB = boxA;
+ boxB._y2 = boxB._y1;
+ _vm->_displayMan->fillScreenBox(boxB, k5_ColorLightBrown);
+ boxB = boxA;
+ boxB._x2 = boxB._x1;
+ boxB._y2--;
+ _vm->_displayMan->fillScreenBox(boxB, k5_ColorLightBrown);
+ boxB = boxA;
+ boxB._y2--;
+ boxB._y1 = boxB._y2;
+ boxB._x1 -= 2;
+ _vm->_displayMan->fillScreenBox(boxB, k0_ColorBlack);
+ boxB = boxA;
+ boxB._x1 = boxB._x2;
+ _vm->_displayMan->fillScreenBox(boxB, k0_ColorBlack);
+ _vm->delay(2);
+ boxB = boxA;
+ boxB._y1++;
+ boxB._y2 = boxB._y1;
+ boxB._x2 -= 2;
+ _vm->_displayMan->fillScreenBox(boxB, k5_ColorLightBrown);
+ boxB = boxA;
+ boxB._x1++;
+ boxB._x2 = boxB._x1;
+ boxB._y2--;
+ _vm->_displayMan->fillScreenBox(boxB, k5_ColorLightBrown);
+ boxB = boxA;
+ boxB._x2--;
+ boxB._x1 = boxB._x2;
+ _vm->_displayMan->fillScreenBox(boxB, k0_ColorBlack);
+ boxB = boxA;
+ boxB._y1 = boxB._y2 = boxB._y2 - 2;
+ boxB._x1++;
+ _vm->_displayMan->fillScreenBox(boxB, k0_ColorBlack);
+ boxB = boxA;
+ boxB._y1 = boxB._y2 = boxB._y2 + 2;
+ boxB._x1--;
+ boxB._x2 += 2;
+ _vm->_displayMan->fillScreenBox(boxB, k13_ColorLightestGray);
+ boxB = boxA;
+ boxB._x1 = boxB._x2 = boxB._x2 + 3;
+ boxB._y2 += 2;
+ _vm->_displayMan->fillScreenBox(boxB, k13_ColorLightestGray);
+ _vm->delay(2);
+ boxA._x2 += 3;
+ boxA._y2 += 3;
+ _vm->_displayMan->blitToBitmap(_vm->_displayMan->_bitmapViewport, _vm->_displayMan->_bitmapScreen,
+ boxA, 0, 0, k160_byteWidthScreen, k160_byteWidthScreen, kM1_ColorNoTransparency, 25, k200_heightScreen);
+ _vm->_eventMan->hideMouse();
+ _vm->_eventMan->_primaryMouseInput = primaryMouseInputBackup;
+ _vm->_eventMan->_secondaryMouseInput = secondaryMouseInputBackup;
+ _vm->_eventMan->_primaryKeyboardInput = primaryKeyboardInputBackup;
+ _vm->_eventMan->_secondaryKeyboardInput = secondaryKeyboardInputBackup;
+ _vm->_eventMan->discardAllInput();
+ _vm->_eventMan->showMouse();
+ return _selectedDialogChoice;
+}
+}
diff --git a/engines/dm/dialog.h b/engines/dm/dialog.h
new file mode 100644
index 0000000000..c736f35b63
--- /dev/null
+++ b/engines/dm/dialog.h
@@ -0,0 +1,61 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_DIALOG_H
+#define DM_DIALOG_H
+
+#include "dm/dm.h"
+
+namespace DM {
+
+#define k0_DIALOG_SET_VIEWPORT 0
+#define k1_DIALOG_SET_SCREEN 1
+#define k2_DIALOG_SET_UNKNOWN 2
+#define k1_ONE_CHOICE 1
+#define k2_TWO_CHOICES 2
+#define k4_FOUR_CHOICES 4
+#define k0_DIALOG_CHOICE_NONE 0
+#define k1_DIALOG_CHOICE_1 1
+#define k2_DIALOG_CHOICE_2 2
+#define k3_DIALOG_CHOICE_3 3
+#define k4_DIALOG_CHOICE_4 4
+
+class DialogMan {
+ DMEngine *_vm;
+public:
+ uint16 _selectedDialogChoice; // @ G0335_ui_SelectedDialogChoice
+ explicit DialogMan(DMEngine *vm);
+ void dialogDraw(const char *msg1, const char *msg2, const char *choice1, const char *choice2,
+ const char *choice3, const char *choice4, bool screenDialog, bool clearScreen, bool fading); // @ F0427_DIALOG_Draw
+ void printCenteredChoice(byte *bitmap, const char *str, int16 posX, int16 posY); // @ F0425_DIALOG_PrintCenteredChoice
+ bool isMessageOnTwoLines(const char *str, char *part1, char *part2); // @ F0426_DIALOG_IsMessageOnTwoLines
+ int16 getChoice(uint16 choiceCount, uint16 dialogSetIndex, int16 driveType, int16 automaticChoiceIfFlopyInDrive); // @ F0424_DIALOG_GetChoice
+};
+
+}
+
+#endif
diff --git a/engines/dm/dm.cpp b/engines/dm/dm.cpp
new file mode 100644
index 0000000000..78ef3a79e5
--- /dev/null
+++ b/engines/dm/dm.cpp
@@ -0,0 +1,1050 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "advancedDetector.h"
+
+#include "common/config-manager.h"
+#include "common/scummsys.h"
+#include "common/system.h"
+
+#include "common/debug.h"
+#include "common/debug-channels.h"
+#include "common/error.h"
+
+#include "common/file.h"
+#include "common/events.h"
+#include "common/array.h"
+#include "common/algorithm.h"
+#include "common/translation.h"
+
+#include "engines/util.h"
+#include "engines/engine.h"
+
+#include "graphics/cursorman.h"
+#include "graphics/palette.h"
+#include "graphics/surface.h"
+
+#include "gui/saveload.h"
+
+#include "dm/dm.h"
+#include "dm/gfx.h"
+#include "dm/dungeonman.h"
+#include "dm/eventman.h"
+#include "dm/menus.h"
+#include "dm/champion.h"
+#include "dm/loadsave.h"
+#include "dm/objectman.h"
+#include "dm/inventory.h"
+#include "dm/text.h"
+#include "dm/movesens.h"
+#include "dm/group.h"
+#include "dm/timeline.h"
+#include "dm/projexpl.h"
+#include "dm/dialog.h"
+#include "dm/sounds.h"
+
+namespace DM {
+const char *debugGetDirectionName(Direction dir) {
+ static const char *directionNames[] = {"North", "East", "South", "West"};
+ if (dir < 0 || dir > 3)
+ return "Invalid direction";
+ return directionNames[dir];
+}
+
+void turnDirRight(Direction &dir) {
+ dir = (Direction)((dir + 1) & 3);
+}
+
+void turnDirLeft(Direction &dir) {
+ dir = (Direction)((dir - 1) & 3);
+}
+
+Direction returnOppositeDir(Direction dir) {
+ return (Direction)((dir + 2) & 3);
+}
+
+uint16 returnPrevVal(uint16 val) {
+ return (Direction)((val + 3) & 3);
+}
+
+uint16 returnNextVal(uint16 val) {
+ return (val + 1) & 0x3;
+}
+
+bool isOrientedWestEast(Direction dir) {
+ return dir & 1;
+}
+
+uint16 toggleFlag(uint16& val, uint16 mask) {
+ return val ^= mask;
+}
+
+uint16 bitmapByteCount(uint16 pixelWidth, uint16 height) {
+ return pixelWidth / 2 * height;
+}
+
+uint16 normalizeModulo4(uint16 val) {
+ return val & 3;
+}
+
+int32 filterTime(int32 mapTime) {
+ return mapTime & 0x00FFFFFF;
+}
+
+int32 setMapAndTime(int32 &mapTime, uint32 map, uint32 time) {
+ return (mapTime) = ((time) | (((long)(map)) << 24));
+}
+
+uint16 getMap(int32 mapTime) {
+ return ((uint16)((mapTime) >> 24));
+}
+
+Thing thingWithNewCell(Thing thing, int16 cell) {
+ return Thing(((thing.toUint16()) & 0x3FFF) | ((cell) << 14));
+}
+
+int16 getDistance(int16 mapx1, int16 mapy1, int16 mapx2, int16 mapy2) {
+ return ABS(mapx1 - mapx2) + ABS(mapy1 - mapy2);
+}
+
+DMEngine::DMEngine(OSystem *syst, const DMADGameDescription *desc) : Engine(syst), _console(nullptr), _gameVersion(desc) {
+ // register random source
+ _rnd = new Common::RandomSource("dm");
+
+ _dungeonMan = nullptr;
+ _displayMan = nullptr;
+ _eventMan = nullptr;
+ _menuMan = nullptr;
+ _championMan = nullptr;
+ _objectMan = nullptr;
+ _inventoryMan = nullptr;
+ _textMan = nullptr;
+ _moveSens = nullptr;
+ _groupMan = nullptr;
+ _timeline = nullptr;
+ _projexpl = nullptr;
+ _displayMan = nullptr;
+ _sound = nullptr;
+
+ _engineShouldQuit = false;
+ _dungeonId = 0;
+
+ _newGameFl = 0;
+ _restartGameRequest = false;
+ _stopWaitingForPlayerInput = true;
+ _gameTimeTicking = false;
+ _restartGameAllowed = false;
+ _gameId = 0;
+ _pressingEye = false;
+ _stopPressingEye = false;
+ _pressingMouth = false;
+ _stopPressingMouth = false;
+ _highlightBoxInversionRequested = false;
+ _projectileDisableMovementTicks = 0;
+ _lastProjectileDisabledMovementDirection = 0;
+ _gameWon = false;
+ _newPartyMapIndex = kM1_mapIndexNone;
+ _setMousePointerToObjectInMainLoop = false;
+ _disabledMovementTicks = 0;
+ _gameTime = 0;
+ _stringBuildBuffer[0] = '\0';
+ _waitForInputMaxVerticalBlankCount = 0;
+ _savedScreenForOpenEntranceDoors = nullptr;
+ for (uint16 i = 0; i < 10; ++i)
+ _entranceDoorAnimSteps[i] = nullptr;
+ _interfaceCredits = nullptr;
+ debug("DMEngine::DMEngine");
+
+ _saveThumbnail = nullptr;
+ _canLoadFromGMM = false;
+ _loadSaveSlotAtRuntime = -1;
+}
+
+DMEngine::~DMEngine() {
+ debug("DMEngine::~DMEngine");
+
+ // dispose of resources
+ delete _rnd;
+ delete _console;
+ delete _displayMan;
+ delete _dungeonMan;
+ delete _eventMan;
+ delete _menuMan;
+ delete _championMan;
+ delete _objectMan;
+ delete _inventoryMan;
+ delete _textMan;
+ delete _moveSens;
+ delete _groupMan;
+ delete _timeline;
+ delete _projexpl;
+ delete _dialog;
+ delete _sound;
+
+ delete _saveThumbnail;
+
+ delete[] _savedScreenForOpenEntranceDoors;
+ // clear debug channels
+ DebugMan.clearAllDebugChannels();
+}
+
+bool DMEngine::hasFeature(EngineFeature f) const {
+ return
+ (f == kSupportsSavingDuringRuntime) ||
+ (f == kSupportsLoadingDuringRuntime);
+}
+
+Common::Error DMEngine::loadGameState(int slot) {
+ if (loadgame(slot) != kM1_LoadgameFailure) {
+ _displayMan->fillScreen(k0_ColorBlack);
+ _displayMan->startEndFadeToPalette(_displayMan->_palDungeonView[0]);
+ _newGameFl = k0_modeLoadSavedGame;
+
+ startGame();
+ _restartGameRequest = false;
+ _eventMan->hideMouse();
+ _eventMan->discardAllInput();
+ return Common::kNoError;
+ }
+
+ return Common::kNoGameDataFoundError;
+}
+
+bool DMEngine::canLoadGameStateCurrently() {
+ return _canLoadFromGMM;
+}
+
+void DMEngine::delay(uint16 verticalBlank) {
+ for (uint16 i = 0; i < verticalBlank * 2; ++i) {
+ _eventMan->processInput();
+ _displayMan->updateScreen();
+ _system->delayMillis(10); // Google says most Amiga games had a refreshrate of 50 hz
+ }
+}
+
+uint16 DMEngine::getScaledProduct(uint16 val, uint16 scale, uint16 vale2) {
+ return ((uint32)val * vale2) >> scale;
+}
+
+void DMEngine::initializeGame() {
+ initMemoryManager();
+ _displayMan->loadGraphics();
+ _displayMan->initializeGraphicData();
+ _displayMan->loadFloorSet(k0_FloorSetStone);
+ _displayMan->loadWallSet(k0_WallSetStone);
+
+ _sound->loadSounds(); // @ F0506_AMIGA_AllocateData
+
+ if (!ConfMan.hasKey("save_slot")) // skip drawing title if loading from launcher
+ drawTittle();
+
+ _textMan->initialize();
+ _objectMan->loadObjectNames();
+ _eventMan->initMouse();
+
+ int16 saveSlot = -1;
+ do {
+ // if loading from the launcher
+ if (ConfMan.hasKey("save_slot")) {
+ saveSlot = ConfMan.getInt("save_slot");
+ } else { // else show the entrance
+ processEntrance();
+ if (_engineShouldQuit)
+ return;
+
+ if (_newGameFl == k0_modeLoadSavedGame) { // if resume was clicked, bring up ScummVM load screen
+ GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
+ saveSlot = dialog->runModalWithCurrentTarget();
+ delete dialog;
+ }
+ }
+ } while (loadgame(saveSlot) != k1_LoadgameSuccess);
+
+ _displayMan->loadIntoBitmap(k11_MenuSpellAreLinesIndice, _menuMan->_bitmapSpellAreaLines); // @ F0396_MENUS_LoadSpellAreaLinesBitmap
+
+ // There was some memory wizardy for the Amiga platform, I skipped that part
+ _displayMan->allocateFlippedWallBitmaps();
+
+ startGame();
+ if (_newGameFl)
+ _moveSens->getMoveResult(Thing::_party, kM1_MapXNotOnASquare, 0, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY);
+ _eventMan->showMouse();
+ _eventMan->discardAllInput();
+}
+
+void DMEngine::initMemoryManager() {
+ static uint16 palSwoosh[16] = {0x000, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0xFFF, 0x000, 0xFFF, 0xAAA, 0xFFF, 0xAAA, 0x444, 0xFF0, 0xFF0}; // @ K0057_aui_Palette_Swoosh
+
+ _displayMan->buildPaletteChangeCopperList(palSwoosh, palSwoosh);
+ for (uint16 i = 0; i < 16; ++i) {
+ _displayMan->_paletteTopAndBottomScreen[i] = _displayMan->_palDungeonView[0][i];
+ _displayMan->_paletteMiddleScreen[i] = _displayMan->_palDungeonView[0][i];
+ }
+}
+
+void DMEngine::startGame() {
+ static Box boxScreenTop(0, 319, 0, 32); // @ G0061_s_Graphic562_Box_ScreenTop
+ static Box boxScreenRight(224, 319, 33, 169); // @ G0062_s_Graphic562_Box_ScreenRight
+ static Box boxScreenBottom(0, 319, 169, 199); // @ G0063_s_Graphic562_Box_ScreenBottom
+
+ _pressingEye = false;
+ _stopPressingEye = false;
+ _pressingMouth = false;
+ _stopPressingMouth = false;
+ _highlightBoxInversionRequested = false;
+ _eventMan->_highlightBoxEnabled = false;
+ _championMan->_partyIsSleeping = false;
+ _championMan->_actingChampionOrdinal = indexToOrdinal(kDMChampionNone);
+ _menuMan->_actionAreaContainsIcons = true;
+ _eventMan->_useChampionIconOrdinalAsMousePointerBitmap = indexToOrdinal(kDMChampionNone);
+
+ _eventMan->_primaryMouseInput = _eventMan->_primaryMouseInputInterface;
+ _eventMan->_secondaryMouseInput = _eventMan->_secondaryMouseInputMovement;
+ _eventMan->_primaryKeyboardInput = _eventMan->_primaryKeyboardInputInterface;
+ _eventMan->_secondaryKeyboardInput = _eventMan->_secondaryKeyboardInputMovement;
+
+ processNewPartyMap(_dungeonMan->_partyMapIndex);
+
+ if (!_newGameFl) {
+ _displayMan->startEndFadeToPalette(_displayMan->_paletteTopAndBottomScreen);
+ _displayMan->_useByteBoxCoordinates = false;
+ delay(1);
+ _displayMan->fillScreenBox(boxScreenTop, k0_ColorBlack);
+ _displayMan->fillScreenBox(boxScreenRight, k0_ColorBlack);
+ _displayMan->fillScreenBox(boxScreenBottom, k0_ColorBlack);
+ } else {
+ _displayMan->_useByteBoxCoordinates = false;
+ _displayMan->fillScreenBox(boxScreenTop, k0_ColorBlack);
+ _displayMan->fillScreenBox(boxScreenRight, k0_ColorBlack);
+ _displayMan->fillScreenBox(boxScreenBottom, k0_ColorBlack);
+ }
+
+ _displayMan->buildPaletteChangeCopperList(_displayMan->_palDungeonView[0], _displayMan->_paletteTopAndBottomScreen);
+ _menuMan->drawMovementArrows();
+ _championMan->resetDataToStartGame();
+ _gameTimeTicking = true;
+}
+
+void DMEngine::processNewPartyMap(uint16 mapIndex) {
+ _groupMan->removeAllActiveGroups();
+ _dungeonMan->setCurrentMapAndPartyMap(mapIndex);
+ _displayMan->loadCurrentMapGraphics();
+ _groupMan->addAllActiveGroups();
+ _inventoryMan->setDungeonViewPalette();
+}
+
+Common::Error DMEngine::run() {
+ initConstants();
+
+ // scummvm/engine specific
+ initGraphics(320, 200, false);
+ _console = new Console(this);
+ _displayMan = new DisplayMan(this);
+ _dungeonMan = new DungeonMan(this);
+ _eventMan = new EventManager(this);
+ _menuMan = new MenuMan(this);
+ _championMan = new ChampionMan(this);
+ _objectMan = new ObjectMan(this);
+ _inventoryMan = new InventoryMan(this);
+ _textMan = new TextMan(this);
+ _moveSens = new MovesensMan(this);
+ _groupMan = new GroupMan(this);
+ _timeline = new Timeline(this);
+ _projexpl = new ProjExpl(this);
+ _dialog = new DialogMan(this);
+ _sound = SoundMan::getSoundMan(this, _gameVersion);
+ _displayMan->setUpScreens(320, 200);
+
+ initializeGame();
+ while (true) {
+ gameloop();
+
+ if (_engineShouldQuit)
+ return Common::kNoError;
+
+ if (_loadSaveSlotAtRuntime == -1)
+ endGame(_championMan->_partyDead);
+ else {
+ loadGameState(_loadSaveSlotAtRuntime);
+ _menuMan->drawEnabledMenus();
+ _displayMan->updateScreen();
+ _loadSaveSlotAtRuntime = -1;
+ }
+ }
+
+ return Common::kNoError;
+}
+
+void DMEngine::gameloop() {
+ _canLoadFromGMM = true;
+ _waitForInputMaxVerticalBlankCount = 15;
+ while (true) {
+ if (_engineShouldQuit) {
+ _canLoadFromGMM = false;
+ return;
+ }
+
+ // DEBUG CODE
+ for (int16 i = 0; i < _championMan->_partyChampionCount; ++i) {
+ Champion &champ = _championMan->_champions[i];
+ if (_console->_debugGodmodeHP)
+ champ._currHealth = champ._maxHealth;
+ if (_console->_debugGodmodeMana)
+ champ._currMana = champ._maxMana;
+ if (_console->_debugGodmodeStamina)
+ champ._currStamina = champ._maxStamina;
+ }
+
+ for (;;) {
+
+
+ if (_newPartyMapIndex != kM1_mapIndexNone) {
+ processNewPartyMap(_newPartyMapIndex);
+ _moveSens->getMoveResult(Thing::_party, kM1_MapXNotOnASquare, 0, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY);
+ _newPartyMapIndex = kM1_mapIndexNone;
+ _eventMan->discardAllInput();
+ }
+ _timeline->processTimeline();
+
+ if (_newPartyMapIndex == kM1_mapIndexNone)
+ break;
+ }
+
+ if (!_inventoryMan->_inventoryChampionOrdinal && !_championMan->_partyIsSleeping) {
+ Box box(0, 223, 0, 135);
+ _displayMan->fillBoxBitmap(_displayMan->_bitmapViewport, box, k0_ColorBlack, k112_byteWidthViewport, k136_heightViewport); // (possibly dummy code)
+ _displayMan->drawDungeon(_dungeonMan->_partyDir, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY);
+ if (_setMousePointerToObjectInMainLoop) {
+ _setMousePointerToObjectInMainLoop = false;
+ _eventMan->showMouse();
+ _eventMan->setPointerToObject(_objectMan->_objectIconForMousePointer);
+ _eventMan->hideMouse();
+
+ }
+ if (_eventMan->_refreshMousePointerInMainLoop) {
+ _eventMan->_refreshMousePointerInMainLoop = false;
+ _eventMan->_mousePointerBitmapUpdated = true;
+ _eventMan->showMouse();
+ _eventMan->hideMouse();
+ }
+ }
+ _eventMan->highlightBoxDisable();
+ _sound->playPendingSound();
+ _championMan->applyAndDrawPendingDamageAndWounds();
+ if (_championMan->_partyDead)
+ break;
+
+ _gameTime++;
+
+ if (!(_gameTime & 511))
+ _inventoryMan->decreaseTorchesLightPower();
+
+ if (_championMan->_party._freezeLifeTicks)
+ _championMan->_party._freezeLifeTicks -= 1;
+
+ _menuMan->refreshActionAreaAndSetChampDirMaxDamageReceived();
+
+ if (!(_gameTime & (_championMan->_partyIsSleeping ? 15 : 63)))
+ _championMan->applyTimeEffects();
+
+ if (_disabledMovementTicks)
+ _disabledMovementTicks--;
+
+ if (_projectileDisableMovementTicks)
+ _projectileDisableMovementTicks--;
+
+ _textMan->clearExpiredRows();
+ _stopWaitingForPlayerInput = false;
+ uint16 vblankCounter = 0;
+ do {
+ _eventMan->processInput();
+
+ if (_stopPressingEye) {
+ _pressingEye = false;
+ _stopPressingEye = false;
+ _inventoryMan->drawStopPressingEye();
+ } else if (_stopPressingMouth) {
+ _pressingMouth = false;
+ _stopPressingMouth = false;
+ _inventoryMan->drawStopPressingMouth();
+ }
+
+ _eventMan->processCommandQueue();
+ if (_engineShouldQuit || _loadSaveSlotAtRuntime != -1) {
+ _canLoadFromGMM = false;
+ return;
+ }
+ _displayMan->updateScreen();
+ if (!_stopWaitingForPlayerInput) {
+ _eventMan->highlightBoxDisable();
+ }
+
+ if (++vblankCounter > _waitForInputMaxVerticalBlankCount)
+ _stopWaitingForPlayerInput = true;
+ else if (!_stopWaitingForPlayerInput)
+ _system->delayMillis(10);
+
+ } while (!_stopWaitingForPlayerInput || !_gameTimeTicking);
+ }
+ _canLoadFromGMM = false;
+}
+
+int16 DMEngine::ordinalToIndex(int16 val) {
+ return val - 1;
+}
+
+int16 DMEngine::indexToOrdinal(int16 val) {
+ return val + 1;
+}
+
+void DMEngine::processEntrance() {
+ _eventMan->_primaryMouseInput = _eventMan->_primaryMouseInputEntrance;
+ _eventMan->_secondaryMouseInput = nullptr;
+ _eventMan->_primaryKeyboardInput = nullptr;
+ _eventMan->_secondaryKeyboardInput = nullptr;
+ _entranceDoorAnimSteps[0] = new byte[128 * 161 * 12];
+ for (uint16 idx = 1; idx < 8; idx++)
+ _entranceDoorAnimSteps[idx] = _entranceDoorAnimSteps[idx - 1] + 128 * 161;
+
+ _entranceDoorAnimSteps[8] = _entranceDoorAnimSteps[7] + 128 * 161;
+ _entranceDoorAnimSteps[9] = _entranceDoorAnimSteps[8] + 128 * 161 * 2;
+
+ _displayMan->loadIntoBitmap(k3_entranceRightDoorGraphicIndice, _entranceDoorAnimSteps[4]);
+ _displayMan->loadIntoBitmap(k2_entranceLeftDoorGraphicIndice, _entranceDoorAnimSteps[0]);
+ _interfaceCredits = _displayMan->getNativeBitmapOrGraphic(k5_creditsGraphicIndice);
+ _displayMan->_useByteBoxCoordinates = false;
+ Box displayBox(0, 100, 0, 160);
+ for (uint16 idx = 1; idx < 4; idx++) {
+ _displayMan->blitToBitmap(_entranceDoorAnimSteps[0], _entranceDoorAnimSteps[idx], displayBox, idx << 2, 0, k64_byteWidth, k64_byteWidth, kM1_ColorNoTransparency, 161, 161);
+ displayBox._x2 -= 4;
+ }
+ displayBox._x2 = 127;
+ for (uint16 idx = 5; idx < 8; idx++) {
+ displayBox._x1 += 4;
+ _displayMan->blitToBitmap(_entranceDoorAnimSteps[4], _entranceDoorAnimSteps[idx], displayBox, 0, 0, k64_byteWidth, k64_byteWidth, kM1_ColorNoTransparency, 161, 161);
+ }
+
+ do {
+ drawEntrance();
+ _eventMan->showMouse();
+ _eventMan->discardAllInput();
+ _newGameFl = k99_modeWaitingOnEntrance;
+ do {
+ _eventMan->processInput();
+ if (_engineShouldQuit)
+ return;
+ _eventMan->processCommandQueue();
+ _displayMan->updateScreen();
+ } while (_newGameFl == k99_modeWaitingOnEntrance);
+ } while (_newGameFl == k202_CommandEntranceDrawCredits);
+
+ //Strangerke: CHECKME: Earlier versions were using G0566_puc_Graphic534_Sound01Switch
+ _sound->play(k01_soundSWITCH, 112, 0x40, 0x40);
+ delay(20);
+ _eventMan->showMouse();
+ if (_newGameFl)
+ openEntranceDoors();
+
+ delete[] _entranceDoorAnimSteps[0];
+ for (uint16 i = 0; i < 10; ++i)
+ _entranceDoorAnimSteps[i] = nullptr;
+}
+
+void DMEngine::endGame(bool doNotDrawCreditsOnly) {
+ static Box boxEndgameRestartOuterEN(103, 217, 145, 159);
+ static Box boxEndgameRestartInnerEN(105, 215, 147, 157);
+
+ static Box boxEndgameRestartOuterDE(82, 238, 145, 159);
+ static Box boxEndgameRestartInnerDE(84, 236, 147, 157);
+
+ static Box boxEndgameRestartOuterFR(100, 220, 145, 159);
+ static Box boxEndgameRestartInnerFR(102, 218, 147, 157);
+
+ Box restartOuterBox;
+ Box restartInnerBox;
+
+ switch (getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY:
+ restartOuterBox = boxEndgameRestartOuterEN;
+ restartInnerBox = boxEndgameRestartInnerEN;
+ break;
+ case Common::DE_DEU:
+ restartOuterBox = boxEndgameRestartOuterDE;
+ restartInnerBox = boxEndgameRestartInnerDE;
+ break;
+ case Common::FR_FRA:
+ restartOuterBox = boxEndgameRestartOuterFR;
+ restartInnerBox = boxEndgameRestartInnerFR;
+ break;
+ }
+
+ static Box theEndBox(120, 199, 95, 108);
+ static Box championMirrorBox(11, 74, 7, 49);
+ static Box championPortraitBox(27, 58, 13, 41);
+
+ bool waitBeforeDrawingRestart = true;
+
+ _eventMan->setMousePointerToNormal(k0_pointerArrow);
+ _eventMan->showMouse();
+ _eventMan->_primaryMouseInput = nullptr;
+ _eventMan->_secondaryMouseInput = nullptr;
+ _eventMan->_primaryKeyboardInput = nullptr;
+ _eventMan->_secondaryKeyboardInput = nullptr;
+ if (doNotDrawCreditsOnly && !_gameWon) {
+ _sound->requestPlay(k06_soundSCREAM, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY, k0_soundModePlayImmediately);
+ delay(240);
+ }
+
+ if (_displayMan->_paletteSwitchingEnabled) {
+ uint16 oldPalTopAndBottomScreen[16];
+ for (uint16 i = 0; i < 16; ++i)
+ oldPalTopAndBottomScreen[i] = _displayMan->_paletteTopAndBottomScreen[i];
+ for (int i = 0; i <= 7; i++) {
+ delay(1);
+ for (int colIdx = 0; colIdx < 16; colIdx++) {
+ _displayMan->_paletteMiddleScreen[colIdx] = _displayMan->getDarkenedColor(_displayMan->_paletteMiddleScreen[colIdx]);
+ _displayMan->_paletteTopAndBottomScreen[colIdx] = _displayMan->getDarkenedColor(_displayMan->_paletteTopAndBottomScreen[colIdx]);
+ }
+ }
+ _displayMan->_paletteSwitchingEnabled = false;
+ delay(1);
+ for (uint16 i = 0; i < 16; ++i)
+ _displayMan->_paletteTopAndBottomScreen[i] = oldPalTopAndBottomScreen[i];
+ } else
+ _displayMan->startEndFadeToPalette(_displayMan->_blankBuffer);
+
+ uint16 darkBluePalette[16];
+ if (doNotDrawCreditsOnly) {
+ if (_gameWon) {
+ // Strangerke: Related to portraits. Game data could be missing for earlier versions of the game.
+ _displayMan->fillScreen(k12_ColorDarkestGray);
+ for (int16 championIndex = kDMChampionFirst; championIndex < _championMan->_partyChampionCount; championIndex++) {
+ int16 textPosY = championIndex * 48;
+ Champion *curChampion = &_championMan->_champions[championIndex];
+ _displayMan->blitToScreen(_displayMan->getNativeBitmapOrGraphic(k208_wallOrn_43_champMirror), &championMirrorBox, k32_byteWidth, k10_ColorFlesh, 43);
+ _displayMan->blitToScreen(curChampion->_portrait, &championPortraitBox, k16_byteWidth, k1_ColorDarkGary, 29);
+ _textMan->printEndGameString(87, textPosY += 14, k9_ColorGold, curChampion->_name);
+ int textPosX = (6 * strlen(curChampion->_name)) + 87;
+ char championTitleFirstCharacter = curChampion->_title[0];
+ if ((championTitleFirstCharacter != ',') && (championTitleFirstCharacter != ';') && (championTitleFirstCharacter != '-'))
+ textPosX += 6;
+
+ _textMan->printEndGameString(textPosX, textPosY++, k9_ColorGold, curChampion->_title);
+ for (int16 idx = kDMSkillFighter; idx <= kDMSkillWizard; idx++) {
+ uint16 skillLevel = MIN<uint16>(16, _championMan->getSkillLevel(championIndex, idx | (kDMIgnoreObjectModifiers | kDMIgnoreTemporaryExperience)));
+ if (skillLevel == 1)
+ continue;
+
+ char displStr[20];
+ strcpy(displStr, _inventoryMan->_skillLevelNames[skillLevel - 2]);
+ strcat(displStr, " ");
+ strcat(displStr, _championMan->_baseSkillName[idx]);
+ _textMan->printEndGameString(105, textPosY = textPosY + 8, k13_ColorLightestGray, displStr);
+ }
+ championMirrorBox._y1 += 48;
+ championMirrorBox._y2 += 48;
+ championPortraitBox._y1 += 48;
+ championPortraitBox._y1 += 48;
+ }
+ _displayMan->startEndFadeToPalette(_displayMan->_paletteTopAndBottomScreen);
+ _engineShouldQuit = true;
+ return;
+ }
+T0444017:
+ _displayMan->fillScreen(k0_ColorBlack);
+ _displayMan->blitToScreen(_displayMan->getNativeBitmapOrGraphic(k6_theEndIndice), &theEndBox, k40_byteWidth, kM1_ColorNoTransparency, 14);
+ for (uint16 i = 0; i < 16; ++i)
+ darkBluePalette[i] = D01_RGB_DARK_BLUE;
+ uint16 curPalette[16];
+ for (uint16 i = 0; i < 15; ++i)
+ curPalette[i] = darkBluePalette[i];
+ curPalette[15] = D09_RGB_WHITE;
+ _displayMan->startEndFadeToPalette(curPalette);
+ _displayMan->updateScreen();
+ if (waitBeforeDrawingRestart)
+ delay(300);
+
+ if (_restartGameAllowed) {
+ _displayMan->_useByteBoxCoordinates = false;
+ _displayMan->fillScreenBox(restartOuterBox, k12_ColorDarkestGray);
+ _displayMan->fillScreenBox(restartInnerBox, k0_ColorBlack);
+
+ switch (getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY:
+ _textMan->printToLogicalScreen(110, 154, k4_ColorCyan, k0_ColorBlack, "RESTART THIS GAME");
+ break;
+ case Common::DE_DEU:
+ _textMan->printToLogicalScreen(110, 154, k4_ColorCyan, k0_ColorBlack, "DIESES SPIEL NEU STARTEN");
+ break;
+ case Common::FR_FRA:
+ _textMan->printToLogicalScreen(110, 154, k4_ColorCyan, k0_ColorBlack, "RECOMMENCER CE JEU");
+ break;
+ }
+
+ curPalette[1] = D03_RGB_PINK;
+ curPalette[4] = D09_RGB_WHITE;
+ _eventMan->_primaryMouseInput = _eventMan->_primaryMouseInputRestartGame;
+ _eventMan->discardAllInput();
+ _eventMan->hideMouse();
+ _displayMan->startEndFadeToPalette(curPalette);
+ for (int16 verticalBlankCount = 900; --verticalBlankCount && !_restartGameRequest; delay(1))
+ _eventMan->processCommandQueue();
+
+ _eventMan->showMouse();
+ if (_restartGameRequest) {
+ _displayMan->startEndFadeToPalette(darkBluePalette);
+ _displayMan->fillScreen(k0_ColorBlack);
+ _displayMan->startEndFadeToPalette(_displayMan->_palDungeonView[0]);
+ _newGameFl = k0_modeLoadSavedGame;
+ if (loadgame(1) != kM1_LoadgameFailure) {
+ startGame();
+ _restartGameRequest = false;
+ _eventMan->hideMouse();
+ _eventMan->discardAllInput();
+ return;
+ }
+ }
+ }
+
+ _displayMan->startEndFadeToPalette(darkBluePalette);
+ }
+ Box box(0, 319, 0, 199);
+ _displayMan->blitToScreen(_displayMan->getNativeBitmapOrGraphic(k5_creditsGraphicIndice), &box, k160_byteWidthScreen, kM1_ColorNoTransparency, k200_heightScreen);
+
+ _displayMan->startEndFadeToPalette(_displayMan->_palCredits);
+ _eventMan->waitForMouseOrKeyActivity();
+ if (_engineShouldQuit)
+ return;
+
+ if (_restartGameAllowed && doNotDrawCreditsOnly) {
+ waitBeforeDrawingRestart = false;
+ _displayMan->startEndFadeToPalette(darkBluePalette);
+ goto T0444017;
+ }
+
+ _engineShouldQuit = true;
+ return;
+}
+
+
+void DMEngine::drawEntrance() {
+ static Box doorsUpperHalfBox = Box(0, 231, 0, 80);
+ static Box doorsLowerHalfBox = Box(0, 231, 81, 160);
+ static Box closedDoorLeftBox = Box(0, 104, 30, 190);
+ static Box closedDoorRightBox = Box(105, 231, 30, 190);
+ /* Atari ST: { 0x000, 0x333, 0x444, 0x420, 0x654, 0x210, 0x040, 0x050, 0x432, 0x700, 0x543, 0x321, 0x222, 0x555, 0x310, 0x777 }, RGB colors are different */
+ static uint16 palEntrance[16] = {0x000, 0x666, 0x888, 0x840, 0xCA8, 0x0C0, 0x080, 0x0A0, 0x864, 0xF00, 0xA86, 0x642, 0x444, 0xAAA, 0x620, 0xFFF}; // @ G0020_aui_Graphic562_Palette_Entrance
+
+ byte *microDungeonCurrentMapData[32];
+
+ _dungeonMan->_partyMapIndex = k255_mapIndexEntrance;
+ _displayMan->_drawFloorAndCeilingRequested = true;
+ _dungeonMan->_currMapWidth = 5;
+ _dungeonMan->_currMapHeight = 5;
+ _dungeonMan->_currMapData = microDungeonCurrentMapData;
+
+ Map map; // uninitialized, won't be used
+ _dungeonMan->_currMap = &map;
+ Square microDungeonSquares[25];
+ for (uint16 i = 0; i < 25; ++i)
+ microDungeonSquares[i] = Square(k0_ElementTypeWall, 0);
+
+ for (int16 idx = 0; idx < 5; idx++) {
+ microDungeonCurrentMapData[idx] = (byte*)&microDungeonSquares[idx * 5];
+ microDungeonSquares[idx + 10] = Square(k1_CorridorElemType, 0);
+ }
+ microDungeonSquares[7] = Square(k1_CorridorElemType, 0);
+ _displayMan->startEndFadeToPalette(_displayMan->_blankBuffer);
+
+ // note, a global variable is used here in the original
+ _displayMan->loadIntoBitmap(k4_entranceGraphicIndice, _displayMan->_bitmapScreen);
+ _displayMan->drawDungeon(kDirSouth, 2, 0);
+
+ if (!_savedScreenForOpenEntranceDoors)
+ _savedScreenForOpenEntranceDoors = new byte[k200_heightScreen * k160_byteWidthScreen * 2];
+ memcpy(_savedScreenForOpenEntranceDoors, _displayMan->_bitmapScreen, 320 * 200);
+
+ _displayMan->_useByteBoxCoordinates = false, _displayMan->blitToBitmap(_displayMan->_bitmapScreen, _entranceDoorAnimSteps[8], doorsUpperHalfBox, 0, 30, k160_byteWidthScreen, k128_byteWidth, kM1_ColorNoTransparency, 200, 161);
+ _displayMan->_useByteBoxCoordinates = false, _displayMan->blitToBitmap(_displayMan->_bitmapScreen, _entranceDoorAnimSteps[8], doorsLowerHalfBox, 0, 111, k160_byteWidthScreen, k128_byteWidth, kM1_ColorNoTransparency, 200, 161);
+
+ _displayMan->blitToScreen(_entranceDoorAnimSteps[0], &closedDoorLeftBox, k64_byteWidth, kM1_ColorNoTransparency, 161);
+ _displayMan->blitToScreen(_entranceDoorAnimSteps[4], &closedDoorRightBox, k64_byteWidth, kM1_ColorNoTransparency, 161);
+ _displayMan->startEndFadeToPalette(palEntrance);
+}
+
+void DMEngine::openEntranceDoors() {
+ Box rightDoorBox(109, 231, 30, 193);
+ byte *rightDoorBitmap = _displayMan->getNativeBitmapOrGraphic(k3_entranceRightDoorGraphicIndice);
+ Box leftDoorBox(0, 100, 30, 193);
+ uint16 leftDoorBlitFrom = 0;
+ byte *leftDoorBitmap = _displayMan->getNativeBitmapOrGraphic(k2_entranceLeftDoorGraphicIndice);
+
+ Box screenBox(0, 319, 0, 199);
+
+ for (uint16 animStep = 1; animStep < 32; ++animStep) {
+ if ((animStep % 3) == 1) {
+ // Strangerke: CHECKME: Earlier versions of the game were using G0565_puc_Graphic535_Sound02DoorRattle instead of k02_soundDOOR_RATTLE 2
+ _sound->play(k02_soundDOOR_RATTLE, 145, 0x40, 0x40);
+ }
+
+ _displayMan->blitToScreen(_savedScreenForOpenEntranceDoors, &screenBox, 160, kM1_ColorNoTransparency, 200);
+ _displayMan->blitToBitmap(leftDoorBitmap, _displayMan->_bitmapScreen, leftDoorBox, leftDoorBlitFrom, 0, 64, k160_byteWidthScreen,
+ kM1_ColorNoTransparency, 161, k200_heightScreen);
+ _displayMan->blitToBitmap(rightDoorBitmap, _displayMan->_bitmapScreen, rightDoorBox, 0, 0, 64, k160_byteWidthScreen,
+ kM1_ColorNoTransparency, 161, k200_heightScreen);
+ _eventMan->discardAllInput();
+ _displayMan->updateScreen();
+
+ leftDoorBox._x2 -= 4;
+ leftDoorBlitFrom += 4;
+ rightDoorBox._x1 += 4;
+
+ delay(3);
+ }
+ delete[] _savedScreenForOpenEntranceDoors;
+ _savedScreenForOpenEntranceDoors = nullptr;
+}
+
+void DMEngine::drawTittle() {
+ static Box boxTitleStrikesBackDestination(0, 319, 118, 174);
+ static Box boxTitleStrikesBackSource(0, 319, 0, 56);
+ static Box boxTitlePresents(0, 319, 90, 105);
+ static Box boxTitleDungeonChaos(0, 319, 0, 79);
+
+ _displayMan->_useByteBoxCoordinates = false;
+
+ byte *allocatedMem = new byte[145600 * 2];
+ byte *titleSteps = allocatedMem;
+ byte *bitmapTitle = titleSteps;
+ _displayMan->loadIntoBitmap(k1_titleGraphicsIndice, titleSteps);
+
+ titleSteps += 320 * 200;
+ uint16 blitPalette[16];
+ for (uint16 i = 0; i < 16; ++i)
+ blitPalette[i] = D01_RGB_DARK_BLUE;
+
+ _displayMan->startEndFadeToPalette(blitPalette);
+ _displayMan->fillScreen(k0_ColorBlack);
+ // uncomment this to draw 'Presents'
+ //_displayMan->f132_blitToBitmap(L1384_puc_Bitmap_Title, _displayMan->_g348_bitmapScreen, G0005_s_Graphic562_Box_Title_Presents, 0, 137, k160_byteWidthScreen, k160_byteWidthScreen, kM1_ColorNoTransparency, k200_heightScreen, k200_heightScreen);
+ blitPalette[15] = D09_RGB_WHITE;
+ _displayMan->startEndFadeToPalette(blitPalette);
+ byte *masterStrikesBack = titleSteps;
+ _displayMan->blitToBitmap(bitmapTitle, masterStrikesBack, boxTitleStrikesBackSource, 0, 80, k160_byteWidthScreen, k160_byteWidthScreen, kM1_ColorNoTransparency, 200, 57);
+ titleSteps += 320 * 57;
+ byte *bitmapDungeonChaos = titleSteps; /* Unreferenced on Atari ST */
+ _displayMan->blitToBitmap(bitmapTitle, bitmapDungeonChaos, boxTitleDungeonChaos, 0, 0, k160_byteWidthScreen, k160_byteWidthScreen, kM1_ColorNoTransparency, 200, 80);
+ titleSteps += 320 * 80;
+ bitmapTitle = bitmapDungeonChaos;
+ uint16 destinationHeight = 12;
+ int16 destinationPixelWidth = 48;
+ byte *shrinkedTitle[20]; /* Only the first 18 entries are actually used */
+ int16 blitCoordinates[20][5]; /* Only the first 18 entries are actually used */
+ for (int16 i = 0; i < 18; i++) {
+ shrinkedTitle[i] = titleSteps;
+ _displayMan->blitToBitmapShrinkWithPalChange(bitmapTitle, titleSteps, 320, 80, destinationPixelWidth, destinationHeight, _displayMan->_palChangesNoChanges);
+ blitCoordinates[i][0] = (320 - destinationPixelWidth) / 2;
+ blitCoordinates[i][1] = blitCoordinates[i][0] + destinationPixelWidth - 1;
+ blitCoordinates[i][2] = (160 - destinationHeight) / 2;
+ blitCoordinates[i][3] = blitCoordinates[i][2] + destinationHeight - 1;
+ titleSteps += (blitCoordinates[i][4] = ((destinationPixelWidth + 15) / 16) * 8) * destinationHeight * 2;
+ destinationHeight += 4;
+ destinationPixelWidth += 16;
+ }
+ blitPalette[15] = D01_RGB_DARK_BLUE;
+ _displayMan->startEndFadeToPalette(blitPalette);
+ _displayMan->fillScreen(k0_ColorBlack);
+ blitPalette[3] = D05_RGB_DARK_GOLD;
+ blitPalette[4] = D02_RGB_LIGHT_BROWN;
+ blitPalette[5] = D06_RGB_GOLD;
+ blitPalette[6] = D04_RGB_LIGHTER_BROWN;
+ blitPalette[8] = D08_RGB_YELLOW;
+ blitPalette[15] = D07_RGB_RED;
+ blitPalette[10] = D01_RGB_DARK_BLUE;
+ blitPalette[12] = D01_RGB_DARK_BLUE;
+ _displayMan->startEndFadeToPalette(blitPalette);
+ delay(1);
+ for (int16 i = 0; i < 18; i++) {
+ delay(2);
+ Box box(blitCoordinates[i]);
+ _displayMan->blitToBitmap(shrinkedTitle[i], _displayMan->_bitmapScreen, box, 0, 0, blitCoordinates[i][4], k160_byteWidthScreen, kM1_ColorNoTransparency, blitCoordinates[i][3] - blitCoordinates[i][2] + 1, k200_heightScreen);
+ }
+ delay(25);
+ _displayMan->blitToBitmap(masterStrikesBack, _displayMan->_bitmapScreen, boxTitleStrikesBackDestination, 0, 0, k160_byteWidthScreen, k160_byteWidthScreen, k0_ColorBlack, 57, k200_heightScreen);
+ blitPalette[10] = D00_RGB_BLACK;
+ blitPalette[12] = D07_RGB_RED;
+ _displayMan->startEndFadeToPalette(blitPalette);
+ delete[] allocatedMem;
+ delay(75);
+}
+
+void DMEngine::entranceDrawCredits() {
+ _eventMan->showMouse();
+ _displayMan->startEndFadeToPalette(_displayMan->_blankBuffer);
+ _displayMan->loadIntoBitmap(k5_creditsGraphicIndice, _displayMan->_bitmapScreen);
+ _displayMan->startEndFadeToPalette(_displayMan->_palCredits);
+ delay(50);
+ _eventMan->waitForMouseOrKeyActivity();
+ _newGameFl = k202_modeEntranceDrawCredits;
+}
+
+void DMEngine::fuseSequence() {
+ _gameWon = true;
+ if (_inventoryMan->_inventoryChampionOrdinal)
+ _inventoryMan->toggleInventory(kDMChampionCloseInventory);
+
+ _eventMan->highlightBoxDisable();
+ _championMan->_party._magicalLightAmount = 200;
+ _inventoryMan->setDungeonViewPalette();
+ _championMan->_party._fireShieldDefense = _championMan->_party._spellShieldDefense = _championMan->_party._shieldDefense = 100;
+ _timeline->refreshAllChampionStatusBoxes();
+ fuseSequenceUpdate();
+ int16 lordChaosMapX = _dungeonMan->_partyMapX;
+ int16 lordChaosMapY = _dungeonMan->_partyMapY;
+ lordChaosMapX += _dirIntoStepCountEast[_dungeonMan->_partyDir], lordChaosMapY += _dirIntoStepCountNorth[_dungeonMan->_partyDir];
+ Thing lordChaosThing = _groupMan->groupGetThing(lordChaosMapX, lordChaosMapY);
+ Group *lordGroup = (Group*)_dungeonMan->getThingData(lordChaosThing);
+ lordGroup->_health[0] = 10000;
+ _dungeonMan->setGroupCells(lordGroup, k255_CreatureTypeSingleCenteredCreature, _dungeonMan->_partyMapIndex);
+ _dungeonMan->setGroupDirections(lordGroup, returnOppositeDir(_dungeonMan->_partyDir), _dungeonMan->_partyMapIndex);
+
+ bool removeFluxcagesFromLordChaosSquare = true;
+ int16 fluxCageMapX = _dungeonMan->_partyMapX;
+ int16 fluxcageMapY = _dungeonMan->_partyMapY;
+
+ for (;;) {
+ Thing curThing = _dungeonMan->getSquareFirstObject(fluxCageMapX, fluxcageMapY);
+ while (curThing != Thing::_endOfList) {
+ if (curThing.getType() == k15_ExplosionThingType) {
+ Explosion *curExplosion = (Explosion*)_dungeonMan->getThingData(curThing);
+ if (curExplosion->getType() == k50_ExplosionType_Fluxcage) {
+ _dungeonMan->unlinkThingFromList(curThing, Thing(0), fluxCageMapX, fluxcageMapY);
+ curExplosion->setNextThing(Thing::_none);
+ continue;
+ }
+ }
+ curThing = _dungeonMan->getNextThing(curThing);
+ }
+ if (removeFluxcagesFromLordChaosSquare) {
+ removeFluxcagesFromLordChaosSquare = false;
+ fluxCageMapX = lordChaosMapX;
+ fluxcageMapY = lordChaosMapY;
+ } else
+ break;
+ }
+ fuseSequenceUpdate();
+ for (int16 attackId = 55; attackId <= 255; attackId += 40) {
+ _projexpl->createExplosion(Thing::_explFireBall, attackId, lordChaosMapX, lordChaosMapY, k255_CreatureTypeSingleCenteredCreature);
+ fuseSequenceUpdate();
+ }
+ _sound->requestPlay(k17_soundBUZZ, lordChaosMapX, lordChaosMapY, k1_soundModePlayIfPrioritized);
+ lordGroup->_type = k25_CreatureTypeLordOrder;
+ fuseSequenceUpdate();
+ for (int16 attackId = 55; attackId <= 255; attackId += 40) {
+ _projexpl->createExplosion(Thing::_explHarmNonMaterial, attackId, lordChaosMapX, lordChaosMapY, k255_CreatureTypeSingleCenteredCreature);
+ fuseSequenceUpdate();
+ }
+ for (int16 cycleCount = 3; cycleCount > 0; cycleCount--) {
+ for (int16 switchCount = 4; switchCount > 0; switchCount--) {
+ _sound->requestPlay(k17_soundBUZZ, lordChaosMapX, lordChaosMapY, k1_soundModePlayIfPrioritized);
+ lordGroup->_type = (switchCount & 0x0001) ? k25_CreatureTypeLordOrder : k23_CreatureTypeLordChaos;
+ for (int16 fuseSequenceUpdateCount = cycleCount - 1; fuseSequenceUpdateCount >= 0; fuseSequenceUpdateCount--)
+ fuseSequenceUpdate();
+ }
+ }
+ _projexpl->createExplosion(Thing::_explFireBall, 255, lordChaosMapX, lordChaosMapY, k255_CreatureTypeSingleCenteredCreature);
+ _projexpl->createExplosion(Thing::_explHarmNonMaterial, 255, lordChaosMapX, lordChaosMapY, k255_CreatureTypeSingleCenteredCreature);
+ fuseSequenceUpdate();
+ lordGroup->_type = k26_CreatureTypeGreyLord;
+ fuseSequenceUpdate();
+ _displayMan->_doNotDrawFluxcagesDuringEndgame = true;
+ fuseSequenceUpdate();
+ for (int16 curMapX = 0; curMapX < _dungeonMan->_currMapWidth; curMapX++) {
+ for (int curMapY = 0; curMapY < _dungeonMan->_currMapHeight; curMapY++) {
+ Thing curThing = _groupMan->groupGetThing(curMapX, curMapY);
+ if ((curThing != Thing::_endOfList) && ((curMapX != lordChaosMapX) || (curMapY != lordChaosMapY))) {
+ _groupMan->groupDelete(curMapX, curMapY);
+ }
+ }
+ }
+ fuseSequenceUpdate();
+ /* Count and get list of text things located at 0, 0 in the current map. Their text is then printed as messages in the order specified by their first letter (which is not printed) */
+ Thing curThing = _dungeonMan->getSquareFirstThing(0, 0);
+ int16 textStringThingCount = 0;
+ Thing textStringThings[8];
+ while (curThing != Thing::_endOfList) {
+ if (curThing.getType() == k2_TextstringType)
+ textStringThings[textStringThingCount++] = curThing;
+
+ curThing = _dungeonMan->getNextThing(curThing);
+ }
+ char textFirstChar = 'A';
+ int16 maxCount = textStringThingCount;
+ while (textStringThingCount--) {
+ for (int16 idx = 0; idx < maxCount; idx++) {
+ char decodedString[200];
+ _dungeonMan->decodeText(decodedString, textStringThings[idx], (TextType)(k1_TextTypeMessage | k0x8000_DecodeEvenIfInvisible));
+ if (decodedString[1] == textFirstChar) {
+ _textMan->clearAllRows();
+ decodedString[1] = '\n'; /* New line */
+ _textMan->printMessage(k15_ColorWhite, &decodedString[1]);
+ fuseSequenceUpdate();
+ delay(780);
+ textFirstChar++;
+ break;
+ }
+ }
+ }
+
+ for (int16 attackId = 55; attackId <= 255; attackId += 40) {
+ _projexpl->createExplosion(Thing::_explHarmNonMaterial, attackId, lordChaosMapX, lordChaosMapY, k255_CreatureTypeSingleCenteredCreature);
+ fuseSequenceUpdate();
+ }
+
+ delay(600);
+ _restartGameAllowed = false;
+ endGame(true);
+}
+
+void DMEngine::fuseSequenceUpdate() {
+ _timeline->processTimeline();
+ _displayMan->drawDungeon(_dungeonMan->_partyDir, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY);
+ _sound->playPendingSound();
+ _eventMan->discardAllInput();
+ _displayMan->updateScreen();
+ delay(2);
+ _gameTime++; /* BUG0_71 Some timings are too short on fast computers.
+ The ending animation when Lord Chaos is fused plays too quickly because the execution speed is not limited */
+}
+
+Common::Language DMEngine::getGameLanguage() {
+ return _gameVersion->_desc.language;
+}
+
+} // End of namespace DM
diff --git a/engines/dm/dm.h b/engines/dm/dm.h
new file mode 100644
index 0000000000..6127da0038
--- /dev/null
+++ b/engines/dm/dm.h
@@ -0,0 +1,331 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_DM_H
+#define DM_DM_H
+
+#include "engines/engine.h"
+#include "engines/savestate.h"
+
+#include "common/random.h"
+#include "common/savefile.h"
+#include "common/str.h"
+#include "common/memstream.h"
+
+#include "advancedDetector.h"
+
+#include "dm/console.h"
+
+struct ADGameDescription;
+
+namespace DM {
+
+class DisplayMan;
+class DungeonMan;
+class EventManager;
+class MenuMan;
+class ChampionMan;
+class ObjectMan;
+class InventoryMan;
+class TextMan;
+class MovesensMan;
+class GroupMan;
+class Timeline;
+class ProjExpl;
+class DialogMan;
+class SoundMan;
+
+enum OriginalSaveFormat {
+ k_saveFormat_accept_any = -1,
+ k_saveFormat_endOfList = 0,
+ k_saveFormat_none = 0,
+ k_saveFormat_dm_atari_st = 1,
+ k_saveFormat_dm_amiga__2_x_pc98_x68000_fm_towns_csb_atari_st = 2,
+ k_saveFormat_dm_apple_iigs = 3,
+ k_saveFormat_dm_amiga_36_pc_csb_amiga_pc98_x68000_fm_towns = 5,
+ k_saveFormat_total
+};
+
+enum OriginalSavePlatform {
+ k_savePlatform_accept_any = -1,
+ k_savePlatform_endOfList = 0,
+ k_savePlatform_none = 0,
+ k_savePlatform_atari_st = 1, // @ C1_PLATFORM_ATARI_ST
+ k_savePlatform_apple_iigs = 2, // @ C2_PLATFORM_APPLE_IIGS
+ k_savePlatform_amiga = 3, // @ C3_PLATFORM_AMIGA
+ k_savePlatform_pc98 = 5, // @ C5_PLATFORM_PC98
+ k_savePlatform_x68000 = 6, // @ C6_PLATFORM_X68000
+ k_savePlatform_fm_towns_en = 7, // @ C7_PLATFORM_FM_TOWNS_EN
+ k_savePlatform_fm_towns_jp = 8, // @ C8_PLATFORM_FM_TOWNS_JP
+ k_savePlatform_pc = 9, // @ C9_PLATFORM_PC
+ k_savePlatform_total
+};
+
+enum SaveTarget {
+ k_saveTarget_accept_any = -1,
+ k_saveTarget_endOfList = 0,
+ k_saveTarget_none = 0,
+ k_saveTarget_DM21 = 1,
+ k_saveTarget_total
+};
+
+struct DMADGameDescription {
+ ADGameDescription _desc;
+
+ SaveTarget _saveTargetToWrite;
+ OriginalSaveFormat _origSaveFormatToWrite;
+ OriginalSavePlatform _origPlatformToWrite;
+
+ SaveTarget _saveTargetToAccept[k_saveTarget_total + 1];
+ OriginalSaveFormat _saveFormatToAccept[k_saveFormat_total + 1];
+ OriginalSavePlatform _origPlatformToAccept[k_savePlatform_total + 1];
+};
+
+enum Direction {
+ kDirNorth = 0,
+ kDirEast = 1,
+ kDirSouth = 2,
+ kDirWest = 3
+};
+
+const char *debugGetDirectionName(Direction dir);
+
+enum ThingType {
+ kM1_PartyThingType = -1, // @ CM1_THING_TYPE_PARTY
+ k0_DoorThingType = 0, // @ C00_THING_TYPE_DOOR
+ k1_TeleporterThingType = 1, // @ C01_THING_TYPE_TELEPORTER
+ k2_TextstringType = 2, // @ C02_THING_TYPE_TEXTSTRING
+ k3_SensorThingType = 3, // @ C03_THING_TYPE_SENSOR
+ k4_GroupThingType = 4, // @ C04_THING_TYPE_GROUP
+ k5_WeaponThingType = 5, // @ C05_THING_TYPE_WEAPON
+ k6_ArmourThingType = 6, // @ C06_THING_TYPE_ARMOUR
+ k7_ScrollThingType = 7, // @ C07_THING_TYPE_SCROLL
+ k8_PotionThingType = 8, // @ C08_THING_TYPE_POTION
+ k9_ContainerThingType = 9, // @ C09_THING_TYPE_CONTAINER
+ k10_JunkThingType = 10, // @ C10_THING_TYPE_JUNK
+ k14_ProjectileThingType = 14, // @ C14_THING_TYPE_PROJECTILE
+ k15_ExplosionThingType = 15, // @ C15_THING_TYPE_EXPLOSION
+ k16_ThingTypeTotal = 16 // +1 than the last (explosionThingType)
+}; // @ C[00..15]_THING_TYPE_...
+
+
+class Thing {
+public:
+ uint16 _data;
+ static const Thing _none; // @ C0xFFFF_THING_NONE
+ static const Thing _endOfList; // @ C0xFFFE_THING_ENDOFLIST
+ static const Thing _firstExplosion; // @ C0xFF80_THING_FIRST_EXPLOSION
+ static const Thing _explFireBall; // @ C0xFF80_THING_EXPLOSION_FIREBALL
+ static const Thing _explSlime; // @ C0xFF81_THING_EXPLOSION_SLIME
+ static const Thing _explLightningBolt; // @ C0xFF82_THING_EXPLOSION_LIGHTNING_BOLT
+ static const Thing _explHarmNonMaterial; // @ C0xFF83_THING_EXPLOSION_HARM_NON_MATERIAL
+ static const Thing _explOpenDoor; // @ C0xFF84_THING_EXPLOSION_OPEN_DOOR
+ static const Thing _explPoisonBolt; // @ C0xFF86_THING_EXPLOSION_POISON_BOLT
+ static const Thing _explPoisonCloud; // @ C0xFF87_THING_EXPLOSION_POISON_CLOUD
+ static const Thing _explSmoke; // @ C0xFFA8_THING_EXPLOSION_SMOKE
+ static const Thing _explFluxcage; // @ C0xFFB2_THING_EXPLOSION_FLUXCAGE
+ static const Thing _explRebirthStep1; // @ C0xFFE4_THING_EXPLOSION_REBIRTH_STEP1
+ static const Thing _explRebirthStep2; // @ C0xFFE5_THING_EXPLOSION_REBIRTH_STEP2
+ static const Thing _party; // @ C0xFFFF_THING_PARTY
+
+ Thing() : _data(0) {}
+ Thing(const Thing &other) { set(other._data); }
+ explicit Thing(uint16 d) { set(d); }
+
+ void set(uint16 d) {
+ _data = d;
+ }
+
+ byte getCell() const { return _data >> 14; }
+ ThingType getType() const { return (ThingType)((_data >> 10) & 0xF); }
+ uint16 getIndex() const { return _data & 0x3FF; }
+
+ void setCell(uint16 cell) { _data = (_data & ~(0x3 << 14)) | ((cell & 0x3) << 14); }
+ void setType(uint16 type) { _data = (_data & ~(0xF << 10)) | ((type & 0xF) << 10); }
+ void setIndex(uint16 index) { _data = (_data & ~0x3FF) | (index & 0x3FF); }
+
+ uint16 getTypeAndIndex() { return _data & 0x3FFF; }
+ uint16 toUint16() const { return _data; } // I don't like 'em cast operators
+ bool operator==(const Thing &rhs) const { return _data == rhs._data; }
+ bool operator!=(const Thing &rhs) const { return _data != rhs._data; }
+}; // @ THING
+
+void turnDirRight(Direction &dir);
+void turnDirLeft(Direction &dir);
+Direction returnOppositeDir(Direction dir); // @ M18_OPPOSITE
+uint16 returnPrevVal(uint16 val); // @ M19_PREVIOUS
+uint16 returnNextVal(uint16 val); // @ M17_NEXT
+bool isOrientedWestEast(Direction dir); // @ M16_IS_ORIENTED_WEST_EAST
+
+#define setFlag(val, mask) ((val) |= (mask))
+#define getFlag(val, mask) ((val) & (mask))
+#define clearFlag(val, mask) ((val) &= (~(mask))) // @ M09_CLEAR
+
+uint16 toggleFlag(uint16 &val, uint16 mask); // @ M10_TOGGLE
+uint16 bitmapByteCount(uint16 pixelWidth, uint16 height); // @ M75_BITMAP_BYTE_COUNT
+uint16 normalizeModulo4(uint16 val); // @ M21_NORMALIZE
+int32 filterTime(int32 map_time); // @ M30_TIME
+int32 setMapAndTime(int32 &map_time, uint32 map, uint32 time); // @ M33_SET_MAP_AND_TIME
+uint16 getMap(int32 map_time); // @ M29_MAP
+Thing thingWithNewCell(Thing thing, int16 cell); // @ M15_THING_WITH_NEW_CELL
+int16 getDistance(int16 mapx1, int16 mapy1, int16 mapx2, int16 mapy2);// @ M38_DISTANCE
+
+enum Cell {
+ kM1_CellAny = -1, // @ CM1_CELL_ANY
+ k0_CellNorthWest = 0, // @ C00_CELL_NORTHWEST
+ k1_CellNorthEast = 1, // @ C01_CELL_NORTHEAST
+ k2_CellSouthEast = 2, // @ C02_CELL_SOUTHEAST
+ k3_CellSouthWest = 3 // @ C03_CELL_SOUTHWEST
+};
+
+#define kM1_mapIndexNone -1 // @ CM1_MAP_INDEX_NONE
+#define k255_mapIndexEntrance 255 // @ C255_MAP_INDEX_ENTRANCE
+
+//TODO: Directly use CLIP
+template<typename T>
+inline T getBoundedValue(T min, T val, T max) {
+ return CLIP<T>(min, val, max);
+} // @ F0026_MAIN_GetBoundedValue
+
+#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))
+
+#define k0_modeLoadSavedGame 0 // @ C000_MODE_LOAD_SAVED_GAME
+#define k1_modeLoadDungeon 1 // @ C001_MODE_LOAD_DUNGEON
+#define k99_modeWaitingOnEntrance 99 // @ C099_MODE_WAITING_ON_ENTRANCE
+#define k202_modeEntranceDrawCredits 202 // @ C202_MODE_ENTRANCE_DRAW_CREDITS
+
+enum LoadgameResponse {
+ kM1_LoadgameFailure = -1, // @ CM1_LOAD_GAME_FAILURE
+ k1_LoadgameSuccess = 1// @ C01_LOAD_GAME_SUCCESS
+};
+
+struct SaveGameHeader {
+ byte _version;
+ SaveStateDescriptor _descr;
+};
+
+class DMEngine : public Engine {
+ void startGame(); // @ F0462_START_StartGame_CPSF
+ void processNewPartyMap(uint16 mapIndex); // @ F0003_MAIN_ProcessNewPartyMap_CPSE
+ void initializeGame(); // @ F0463_START_InitializeGame_CPSADEF
+ void initMemoryManager(); // @ F0448_STARTUP1_InitializeMemoryManager_CPSADEF
+ void gameloop(); // @ F0002_MAIN_GameLoop_CPSDF
+ void initConstants();
+ Common::String getSavefileName(uint16 slot);
+ void writeSaveGameHeader(Common::OutSaveFile *out, const Common::String &saveName);
+ bool writeCompleteSaveFile(int16 slot, Common::String &desc, int16 saveAndPlayChoice);
+ void drawEntrance(); // @ F0439_STARTEND_DrawEntrance
+public:
+ explicit DMEngine(OSystem *syst, const DMADGameDescription *gameDesc);
+ ~DMEngine();
+ virtual bool hasFeature(EngineFeature f) const;
+
+ virtual Common::Error loadGameState(int slot);
+ virtual bool canLoadGameStateCurrently();
+
+ GUI::Debugger *getDebugger() { return _console; }
+
+ void delay(uint16 verticalBlank); // @ F0022_MAIN_Delay
+ uint16 getScaledProduct(uint16 val, uint16 scale, uint16 vale2); // @ F0030_MAIN_GetScaledProduct
+ uint16 getRandomNumber(uint32 max) { return _rnd->getRandomNumber(max - 1); }
+ int16 ordinalToIndex(int16 val); // @ M01_ORDINAL_TO_INDEX
+ int16 indexToOrdinal(int16 val); // @ M00_INDEX_TO_ORDINAL
+ virtual Common::Error run(); // @ main
+ void saveGame(); // @ F0433_STARTEND_ProcessCommand140_SaveGame_CPSCDF
+ LoadgameResponse loadgame(int16 slot); // @ F0435_STARTEND_LoadGame_CPSF
+ void processEntrance(); // @ F0441_STARTEND_ProcessEntrance
+ void endGame(bool doNotDrawCreditsOnly); // @ F0444_STARTEND_Endgame
+
+ void openEntranceDoors(); // @ F0438_STARTEND_OpenEntranceDoors
+ void drawTittle(); // @ F0437_STARTEND_DrawTitle
+ void entranceDrawCredits();
+ void fuseSequence(); // @ F0446_STARTEND_FuseSequence
+ void fuseSequenceUpdate(); // @ F0445_STARTEND_FuseSequenceUpdate
+ Common::Language getGameLanguage();
+
+private:
+ uint16 _dungeonId; // @ G0526_ui_DungeonID
+ byte *_entranceDoorAnimSteps[10]; // @ G0562_apuc_Bitmap_EntranceDoorAnimationSteps
+ byte *_interfaceCredits; // @ G0564_puc_Graphic5_InterfaceCredits
+ Common::RandomSource *_rnd;
+
+ byte *_savedScreenForOpenEntranceDoors; // ad-hoc HACK
+ const DMADGameDescription *_gameVersion;
+ bool _canLoadFromGMM;
+public:
+ Console *_console;
+ DisplayMan *_displayMan;
+ DungeonMan *_dungeonMan;
+ EventManager *_eventMan;
+ MenuMan *_menuMan;
+ ChampionMan *_championMan;
+ ObjectMan *_objectMan;
+ InventoryMan *_inventoryMan;
+ TextMan *_textMan;
+ MovesensMan *_moveSens;
+ GroupMan *_groupMan;
+ Timeline *_timeline;
+ ProjExpl *_projexpl;
+ DialogMan *_dialog;
+ SoundMan *_sound;
+
+ Common::MemoryWriteStreamDynamic *_saveThumbnail;
+
+ bool _engineShouldQuit;
+ int _loadSaveSlotAtRuntime;
+
+ int16 _newGameFl; // @ G0298_B_NewGame
+ bool _restartGameRequest; // @ G0523_B_RestartGameRequested
+
+ bool _stopWaitingForPlayerInput; // @ G0321_B_StopWaitingForPlayerInput
+ bool _gameTimeTicking; // @ G0301_B_GameTimeTicking
+ bool _restartGameAllowed; // @ G0524_B_RestartGameAllowed
+ int32 _gameId; // @ G0525_l_GameID, probably useless here
+ bool _pressingEye; // @ G0331_B_PressingEye
+ bool _stopPressingEye; // @ G0332_B_StopPressingEye
+ bool _pressingMouth; // @ G0333_B_PressingMouth
+ bool _stopPressingMouth; // @ G0334_B_StopPressingMouth
+ bool _highlightBoxInversionRequested; // @ G0340_B_HighlightBoxInversionRequested
+ int16 _projectileDisableMovementTicks; // @ G0311_i_ProjectileDisabledMovementTicks
+ int16 _lastProjectileDisabledMovementDirection; // @ G0312_i_LastProjectileDisabledMovementDirection
+ bool _gameWon; // @ G0302_B_GameWon
+ int16 _newPartyMapIndex; // @ G0327_i_NewPartyMapIndex
+ bool _setMousePointerToObjectInMainLoop; // @ G0325_B_SetMousePointerToObjectInMainLoop
+ int16 _disabledMovementTicks; // @ G0310_i_DisabledMovementTicks
+
+ int8 _dirIntoStepCountEast[4]; // @ G0233_ai_Graphic559_DirectionToStepEastCount
+ int8 _dirIntoStepCountNorth[4]; // @ G0234_ai_Graphic559_DirectionToStepNorthCount
+ int32 _gameTime; // @ G0313_ul_GameTime
+ char _stringBuildBuffer[128]; // @ G0353_ac_StringBuildBuffer
+ int16 _waitForInputMaxVerticalBlankCount; // @ G0318_i_WaitForInputMaximumVerticalBlankCount
+};
+
+bool readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader *header);
+
+} // End of namespace DM
+
+#endif
diff --git a/engines/dm/dmglobals.cpp b/engines/dm/dmglobals.cpp
new file mode 100644
index 0000000000..993f112b02
--- /dev/null
+++ b/engines/dm/dmglobals.cpp
@@ -0,0 +1,58 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "common/system.h"
+
+#include "dm/dm.h"
+#include "dm/gfx.h"
+#include "dm/dungeonman.h"
+#include "dm/eventman.h"
+#include "dm/menus.h"
+#include "dm/champion.h"
+#include "dm/loadsave.h"
+#include "dm/objectman.h"
+#include "dm/inventory.h"
+#include "dm/text.h"
+#include "dm/movesens.h"
+
+namespace DM {
+
+void DMEngine::initConstants() {
+ // G0233_ai_Graphic559_DirectionToStepEastCount
+ _dirIntoStepCountEast[0] = 0; // North
+ _dirIntoStepCountEast[1] = 1; // East
+ _dirIntoStepCountEast[2] = 0; // West
+ _dirIntoStepCountEast[3] = -1; // South
+
+ // G0234_ai_Graphic559_DirectionToStepNorthCount
+ _dirIntoStepCountNorth[0] = -1; // North
+ _dirIntoStepCountNorth[1] = 0; // East
+ _dirIntoStepCountNorth[2] = 1; // West
+ _dirIntoStepCountNorth[3] = 0; // South
+}
+
+} // End of namespace DM
diff --git a/engines/dm/dungeonman.cpp b/engines/dm/dungeonman.cpp
new file mode 100644
index 0000000000..5527e1e9c1
--- /dev/null
+++ b/engines/dm/dungeonman.cpp
@@ -0,0 +1,1672 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "common/file.h"
+#include "common/memstream.h"
+
+#include "dm/dungeonman.h"
+#include "dm/timeline.h"
+#include "dm/champion.h"
+#include "dm/group.h"
+#include "dm/movesens.h"
+#include "dm/projexpl.h"
+
+namespace DM {
+
+void DungeonMan::mapCoordsAfterRelMovement(Direction dir, int16 stepsForward, int16 stepsRight, int16 &posX, int16 &posY) {
+ posX += _vm->_dirIntoStepCountEast[dir] * stepsForward;
+ posY += _vm->_dirIntoStepCountNorth[dir] * stepsForward;
+ turnDirRight(dir);
+ posX += _vm->_dirIntoStepCountEast[dir] * stepsRight;
+ posY += _vm->_dirIntoStepCountNorth[dir] * stepsRight;
+}
+
+void DungeonMan::setupConstants() {
+ ObjectInfo objectInfo[180] = { // @ G0237_as_Graphic559_ObjectInfo
+ /* { Type, ObjectAspectIndex, ActionSetIndex, AllowedSlots } */
+ ObjectInfo(30, 1, 0, 0x0500), /* COMPASS Pouch/Chest */
+ ObjectInfo(144, 0, 0, 0x0200), /* COMPASS Hands */
+ ObjectInfo(148, 67, 0, 0x0500), /* COMPASS Pouch/Chest */
+ ObjectInfo(149, 67, 0, 0x0500), /* COMPASS Pouch/Chest */
+ ObjectInfo(150, 67, 0, 0x0500), /* TORCH Pouch/Chest */
+ ObjectInfo(151, 67, 42, 0x0500), /* TORCH Pouch/Chest */
+ ObjectInfo(152, 67, 0, 0x0500), /* TORCH Pouch/Chest */
+ ObjectInfo(153, 67, 0, 0x0500), /* TORCH Pouch/Chest */
+ ObjectInfo(154, 2, 0, 0x0501), /* WATERSKIN Mouth/Pouch/Chest */
+ ObjectInfo(155, 2, 0, 0x0501), /* WATER Mouth/Pouch/Chest */
+ ObjectInfo(156, 2, 0, 0x0501), /* JEWEL SYMAL Mouth/Pouch/Chest */
+ ObjectInfo(157, 2, 0, 0x0501), /* JEWEL SYMAL Mouth/Pouch/Chest */
+ ObjectInfo(158, 2, 0, 0x0501), /* ILLUMULET Mouth/Pouch/Chest */
+ ObjectInfo(159, 2, 0, 0x0501), /* ILLUMULET Mouth/Pouch/Chest */
+ ObjectInfo(160, 2, 0, 0x0501), /* FLAMITT Mouth/Pouch/Chest */
+ ObjectInfo(161, 2, 0, 0x0501), /* FLAMITT Mouth/Pouch/Chest */
+ ObjectInfo(162, 2, 0, 0x0501), /* EYE OF TIME Mouth/Pouch/Chest */
+ ObjectInfo(163, 2, 0, 0x0501), /* EYE OF TIME Mouth/Pouch/Chest */
+ ObjectInfo(164, 68, 0, 0x0500), /* STORMRING Pouch/Chest */
+ ObjectInfo(165, 68, 0, 0x0500), /* STORMRING Pouch/Chest */
+ ObjectInfo(166, 68, 0, 0x0500), /* STAFF OF CLAWS Pouch/Chest */
+ ObjectInfo(167, 68, 42, 0x0500), /* STAFF OF CLAWS Pouch/Chest */
+ ObjectInfo(195, 80, 0, 0x0500), /* STAFF OF CLAWS Pouch/Chest */
+ ObjectInfo(16, 38, 43, 0x0500), /* BOLT BLADE Pouch/Chest */
+ ObjectInfo(18, 38, 7, 0x0500), /* BOLT BLADE Pouch/Chest */
+ ObjectInfo(4, 35, 5, 0x0400), /* FURY Chest */
+ ObjectInfo(14, 37, 6, 0x0400), /* FURY Chest */
+ ObjectInfo(20, 11, 8, 0x0040), /* THE FIRESTAFF Quiver 1 */
+ ObjectInfo(23, 12, 9, 0x0040), /* THE FIRESTAFF Quiver 1 */
+ ObjectInfo(25, 12, 10, 0x0040), /* THE FIRESTAFF Quiver 1 */
+ ObjectInfo(27, 39, 11, 0x0040), /* OPEN SCROLL Quiver 1 */
+ ObjectInfo(32, 17, 12, 0x05C0), /* SCROLL Quiver 1/Quiver 2/Pouch/Chest */
+ ObjectInfo(33, 12, 13, 0x0040), /* DAGGER Quiver 1 */
+ ObjectInfo(34, 12, 13, 0x0040), /* FALCHION Quiver 1 */
+ ObjectInfo(35, 12, 14, 0x0040), /* SWORD Quiver 1 */
+ ObjectInfo(36, 12, 15, 0x0040), /* RAPIER Quiver 1 */
+ ObjectInfo(37, 12, 15, 0x0040), /* SABRE Quiver 1 */
+ ObjectInfo(38, 12, 16, 0x0040), /* SAMURAI SWORD Quiver 1 */
+ ObjectInfo(39, 12, 17, 0x0040), /* DELTA Quiver 1 */
+ ObjectInfo(40, 42, 18, 0x0040), /* DIAMOND EDGE Quiver 1 */
+ ObjectInfo(41, 12, 19, 0x0040), /* VORPAL BLADE Quiver 1 */
+ ObjectInfo(42, 13, 20, 0x0040), /* THE INQUISITOR Quiver 1 */
+ ObjectInfo(43, 13, 21, 0x0040), /* AXE Quiver 1 */
+ ObjectInfo(44, 21, 22, 0x0040), /* HARDCLEAVE Quiver 1 */
+ ObjectInfo(45, 21, 22, 0x0040), /* MACE Quiver 1 */
+ ObjectInfo(46, 33, 23, 0x0440), /* MACE OF ORDER Quiver 1/Chest */
+ ObjectInfo(47, 43, 24, 0x0040), /* MORNINGSTAR Quiver 1 */
+ ObjectInfo(48, 44, 24, 0x0040), /* CLUB Quiver 1 */
+ ObjectInfo(49, 14, 27, 0x0040), /* STONE CLUB Quiver 1 */
+ ObjectInfo(50, 45, 27, 0x0040), /* BOW Quiver 1 */
+ ObjectInfo(51, 16, 26, 0x05C0), /* CROSSBOW Quiver 1/Quiver 2/Pouch/Chest */
+ ObjectInfo(52, 46, 26, 0x05C0), /* ARROW Quiver 1/Quiver 2/Pouch/Chest */
+ ObjectInfo(53, 11, 27, 0x0440), /* SLAYER Quiver 1/Chest */
+ ObjectInfo(54, 47, 42, 0x05C0), /* SLING Quiver 1/Quiver 2/Pouch/Chest */
+ ObjectInfo(55, 48, 40, 0x05C0), /* ROCK Quiver 1/Quiver 2/Pouch/Chest */
+ ObjectInfo(56, 49, 42, 0x05C0), /* POISON DART Quiver 1/Quiver 2/Pouch/Chest */
+ ObjectInfo(57, 50, 5, 0x0040), /* THROWING STAR Quiver 1 */
+ ObjectInfo(58, 11, 5, 0x0040), /* STICK Quiver 1 */
+ ObjectInfo(59, 31, 28, 0x0540), /* STAFF Quiver 1/Pouch/Chest */
+ ObjectInfo(60, 31, 29, 0x0540), /* WAND Quiver 1/Pouch/Chest */
+ ObjectInfo(61, 11, 30, 0x0040), /* TEOWAND Quiver 1 */
+ ObjectInfo(62, 11, 31, 0x0040), /* YEW STAFF Quiver 1 */
+ ObjectInfo(63, 11, 32, 0x0040), /* STAFF OF MANAR Quiver 1 Atari ST Version 1.0 1987-12-08: ObjectAspectIndex = 35 */
+ ObjectInfo(64, 51, 33, 0x0040), /* SNAKE STAFF Quiver 1 */
+ ObjectInfo(65, 32, 5, 0x0440), /* THE CONDUIT Quiver 1/Chest */
+ ObjectInfo(66, 30, 35, 0x0040), /* DRAGON SPIT Quiver 1 */
+ ObjectInfo(135, 65, 36, 0x0440), /* SCEPTRE OF LYF Quiver 1/Chest */
+ ObjectInfo(143, 45, 27, 0x0040), /* ROBE Quiver 1 */
+ ObjectInfo(28, 82, 1, 0x0040), /* FINE ROBE Quiver 1 */
+ ObjectInfo(80, 23, 0, 0x040C), /* KIRTLE Neck/Torso/Chest */
+ ObjectInfo(81, 23, 0, 0x040C), /* SILK SHIRT Neck/Torso/Chest */
+ ObjectInfo(82, 23, 0, 0x0410), /* ELVEN DOUBLET Legs/Chest */
+ ObjectInfo(112, 55, 0, 0x0420), /* LEATHER JERKIN Feet/Chest */
+ ObjectInfo(114, 8, 0, 0x0420), /* TUNIC Feet/Chest */
+ ObjectInfo(67, 24, 0, 0x0408), /* GHI Torso/Chest */
+ ObjectInfo(83, 24, 0, 0x0410), /* MAIL AKETON Legs/Chest */
+ ObjectInfo(68, 24, 0, 0x0408), /* MITHRAL AKETON Torso/Chest */
+ ObjectInfo(84, 24, 0, 0x0410), /* TORSO PLATE Legs/Chest */
+ ObjectInfo(69, 69, 0, 0x0408), /* PLATE OF LYTE Torso/Chest */
+ ObjectInfo(70, 24, 0, 0x0408), /* PLATE OF DARC Torso/Chest */
+ ObjectInfo(85, 24, 0, 0x0410), /* CAPE Legs/Chest */
+ ObjectInfo(86, 69, 0, 0x0410), /* CLOAK OF NIGHT Legs/Chest */
+ ObjectInfo(71, 7, 0, 0x0408), /* BARBARIAN HIDE Torso/Chest */
+ ObjectInfo(87, 7, 0, 0x0410), /* ROBE Legs/Chest */
+ ObjectInfo(119, 57, 0, 0x0420), /* FINE ROBE Feet/Chest */
+ ObjectInfo(72, 23, 0, 0x0408), /* TABARD Torso/Chest */
+ ObjectInfo(88, 23, 0, 0x0410), /* GUNNA Legs/Chest */
+ ObjectInfo(113, 29, 0, 0x0420), /* ELVEN HUKE Feet/Chest */
+ ObjectInfo(89, 69, 0, 0x0410), /* LEATHER PANTS Legs/Chest */
+ ObjectInfo(73, 69, 0, 0x0408), /* BLUE PANTS Torso/Chest */
+ ObjectInfo(74, 24, 0, 0x0408), /* GHI TROUSERS Torso/Chest */
+ ObjectInfo(90, 24, 0, 0x0410), /* LEG MAIL Legs/Chest */
+ ObjectInfo(103, 53, 0, 0x0402), /* MITHRAL MAIL Head/Chest */
+ ObjectInfo(104, 53, 0, 0x0402), /* LEG PLATE Head/Chest */
+ ObjectInfo(96, 9, 0, 0x0402), /* POLEYN OF LYTE Head/Chest */
+ ObjectInfo(97, 9, 0, 0x0402), /* POLEYN OF DARC Head/Chest */
+ ObjectInfo(98, 9, 0, 0x0402), /* BEZERKER HELM Head/Chest */
+ ObjectInfo(105, 54, 41, 0x0400), /* HELMET Chest */
+ ObjectInfo(106, 54, 41, 0x0200), /* BASINET Hands */
+ ObjectInfo(108, 10, 41, 0x0200), /* CASQUE 'N COIF Hands */
+ ObjectInfo(107, 54, 41, 0x0200), /* ARMET Hands */
+ ObjectInfo(75, 19, 0, 0x0408), /* HELM OF LYTE Torso/Chest */
+ ObjectInfo(91, 19, 0, 0x0410), /* HELM OF DARC Legs/Chest */
+ ObjectInfo(76, 19, 0, 0x0408), /* CALISTA Torso/Chest */
+ ObjectInfo(92, 19, 0, 0x0410), /* CROWN OF NERRA Legs/Chest */
+ ObjectInfo(99, 9, 0, 0x0402), /* BUCKLER Head/Chest */
+ ObjectInfo(115, 19, 0, 0x0420), /* HIDE SHIELD Feet/Chest */
+ ObjectInfo(100, 52, 0, 0x0402), /* SMALL SHIELD Head/Chest */
+ ObjectInfo(77, 20, 0, 0x0008), /* WOODEN SHIELD Torso */
+ ObjectInfo(93, 22, 0, 0x0010), /* LARGE SHIELD Legs */
+ ObjectInfo(116, 56, 0, 0x0420), /* SHIELD OF LYTE Feet/Chest */
+ ObjectInfo(109, 10, 41, 0x0200), /* SHIELD OF DARC Hands */
+ ObjectInfo(101, 52, 0, 0x0402), /* SANDALS Head/Chest */
+ ObjectInfo(78, 20, 0, 0x0008), /* SUEDE BOOTS Torso */
+ ObjectInfo(94, 22, 0, 0x0010), /* LEATHER BOOTS Legs */
+ ObjectInfo(117, 56, 0, 0x0420), /* HOSEN Feet/Chest */
+ ObjectInfo(110, 10, 41, 0x0200), /* FOOT PLATE Hands */
+ ObjectInfo(102, 52, 0, 0x0402), /* GREAVE OF LYTE Head/Chest */
+ ObjectInfo(79, 20, 0, 0x0008), /* GREAVE OF DARC Torso */
+ ObjectInfo(95, 22, 0, 0x0010), /* ELVEN BOOTS Legs */
+ ObjectInfo(118, 56, 0, 0x0420), /* GEM OF AGES Feet/Chest */
+ ObjectInfo(111, 10, 41, 0x0200), /* EKKHARD CROSS Hands */
+ ObjectInfo(140, 52, 0, 0x0402), /* MOONSTONE Head/Chest */
+ ObjectInfo(141, 19, 0, 0x0408), /* THE HELLION Torso/Chest */
+ ObjectInfo(142, 22, 0, 0x0010), /* PENDANT FERAL Legs */
+ ObjectInfo(194, 81, 0, 0x0420), /* COPPER COIN Feet/Chest */
+ ObjectInfo(196, 84, 0, 0x0408), /* SILVER COIN Torso/Chest */
+ ObjectInfo(0, 34, 0, 0x0500), /* GOLD COIN Pouch/Chest */
+ ObjectInfo(8, 6, 0, 0x0501), /* BOULDER Mouth/Pouch/Chest */
+ ObjectInfo(10, 15, 0, 0x0504), /* BLUE GEM Neck/Pouch/Chest */
+ ObjectInfo(12, 15, 0, 0x0504), /* ORANGE GEM Neck/Pouch/Chest */
+ ObjectInfo(146, 40, 0, 0x0500), /* GREEN GEM Pouch/Chest */
+ ObjectInfo(147, 41, 0, 0x0400), /* MAGICAL BOX Chest */
+ ObjectInfo(125, 4, 37, 0x0500), /* MAGICAL BOX Pouch/Chest */
+ ObjectInfo(126, 83, 37, 0x0500), /* MIRROR OF DAWN Pouch/Chest */
+ ObjectInfo(127, 4, 37, 0x0500), /* HORN OF FEAR Pouch/Chest */
+ ObjectInfo(176, 18, 0, 0x0500), /* ROPE Pouch/Chest */
+ ObjectInfo(177, 18, 0, 0x0500), /* RABBIT'S FOOT Pouch/Chest */
+ ObjectInfo(178, 18, 0, 0x0500), /* CORBAMITE Pouch/Chest */
+ ObjectInfo(179, 18, 0, 0x0500), /* CHOKER Pouch/Chest */
+ ObjectInfo(180, 18, 0, 0x0500), /* DEXHELM Pouch/Chest */
+ ObjectInfo(181, 18, 0, 0x0500), /* FLAMEBAIN Pouch/Chest */
+ ObjectInfo(182, 18, 0, 0x0500), /* POWERTOWERS Pouch/Chest */
+ ObjectInfo(183, 18, 0, 0x0500), /* SPEEDBOW Pouch/Chest */
+ ObjectInfo(184, 62, 0, 0x0500), /* CHEST Pouch/Chest */
+ ObjectInfo(185, 62, 0, 0x0500), /* OPEN CHEST Pouch/Chest */
+ ObjectInfo(186, 62, 0, 0x0500), /* ASHES Pouch/Chest */
+ ObjectInfo(187, 62, 0, 0x0500), /* BONES Pouch/Chest */
+ ObjectInfo(188, 62, 0, 0x0500), /* MON POTION Pouch/Chest */
+ ObjectInfo(189, 62, 0, 0x0500), /* UM POTION Pouch/Chest */
+ ObjectInfo(190, 62, 0, 0x0500), /* DES POTION Pouch/Chest */
+ ObjectInfo(191, 62, 0, 0x0500), /* VEN POTION Pouch/Chest */
+ ObjectInfo(128, 76, 0, 0x0200), /* SAR POTION Hands */
+ ObjectInfo(129, 3, 0, 0x0500), /* ZO POTION Pouch/Chest */
+ ObjectInfo(130, 60, 0, 0x0500), /* ROS POTION Pouch/Chest */
+ ObjectInfo(131, 61, 0, 0x0500), /* KU POTION Pouch/Chest */
+ ObjectInfo(168, 27, 0, 0x0501), /* DANE POTION Mouth/Pouch/Chest */
+ ObjectInfo(169, 28, 0, 0x0501), /* NETA POTION Mouth/Pouch/Chest */
+ ObjectInfo(170, 25, 0, 0x0501), /* BRO POTION Mouth/Pouch/Chest */
+ ObjectInfo(171, 26, 0, 0x0501), /* MA POTION Mouth/Pouch/Chest */
+ ObjectInfo(172, 71, 0, 0x0401), /* YA POTION Mouth/Chest */
+ ObjectInfo(173, 70, 0, 0x0401), /* EE POTION Mouth/Chest */
+ ObjectInfo(174, 5, 0, 0x0501), /* VI POTION Mouth/Pouch/Chest */
+ ObjectInfo(175, 66, 0, 0x0501), /* WATER FLASK Mouth/Pouch/Chest */
+ ObjectInfo(120, 15, 0, 0x0504), /* KATH BOMB Neck/Pouch/Chest */
+ ObjectInfo(121, 15, 0, 0x0504), /* PEW BOMB Neck/Pouch/Chest */
+ ObjectInfo(122, 58, 0, 0x0504), /* RA BOMB Neck/Pouch/Chest */
+ ObjectInfo(123, 59, 0, 0x0504), /* FUL BOMB Neck/Pouch/Chest */
+ ObjectInfo(124, 59, 0, 0x0504), /* APPLE Neck/Pouch/Chest */
+ ObjectInfo(132, 79, 38, 0x0500), /* CORN Pouch/Chest */
+ ObjectInfo(133, 63, 38, 0x0500), /* BREAD Pouch/Chest */
+ ObjectInfo(134, 64, 0, 0x0500), /* CHEESE Pouch/Chest */
+ ObjectInfo(136, 72, 39, 0x0400), /* SCREAMER SLICE Chest */
+ ObjectInfo(137, 73, 0, 0x0500), /* WORM ROUND Pouch/Chest */
+ ObjectInfo(138, 74, 0, 0x0500), /* DRUMSTICK Pouch/Chest */
+ ObjectInfo(139, 75, 0, 0x0504), /* DRAGON STEAK Neck/Pouch/Chest */
+ ObjectInfo(192, 77, 0, 0x0500), /* IRON KEY Pouch/Chest */
+ ObjectInfo(193, 78, 0, 0x0500), /* KEY OF B Pouch/Chest */
+ ObjectInfo(197, 74, 0, 0x0000), /* SOLID KEY */
+ ObjectInfo(198, 41, 0, 0x0400) /* SQUARE KEY Chest */
+ };
+ ArmourInfo armourInfo[58] = { // G0239_as_Graphic559_ArmourInfo
+ /* { Weight, Defense, Attributes, Unreferenced } */
+ ArmourInfo(3, 5, 0x01), /* CAPE */
+ ArmourInfo(4, 10, 0x01), /* CLOAK OF NIGHT */
+ ArmourInfo(3, 4, 0x01), /* BARBARIAN HIDE */
+ ArmourInfo(6, 5, 0x02), /* SANDALS */
+ ArmourInfo(16, 25, 0x04), /* LEATHER BOOTS */
+ ArmourInfo(4, 5, 0x00), /* ROBE */
+ ArmourInfo(4, 5, 0x00), /* ROBE */
+ ArmourInfo(3, 7, 0x01), /* FINE ROBE */
+ ArmourInfo(3, 7, 0x01), /* FINE ROBE */
+ ArmourInfo(4, 6, 0x01), /* KIRTLE */
+ ArmourInfo(2, 4, 0x00), /* SILK SHIRT */
+ ArmourInfo(4, 5, 0x01), /* TABARD */
+ ArmourInfo(5, 7, 0x01), /* GUNNA */
+ ArmourInfo(3, 11, 0x02), /* ELVEN DOUBLET */
+ ArmourInfo(3, 13, 0x02), /* ELVEN HUKE */
+ ArmourInfo(4, 13, 0x02), /* ELVEN BOOTS */
+ ArmourInfo(6, 17, 0x03), /* LEATHER JERKIN */
+ ArmourInfo(8, 20, 0x03), /* LEATHER PANTS */
+ ArmourInfo(14, 20, 0x03), /* SUEDE BOOTS */
+ ArmourInfo(6, 12, 0x02), /* BLUE PANTS */
+ ArmourInfo(5, 9, 0x01), /* TUNIC */
+ ArmourInfo(5, 8, 0x01), /* GHI */
+ ArmourInfo(5, 9, 0x01), /* GHI TROUSERS */
+ ArmourInfo(4, 1, 0x04), /* CALISTA */
+ ArmourInfo(6, 5, 0x04), /* CROWN OF NERRA */
+ ArmourInfo(11, 12, 0x05), /* BEZERKER HELM */
+ ArmourInfo(14, 17, 0x05), /* HELMET */
+ ArmourInfo(15, 20, 0x05), /* BASINET */
+ ArmourInfo(11, 22, 0x85), /* BUCKLER */
+ ArmourInfo(10, 16, 0x82), /* HIDE SHIELD */
+ ArmourInfo(14, 20, 0x83), /* WOODEN SHIELD */
+ ArmourInfo(21, 35, 0x84), /* SMALL SHIELD */
+ ArmourInfo(65, 35, 0x05), /* MAIL AKETON */
+ ArmourInfo(53, 35, 0x05), /* LEG MAIL */
+ ArmourInfo(52, 70, 0x07), /* MITHRAL AKETON */
+ ArmourInfo(41, 55, 0x07), /* MITHRAL MAIL */
+ ArmourInfo(16, 25, 0x06), /* CASQUE 'N COIF */
+ ArmourInfo(16, 30, 0x06), /* HOSEN */
+ ArmourInfo(19, 40, 0x07), /* ARMET */
+ ArmourInfo(120, 65, 0x04), /* TORSO PLATE */
+ ArmourInfo(80, 56, 0x04), /* LEG PLATE */
+ ArmourInfo(28, 37, 0x05), /* FOOT PLATE */
+ ArmourInfo(34, 56, 0x84), /* LARGE SHIELD */
+ ArmourInfo(17, 62, 0x05), /* HELM OF LYTE */
+ ArmourInfo(108, 125, 0x04), /* PLATE OF LYTE */
+ ArmourInfo(72, 90, 0x04), /* POLEYN OF LYTE */
+ ArmourInfo(24, 50, 0x05), /* GREAVE OF LYTE */
+ ArmourInfo(30, 85, 0x84), /* SHIELD OF LYTE */
+ ArmourInfo(35, 76, 0x04), /* HELM OF DARC */
+ ArmourInfo(141, 160, 0x04), /* PLATE OF DARC */
+ ArmourInfo(90, 101, 0x04), /* POLEYN OF DARC */
+ ArmourInfo(31, 60, 0x05), /* GREAVE OF DARC */
+ ArmourInfo(40, 100, 0x84), /* SHIELD OF DARC */
+ ArmourInfo(14, 54, 0x06), /* DEXHELM */
+ ArmourInfo(57, 60, 0x07), /* FLAMEBAIN */
+ ArmourInfo(81, 88, 0x04), /* POWERTOWERS */
+ ArmourInfo(3, 16, 0x02), /* BOOTS OF SPEED */
+ ArmourInfo(2, 3, 0x03) /* HALTER */
+ };
+
+ WeaponInfo weaponInfo[46] = { // @ G0238_as_Graphic559_WeaponInfo
+ /* { Weight, Class, Strength, KineticEnergy, Attributes } */
+ WeaponInfo(1, 130, 2, 0, 0x2000), /* EYE OF TIME */
+ WeaponInfo(1, 131, 2, 0, 0x2000), /* STORMRING */
+ WeaponInfo(11, 0, 8, 2, 0x2000), /* TORCH */
+ WeaponInfo(12, 112, 10, 80, 0x2028), /* FLAMITT */
+ WeaponInfo(9, 129, 16, 7, 0x2000), /* STAFF OF CLAWS */
+ WeaponInfo(30, 113, 49, 110, 0x0942), /* BOLT BLADE */
+ WeaponInfo(47, 0, 55, 20, 0x0900), /* FURY */
+ WeaponInfo(24, 255, 25, 10, 0x20FF), /* THE FIRESTAFF */
+ WeaponInfo(5, 2, 10, 19, 0x0200), /* DAGGER */
+ WeaponInfo(33, 0, 30, 8, 0x0900), /* FALCHION */
+ WeaponInfo(32, 0, 34, 10, 0x0900), /* SWORD */
+ WeaponInfo(26, 0, 38, 10, 0x0900), /* RAPIER */
+ WeaponInfo(35, 0, 42, 11, 0x0900), /* SABRE */
+ WeaponInfo(36, 0, 46, 12, 0x0900), /* SAMURAI SWORD */
+ WeaponInfo(33, 0, 50, 14, 0x0900), /* DELTA */
+ WeaponInfo(37, 0, 62, 14, 0x0900), /* DIAMOND EDGE */
+ WeaponInfo(30, 0, 48, 13, 0x0000), /* VORPAL BLADE */
+ WeaponInfo(39, 0, 58, 15, 0x0900), /* THE INQUISITOR */
+ WeaponInfo(43, 2, 49, 33, 0x0300), /* AXE */
+ WeaponInfo(65, 2, 70, 44, 0x0300), /* HARDCLEAVE */
+ WeaponInfo(31, 0, 32, 10, 0x2000), /* MACE */
+ WeaponInfo(41, 0, 42, 13, 0x2000), /* MACE OF ORDER */
+ WeaponInfo(50, 0, 60, 15, 0x2000), /* MORNINGSTAR */
+ WeaponInfo(36, 0, 19, 10, 0x2700), /* CLUB */
+ WeaponInfo(110, 0, 44, 22, 0x2600), /* STONE CLUB */
+ WeaponInfo(10, 20, 1, 50, 0x2032), /* BOW */
+ WeaponInfo(28, 30, 1, 180, 0x2078), /* CROSSBOW */
+ WeaponInfo(2, 10, 2, 10, 0x0100), /* ARROW */
+ WeaponInfo(2, 10, 2, 28, 0x0500), /* SLAYER */
+ WeaponInfo(19, 39, 5, 20, 0x2032), /* SLING */
+ WeaponInfo(10, 11, 6, 18, 0x2000), /* ROCK */
+ WeaponInfo(3, 12, 7, 23, 0x0800), /* POISON DART */
+ WeaponInfo(1, 1, 3, 19, 0x0A00), /* THROWING STAR */
+ WeaponInfo(8, 0, 4, 4, 0x2000), /* STICK */
+ WeaponInfo(26, 129, 12, 4, 0x2000), /* STAFF */
+ WeaponInfo(1, 130, 0, 0, 0x2000), /* WAND */
+ WeaponInfo(2, 140, 1, 20, 0x2000), /* TEOWAND */
+ WeaponInfo(35, 128, 18, 6, 0x2000), /* YEW STAFF */
+ WeaponInfo(29, 159, 0, 4, 0x2000), /* STAFF OF MANAR */
+ WeaponInfo(21, 131, 0, 3, 0x2000), /* SNAKE STAFF */
+ WeaponInfo(33, 136, 0, 7, 0x2000), /* THE CONDUIT */
+ WeaponInfo(8, 132, 3, 1, 0x2000), /* DRAGON SPIT */
+ WeaponInfo(18, 131, 9, 4, 0x2000), /* SCEPTRE OF LYF */
+ WeaponInfo(8, 192, 1, 1, 0x2000), /* HORN OF FEAR */
+ WeaponInfo(30, 26, 1, 220, 0x207D), /* SPEEDBOW */
+ WeaponInfo(36, 255, 100, 50, 0x20FF) /* THE FIRESTAFF */
+ };
+
+ CreatureInfo creatureInfo[k27_CreatureTypeCount] = { // @ G0243_as_Graphic559_CreatureInfo
+ /* { CreatureAspectIndex, AttackSoundOrdinal, Attributes, GraphicInfo,
+ MovementTicks, AttackTicks, Defense, BaseHealth, Attack, PoisonAttack,
+ Dexterity, Ranges, Properties, Resistances, AnimationTicks, WoundProbabilities, AttackType } */
+ {0, 4, 0x0482, 0x623D, 8, 20, 55, 150, 150, 240, 55, 0x1153, 0x299B, 0x0876, 0x0254, 0xFD40, 4},
+ {1, 0, 0x0480, 0xA625, 15, 32, 20, 110, 80, 15, 20, 0x3132, 0x33A9, 0x0E42, 0x0384, 0xFC41, 3},
+ {2, 6, 0x0510, 0x6198, 3, 5, 50, 10, 10, 0, 110, 0x1376, 0x710A, 0x0235, 0x0222, 0xFD20, 0},
+ {3, 0, 0x04B4, 0xB225, 10, 21, 30, 40, 58, 0, 80, 0x320A, 0x96AA, 0x0B3C, 0x0113, 0xF910, 5},
+ {4, 1, 0x0701, 0xA3B8, 9, 8, 45, 101, 90, 0, 65, 0x1554, 0x58FF, 0x0A34, 0x0143, 0xFE93, 4},
+ {5, 0, 0x0581, 0x539D, 20, 18, 100, 60, 30, 0, 30, 0x1232, 0x4338, 0x0583, 0x0265, 0xFFD6, 3},
+ {6, 3, 0x070C, 0x0020, 120, 10, 5, 165, 5, 0, 5, 0x1111, 0x10F1, 0x0764, 0x02F2, 0xFC84, 6},
+ {7, 7, 0x0300, 0x0220, 185, 15, 170, 50, 40, 5, 10, 0x1463, 0x25C4, 0x06E3, 0x01F4, 0xFD93, 4}, /* Atari ST: AttackSoundOrdinal = 0 */
+ {8, 2, 0x1864, 0x5225, 11, 16, 15, 30, 55, 0, 80, 0x1423, 0x4664, 0x0FC8, 0x0116, 0xFB30, 6},
+ {9, 10, 0x0282, 0x71B8, 21, 14, 240, 120, 219, 0, 35, 0x1023, 0x3BFF, 0x0FF7, 0x04F3, 0xF920, 3}, /* Atari ST: AttackSoundOrdinal = 7 */
+ {10, 2, 0x1480, 0x11B8, 17, 12, 25, 33, 20, 0, 40, 0x1224, 0x5497, 0x0F15, 0x0483, 0xFB20, 3},
+ {11, 0, 0x18C6, 0x0225, 255, 8, 45, 80, 105, 0, 60, 0x1314, 0x55A5, 0x0FF9, 0x0114, 0xFD95, 1},
+ {12, 11, 0x1280, 0x6038, 7, 7, 22, 20, 22, 0, 80, 0x1013, 0x6596, 0x0F63, 0x0132, 0xFA30, 4}, /* Atari ST: AttackSoundOrdinal = 8 */
+ {13, 9, 0x14A2, 0xB23D, 5, 10, 42, 39, 90, 100, 88, 0x1343, 0x5734, 0x0638, 0x0112, 0xFA30, 4}, /* Atari ST: AttackSoundOrdinal = 0 */
+ {14, 0, 0x05B8, 0x1638, 10, 20, 47, 44, 75, 0, 90, 0x4335, 0xD952, 0x035B, 0x0664, 0xFD60, 5},
+ {15, 5, 0x0381, 0x523D, 18, 19, 72, 70, 45, 35, 35, 0x1AA1, 0x15AB, 0x0B93, 0x0253, 0xFFC5, 4},
+ {16, 10, 0x0680, 0xA038, 13, 8, 28, 20, 25, 0, 41, 0x1343, 0x2148, 0x0321, 0x0332, 0xFC30, 3}, /* Atari ST: AttackSoundOrdinal = 7 */
+ {17, 0, 0x04A0, 0xF23D, 1, 16, 180, 8, 28, 20, 150, 0x1432, 0x19FD, 0x0004, 0x0112, 0xF710, 4},
+ {18, 11, 0x0280, 0xA3BD, 14, 6, 140, 60, 105, 0, 70, 0x1005, 0x7AFF, 0x0FFA, 0x0143, 0xFA30, 4}, /* Atari ST: AttackSoundOrdinal = 8 */
+ {19, 0, 0x0060, 0xE23D, 5, 18, 15, 33, 61, 0, 65, 0x3258, 0xAC77, 0x0F56, 0x0117, 0xFC40, 5},
+ {20, 8, 0x10DE, 0x0225, 25, 25, 75, 144, 66, 0, 50, 0x1381, 0x7679, 0x0EA7, 0x0345, 0xFD93, 3}, /* Atari ST: AttackSoundOrdinal = 0 */
+ {21, 3, 0x0082, 0xA3BD, 7, 15, 33, 77, 130, 0, 60, 0x1592, 0x696A, 0x0859, 0x0224, 0xFC30, 4},
+ {22, 0, 0x1480, 0x53BD, 10, 14, 68, 100, 100, 0, 75, 0x4344, 0xBDF9, 0x0A5D, 0x0124, 0xF920, 3},
+ {23, 0, 0x38AA, 0x0038, 12, 22, 255, 180, 210, 0, 130, 0x6369, 0xFF37, 0x0FBF, 0x0564, 0xFB52, 5},
+ {24, 1, 0x068A, 0x97BD, 13, 28, 110, 255, 255, 0, 70, 0x3645, 0xBF7C, 0x06CD, 0x0445, 0xFC30, 4}, /* Atari ST Version 1.0 1987-12-08 1987-12-11: Ranges = 0x2645 */
+ {25, 0, 0x38AA, 0x0000, 12, 22, 255, 180, 210, 0, 130, 0x6369, 0xFF37, 0x0FBF, 0x0564, 0xFB52, 5},
+ {26, 0, 0x38AA, 0x0000, 12, 22, 255, 180, 210, 0, 130, 0x6369, 0xFF37, 0x0FBF, 0x0564, 0xFB52, 5}
+ };
+ // this is the number of uint16s the data has to be stored, not the length of the data in dungeon.dat!
+ byte thingDataWordCount[16] = { // @ G0235_auc_Graphic559_ThingDataByteCount
+ 2, /* Door */
+ 3, /* Teleporter */
+ 2, /* Text String */
+ 4, /* Sensor */
+ 9, /* Group */
+ 2, /* Weapon */
+ 2, /* Armour */
+ 2, /* Scroll */
+ 2, /* Potion */
+ 4, /* Container */
+ 2, /* Junk */
+ 0, /* Unused */
+ 0, /* Unused */
+ 0, /* Unused */
+ 5, /* Projectile */
+ 2 /* Explosion */
+ };
+
+ for (int i = 0; i < 180; i++)
+ _objectInfos[i] = objectInfo[i];
+
+ for (int i = 0; i < 58; i++)
+ _armourInfos[i] = armourInfo[i];
+
+ for (int i = 0; i < 46; i++)
+ _weaponInfos[i] = weaponInfo[i];
+
+ for (int i = 0; i < k27_CreatureTypeCount; i++)
+ _creatureInfos[i] = creatureInfo[i];
+
+ for (int i = 0; i < 16; i++)
+ _thingDataWordCount[i] = thingDataWordCount[i];
+
+}
+
+DungeonMan::DungeonMan(DMEngine *dmEngine) : _vm(dmEngine) {
+ _rawDunFileDataSize = 0;
+ _rawDunFileData = nullptr;
+ _dungeonColumCount = 0;
+ _dungeonMapsFirstColumnIndex = nullptr;
+
+ _dungeonColumCount = 0;
+ _dungeonColumnsCumulativeSquareThingCount = nullptr;
+ _squareFirstThings = nullptr;
+ _dungeonTextData = nullptr;
+ for (uint16 i = 0; i < 16; ++i)
+ _thingData[i] = nullptr;
+
+ _dungeonMapData = nullptr;
+ _partyDir = (Direction)0;
+ _partyMapX = 0;
+ _partyMapY = 0;
+ _partyMapIndex = 0;
+ _currMapIndex = kM1_mapIndexNone;
+ _currMapData = nullptr;
+ _currMap = nullptr;
+ _currMapWidth = 0;
+ _currMapHeight = 0;
+ _currMapColCumulativeSquareFirstThingCount = nullptr;
+ _dungeonMaps = nullptr;
+ _dungeonRawMapData = nullptr;
+ _currMapInscriptionWallOrnIndex = 0;
+ for (uint16 i = 0; i < 6; ++i)
+ _dungeonViewClickableBoxes[i].setToZero();
+ _isFacingAlcove = false;
+ _isFacingViAltar = false;
+ _isFacingFountain = false;
+ _squareAheadElement = (ElementType)0;
+ for (uint16 i = 0; i < 5; ++i)
+ _pileTopObject[i] = Thing(0);
+ for (uint16 i = 0; i < 2; ++i)
+ _currMapDoorInfo[i].resetToZero();
+
+ setupConstants();
+}
+
+DungeonMan::~DungeonMan() {
+ delete[] _rawDunFileData;
+ delete[] _dungeonMaps;
+ delete[] _dungeonMapsFirstColumnIndex;
+ delete[] _dungeonColumnsCumulativeSquareThingCount;
+ delete[] _squareFirstThings;
+ delete[] _dungeonTextData;
+ delete[] _dungeonMapData;
+ for (uint16 i = 0; i < 16; ++i)
+ delete[] _thingData[i];
+
+ delete[] _dungeonRawMapData;
+}
+
+void DungeonMan::decompressDungeonFile() {
+ Common::File f;
+ f.open("Dungeon.dat");
+ if (f.readUint16BE() == 0x8104) { // if dungeon is compressed
+ _rawDunFileDataSize = f.readUint32BE();
+ delete[] _rawDunFileData;
+ _rawDunFileData = new byte[_rawDunFileDataSize];
+ f.readUint16BE(); // discard
+ byte common[4];
+ for (uint16 i = 0; i < 4; ++i)
+ common[i] = f.readByte();
+ byte lessCommon[16];
+ for (uint16 i = 0; i < 16; ++i)
+ lessCommon[i] = f.readByte();
+
+ // start unpacking
+ uint32 uncompIndex = 0;
+ uint8 bitsUsedInWord = 0;
+ uint16 wordBuff = f.readUint16BE();
+ uint8 bitsLeftInByte = 8;
+ byte byteBuff = f.readByte();
+ while (uncompIndex < _rawDunFileDataSize) {
+ while (bitsUsedInWord != 0) {
+ uint8 shiftVal;
+ if (f.eos()) {
+ shiftVal = bitsUsedInWord;
+ wordBuff <<= shiftVal;
+ } else {
+ shiftVal = MIN(bitsLeftInByte, bitsUsedInWord);
+ wordBuff <<= shiftVal;
+ wordBuff |= (byteBuff >> (8 - shiftVal));
+ byteBuff <<= shiftVal;
+ bitsLeftInByte -= shiftVal;
+ if (!bitsLeftInByte) {
+ byteBuff = f.readByte();
+ bitsLeftInByte = 8;
+ }
+ }
+ bitsUsedInWord -= shiftVal;
+ }
+ if (((wordBuff >> 15) & 1) == 0) {
+ _rawDunFileData[uncompIndex++] = common[(wordBuff >> 13) & 3];
+ bitsUsedInWord += 3;
+ } else if (((wordBuff >> 14) & 3) == 2) {
+ _rawDunFileData[uncompIndex++] = lessCommon[(wordBuff >> 10) & 15];
+ bitsUsedInWord += 6;
+ } else if (((wordBuff >> 14) & 3) == 3) {
+ _rawDunFileData[uncompIndex++] = (wordBuff >> 6) & 255;
+ bitsUsedInWord += 10;
+ }
+ }
+ } else { // read uncompressed Dungeon.dat
+ f.seek(0);
+ _rawDunFileDataSize = f.size();
+ delete[] _rawDunFileData;
+ _rawDunFileData = new byte[_rawDunFileDataSize];
+ f.read(_rawDunFileData, _rawDunFileDataSize);
+ }
+ f.close();
+}
+
+const Thing Thing::_none(0); // @ C0xFFFF_THING_NONE
+const Thing Thing::_endOfList(0xFFFE); // @ C0xFFFE_THING_ENDOFLIST
+const Thing Thing::_firstExplosion(0xFF80); // @ C0xFF80_THING_FIRST_EXPLOSION
+const Thing Thing::_explFireBall(0xFF80); // @ C0xFF80_THING_EXPLOSION_FIREBALL
+const Thing Thing::_explSlime(0xFF81); // @ C0xFF81_THING_EXPLOSION_SLIME
+const Thing Thing::_explLightningBolt(0xFF82); // @ C0xFF82_THING_EXPLOSION_LIGHTNING_BOLT
+const Thing Thing::_explHarmNonMaterial(0xFF83); // @ C0xFF83_THING_EXPLOSION_HARM_NON_MATERIAL
+const Thing Thing::_explOpenDoor(0xFF84); // @ C0xFF84_THING_EXPLOSION_OPEN_DOOR
+const Thing Thing::_explPoisonBolt(0xFF86); // @ C0xFF86_THING_EXPLOSION_POISON_BOLT
+const Thing Thing::_explPoisonCloud(0xFF87); // @ C0xFF87_THING_EXPLOSION_POISON_CLOUD
+const Thing Thing::_explSmoke(0xFFA8); // @ C0xFFA8_THING_EXPLOSION_SMOKE
+const Thing Thing::_explFluxcage(0xFFB2); // @ C0xFFB2_THING_EXPLOSION_FLUXCAGE
+const Thing Thing::_explRebirthStep1(0xFFE4); // @ C0xFFE4_THING_EXPLOSION_REBIRTH_STEP1
+const Thing Thing::_explRebirthStep2(0xFFE5); // @ C0xFFE5_THING_EXPLOSION_REBIRTH_STEP2
+const Thing Thing::_party(0xFFFF); // @ C0xFFFF_THING_PARTY
+
+void DungeonMan::loadDungeonFile(Common::InSaveFile *file) {
+ static const byte additionalThingCounts[16] = { // @ G0236_auc_Graphic559_AdditionalThingCounts{
+ 0, /* Door */
+ 0, /* Teleporter */
+ 0, /* Text String */
+ 0, /* Sensor */
+ 75, /* Group */
+ 100, /* Weapon */
+ 120, /* Armour */
+ 0, /* Scroll */
+ 5, /* Potion */
+ 0, /* Container */
+ 140, /* Junk */
+ 0, /* Unused */
+ 0, /* Unused */
+ 0, /* Unused */
+ 60, /* Projectile */
+ 50 /* Explosion */
+ };
+
+ if (_vm->_newGameFl)
+ decompressDungeonFile();
+
+ Common::ReadStream *dunDataStream = nullptr;
+ if (file) {
+ // if loading a save
+ dunDataStream = file;
+ } else {
+ // else read dungeon.dat
+ assert(_rawDunFileData && _rawDunFileDataSize);
+ dunDataStream = new Common::MemoryReadStream(_rawDunFileData, _rawDunFileDataSize, DisposeAfterUse::NO);
+ }
+
+ // initialize _g278_dungeonFileHeader
+ _dungeonFileHeader._ornamentRandomSeed = dunDataStream->readUint16BE();
+ _dungeonFileHeader._rawMapDataSize = dunDataStream->readUint16BE();
+ _dungeonFileHeader._mapCount = dunDataStream->readByte();
+ dunDataStream->readByte(); // discard 1 byte
+ _dungeonFileHeader._textDataWordCount = dunDataStream->readUint16BE();
+ _dungeonFileHeader._partyStartLocation = dunDataStream->readUint16BE();
+ _dungeonFileHeader._squareFirstThingCount = dunDataStream->readUint16BE();
+ for (uint16 i = 0; i < k16_ThingTypeTotal; ++i)
+ _dungeonFileHeader._thingCounts[i] = dunDataStream->readUint16BE();
+
+ // init party position and mapindex
+ if (_vm->_newGameFl) {
+ uint16 startLoc = _dungeonFileHeader._partyStartLocation;
+ _partyDir = (Direction)((startLoc >> 10) & 3);
+ _partyMapX = startLoc & 0x1F;
+ _partyMapY = (startLoc >> 5) & 0x1F;
+ _partyMapIndex = 0;
+ }
+
+ // load map data
+ if (!_vm->_restartGameRequest) {
+ delete[] _dungeonMaps;
+ _dungeonMaps = new Map[_dungeonFileHeader._mapCount];
+ }
+
+ for (uint16 i = 0; i < _dungeonFileHeader._mapCount; ++i) {
+ _dungeonMaps[i]._rawDunDataOffset = dunDataStream->readUint16BE();
+ dunDataStream->readUint32BE(); // discard 4 bytes
+ _dungeonMaps[i]._offsetMapX = dunDataStream->readByte();
+ _dungeonMaps[i]._offsetMapY = dunDataStream->readByte();
+
+ uint16 tmp = dunDataStream->readUint16BE();
+ _dungeonMaps[i]._height = tmp >> 11;
+ _dungeonMaps[i]._width = (tmp >> 6) & 0x1F;
+ _dungeonMaps[i]._level = tmp & 0x3F; // Only used in DMII
+
+ tmp = dunDataStream->readUint16BE();
+ _dungeonMaps[i]._randFloorOrnCount = tmp >> 12;
+ _dungeonMaps[i]._floorOrnCount = (tmp >> 8) & 0xF;
+ _dungeonMaps[i]._randWallOrnCount = (tmp >> 4) & 0xF;
+ _dungeonMaps[i]._wallOrnCount = tmp & 0xF;
+
+ tmp = dunDataStream->readUint16BE();
+ _dungeonMaps[i]._difficulty = tmp >> 12;
+ _dungeonMaps[i]._creatureTypeCount = (tmp >> 4) & 0xF;
+ _dungeonMaps[i]._doorOrnCount = tmp & 0xF;
+
+ tmp = dunDataStream->readUint16BE();
+ _dungeonMaps[i]._doorSet1 = (tmp >> 12) & 0xF;
+ _dungeonMaps[i]._doorSet0 = (tmp >> 8) & 0xF;
+ _dungeonMaps[i]._wallSet = (WallSet)((tmp >> 4) & 0xF);
+ _dungeonMaps[i]._floorSet = (FloorSet)(tmp & 0xF);
+ }
+
+ // load column stuff thingy
+ if (!_vm->_restartGameRequest) {
+ delete[] _dungeonMapsFirstColumnIndex;
+ _dungeonMapsFirstColumnIndex = new uint16[_dungeonFileHeader._mapCount];
+ }
+ uint16 columCount = 0;
+ for (uint16 i = 0; i < _dungeonFileHeader._mapCount; ++i) {
+ _dungeonMapsFirstColumnIndex[i] = columCount;
+ columCount += _dungeonMaps[i]._width + 1;
+ }
+ _dungeonColumCount = columCount;
+
+ uint32 actualSquareFirstThingCount = _dungeonFileHeader._squareFirstThingCount;
+ if (_vm->_newGameFl)
+ _dungeonFileHeader._squareFirstThingCount += 300;
+
+ if (!_vm->_restartGameRequest) {
+ delete[] _dungeonColumnsCumulativeSquareThingCount;
+ _dungeonColumnsCumulativeSquareThingCount = new uint16[columCount];
+ }
+ for (uint16 i = 0; i < columCount; ++i)
+ _dungeonColumnsCumulativeSquareThingCount[i] = dunDataStream->readUint16BE();
+
+ // load square first things
+ if (!_vm->_restartGameRequest) {
+ delete[] _squareFirstThings;
+ _squareFirstThings = new Thing[_dungeonFileHeader._squareFirstThingCount];
+ }
+
+ for (uint16 i = 0; i < actualSquareFirstThingCount; ++i)
+ _squareFirstThings[i].set(dunDataStream->readUint16BE());
+
+ if (_vm->_newGameFl) {
+ for (uint16 i = 0; i < 300; ++i)
+ _squareFirstThings[actualSquareFirstThingCount + i] = Thing::_none;
+ }
+
+ // load text data
+ if (!_vm->_restartGameRequest) {
+ delete[] _dungeonTextData;
+ _dungeonTextData = new uint16[_dungeonFileHeader._textDataWordCount];
+ }
+
+ for (uint16 i = 0; i < _dungeonFileHeader._textDataWordCount; ++i)
+ _dungeonTextData[i] = dunDataStream->readUint16BE();
+
+ if (_vm->_newGameFl)
+ _vm->_timeline->_eventMaxCount = 100;
+
+ // load things
+ for (uint16 thingType = k0_DoorThingType; thingType < k16_ThingTypeTotal; ++thingType) {
+ uint16 thingCount = _dungeonFileHeader._thingCounts[thingType];
+ if (_vm->_newGameFl)
+ _dungeonFileHeader._thingCounts[thingType] = MIN((thingType == k15_ExplosionThingType) ? 768 : 1024, thingCount + additionalThingCounts[thingType]);
+
+ uint16 thingStoreWordCount = _thingDataWordCount[thingType];
+
+ if (thingStoreWordCount == 0)
+ continue;
+
+ if (!_vm->_restartGameRequest) {
+ delete[] _thingData[thingType];
+ _thingData[thingType] = new uint16[_dungeonFileHeader._thingCounts[thingType] * thingStoreWordCount];
+ }
+
+ if ((thingType == k4_GroupThingType || thingType == k14_ProjectileThingType) && !file) { // !file because save files have diff. structure than dungeon.dat
+ for (uint16 i = 0; i < thingCount; ++i) {
+ uint16 *nextSlot = _thingData[thingType] + i *thingStoreWordCount;
+ for (uint16 j = 0; j < thingStoreWordCount; ++j) {
+ if (j == 2 || j == 3)
+ nextSlot[j] = dunDataStream->readByte();
+ else
+ nextSlot[j] = dunDataStream->readUint16BE();
+ }
+ }
+ } else {
+ for (uint16 i = 0; i < thingCount; ++i) {
+ uint16 *nextSlot = _thingData[thingType] + i *thingStoreWordCount;
+ for (uint16 j = 0; j < thingStoreWordCount; ++j)
+ nextSlot[j] = dunDataStream->readUint16BE();
+ }
+ }
+
+ if (_vm->_newGameFl) {
+ if ((thingType == k4_GroupThingType) || thingType >= k14_ProjectileThingType)
+ _vm->_timeline->_eventMaxCount += _dungeonFileHeader._thingCounts[thingType];
+
+ for (uint16 i = 0; i < additionalThingCounts[thingType]; ++i)
+ (_thingData[thingType] + (thingCount + i) * thingStoreWordCount)[0] = Thing::_none.toUint16();
+ }
+ }
+
+ // load map data
+ if (!_vm->_restartGameRequest) {
+ delete[] _dungeonRawMapData;
+ _dungeonRawMapData = new byte[_dungeonFileHeader._rawMapDataSize];
+ }
+
+ for (uint32 i = 0; i < _dungeonFileHeader._rawMapDataSize; ++i)
+ _dungeonRawMapData[i] = dunDataStream->readByte();
+
+ if (!_vm->_restartGameRequest) {
+ uint8 mapCount = _dungeonFileHeader._mapCount;
+ delete[] _dungeonMapData;
+ _dungeonMapData = new byte**[_dungeonColumCount + mapCount];
+ byte **colFirstSquares = (byte **)_dungeonMapData + mapCount;
+ for (uint8 i = 0; i < mapCount; ++i) {
+ _dungeonMapData[i] = colFirstSquares;
+ byte *square = _dungeonRawMapData + _dungeonMaps[i]._rawDunDataOffset;
+ *colFirstSquares++ = square;
+ for (uint16 w = 1; w <= _dungeonMaps[i]._width; ++w) {
+ square += _dungeonMaps[i]._height + 1;
+ *colFirstSquares++ = square;
+ }
+ }
+ }
+
+ if (!file) { // this means that we created a new MemoryReadStream
+ delete file;
+ } // the deletion of the function parameter 'file' happens elsewhere
+}
+
+void DungeonMan::setCurrentMap(uint16 mapIndex) {
+ static const DoorInfo doorInfo[4] = { // @ G0254_as_Graphic559_DoorInfo
+ /* { Attributes, Defense } */
+ DoorInfo(3, 110), /* Door type 0 Portcullis */
+ DoorInfo(0, 42), /* Door type 1 Wooden door */
+ DoorInfo(0, 230), /* Door type 2 Iron door */
+ DoorInfo(5, 255) /* Door type 3 Ra door */
+ };
+
+
+ _currMapIndex = mapIndex;
+ _currMapData = _dungeonMapData[mapIndex];
+ _currMap = _dungeonMaps + mapIndex;
+ _currMapWidth = _dungeonMaps[mapIndex]._width + 1;
+ _currMapHeight = _dungeonMaps[mapIndex]._height + 1;
+ _currMapDoorInfo[0] = doorInfo[_currMap->_doorSet0];
+ _currMapDoorInfo[1] = doorInfo[_currMap->_doorSet1];
+ _currMapColCumulativeSquareFirstThingCount = &_dungeonColumnsCumulativeSquareThingCount[_dungeonMapsFirstColumnIndex[mapIndex]];
+}
+
+void DungeonMan::setCurrentMapAndPartyMap(uint16 mapIndex) {
+ setCurrentMap(_partyMapIndex = mapIndex);
+
+ byte *metaMapData = _currMapData[_currMapWidth - 1] + _currMapHeight;
+
+ _vm->_displayMan->_currMapAllowedCreatureTypes = metaMapData;
+ metaMapData += _currMap->_creatureTypeCount;
+
+ memcpy(_vm->_displayMan->_currMapWallOrnIndices, metaMapData, _currMap->_wallOrnCount);
+ metaMapData += _currMap->_wallOrnCount;
+
+ memcpy(_vm->_displayMan->_currMapFloorOrnIndices, metaMapData, _currMap->_floorOrnCount);
+ metaMapData += _currMap->_floorOrnCount;
+
+ memcpy(_vm->_displayMan->_currMapDoorOrnIndices, metaMapData, _currMap->_doorOrnCount);
+
+ _currMapInscriptionWallOrnIndex = _currMap->_wallOrnCount;
+ _vm->_displayMan->_currMapWallOrnIndices[_currMapInscriptionWallOrnIndex] = k0_WallOrnInscription;
+}
+
+
+Square DungeonMan::getSquare(int16 mapX, int16 mapY) {
+ bool isMapYInBounds = (mapY >= 0) && (mapY < _currMapHeight);
+ bool isMapXInBounds = (mapX >= 0) && (mapX < _currMapWidth);
+
+ if (isMapXInBounds && isMapYInBounds)
+ return Square(_currMapData[mapX][mapY]);
+
+ if (isMapYInBounds) {
+ SquareType squareType = Square(_currMapData[0][mapY]).getType();
+ if (((mapX == -1) && (squareType == k1_CorridorElemType)) || (squareType == k2_PitElemType))
+ return Square(k0_ElementTypeWall, k0x0004_WallEastRandOrnAllowed);
+
+ squareType = Square(_currMapData[_currMapWidth - 1][mapY]).getType();
+ if (((mapX == _currMapWidth) && (squareType == k1_CorridorElemType)) || (squareType == k2_PitElemType))
+ return Square(k0_ElementTypeWall, k0x0001_WallWestRandOrnAllowed);
+ } else if (isMapXInBounds) {
+ SquareType squareType = Square(_currMapData[mapX][0]).getType();
+ if (((mapY == -1) && (squareType == k1_CorridorElemType)) || (squareType == k2_PitElemType))
+ return Square(k0_ElementTypeWall, k0x0002_WallSouthRandOrnAllowed);
+
+ squareType = Square(_currMapData[mapX][_currMapHeight - 1]).getType();
+ if (((mapY == _currMapHeight) && (squareType == k1_CorridorElemType)) || (squareType == k2_PitElemType))
+ return Square(k0_ElementTypeWall, k0x0008_WallNorthRandOrnAllowed);
+ }
+ return Square(k0_ElementTypeWall, 0);
+}
+
+Square DungeonMan::getRelSquare(Direction dir, int16 stepsForward, int16 stepsRight, int16 posX, int16 posY) {
+ mapCoordsAfterRelMovement(dir, stepsForward, stepsForward, posX, posY);
+ return getSquare(posX, posY);
+}
+
+int16 DungeonMan::getSquareFirstThingIndex(int16 mapX, int16 mapY) {
+ unsigned char *curSquare = _currMapData[mapX];
+ if ((mapX < 0) || (mapX >= _currMapWidth) || (mapY < 0) || (mapY >= _currMapHeight) || !getFlag(curSquare[mapY], k0x0010_ThingListPresent))
+ return -1;
+
+ int16 curMapY = 0;
+ uint16 thingIndex = _currMapColCumulativeSquareFirstThingCount[mapX];
+ while (curMapY++ != mapY) {
+ if (getFlag(*curSquare++, k0x0010_ThingListPresent))
+ thingIndex++;
+ }
+ return thingIndex;
+}
+
+Thing DungeonMan::getSquareFirstThing(int16 mapX, int16 mapY) {
+ int16 index = getSquareFirstThingIndex(mapX, mapY);
+ if (index == -1)
+ return Thing::_endOfList;
+ return _squareFirstThings[index];
+}
+
+void DungeonMan::setSquareAspect(uint16 *aspectArray, Direction dir, int16 mapX, int16 mapY) {
+ unsigned char L0307_uc_Multiple;
+#define AL0307_uc_Square L0307_uc_Multiple
+#define AL0307_uc_FootprintsAllowed L0307_uc_Multiple
+#define AL0307_uc_ScentOrdinal L0307_uc_Multiple
+
+ for (uint16 i = 0; i < 5; ++i)
+ aspectArray[i] = 0;
+
+ Thing curThing = getSquareFirstThing(mapX, mapY);
+ AL0307_uc_Square = getSquare(mapX, mapY).toByte();
+ bool leftRandomWallOrnamentAllowed = false;
+ bool rightRandomWallOrnamentAllowed = false;
+ bool frontRandomWallOrnamentAllowed = false;
+ bool squareIsFakeWall;
+
+ aspectArray[k0_ElementAspect] = Square(AL0307_uc_Square).getType();
+ switch (aspectArray[k0_ElementAspect]) {
+ case k0_ElementTypeWall:
+ switch (dir) {
+ case kDirNorth:
+ leftRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0004_WallEastRandOrnAllowed);
+ frontRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0002_WallSouthRandOrnAllowed);
+ rightRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0001_WallWestRandOrnAllowed);
+ break;
+ case kDirEast:
+ leftRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0002_WallSouthRandOrnAllowed);
+ frontRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0001_WallWestRandOrnAllowed);
+ rightRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0008_WallNorthRandOrnAllowed);
+ break;
+ case kDirSouth:
+ leftRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0001_WallWestRandOrnAllowed);
+ frontRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0008_WallNorthRandOrnAllowed);
+ rightRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0004_WallEastRandOrnAllowed);
+ break;
+ case kDirWest:
+ leftRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0008_WallNorthRandOrnAllowed);
+ frontRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0004_WallEastRandOrnAllowed);
+ rightRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0002_WallSouthRandOrnAllowed);
+ break;
+ default:
+ assert(false);
+ }
+ _vm->_displayMan->_championPortraitOrdinal = 0;
+ squareIsFakeWall = false;
+T0172010_ClosedFakeWall:
+ setSquareAspectOrnOrdinals(aspectArray, leftRandomWallOrnamentAllowed, frontRandomWallOrnamentAllowed, rightRandomWallOrnamentAllowed, dir, mapX, mapY, squareIsFakeWall);
+ while ((curThing != Thing::_endOfList) && (curThing.getType() <= k3_SensorThingType)) {
+ ThingType curThingType = curThing.getType();
+ int16 AL0310_i_SideIndex = normalizeModulo4(curThing.getCell() - dir);
+ if (AL0310_i_SideIndex) { /* Invisible on the back wall if 0 */
+ Sensor *curSensor = (Sensor *)getThingData(curThing);
+ if (curThingType == k2_TextstringType) {
+ if (((TextString *)curSensor)->isVisible()) {
+ aspectArray[AL0310_i_SideIndex + 1] = _currMapInscriptionWallOrnIndex + 1;
+ _vm->_displayMan->_inscriptionThing = curThing; /* BUG0_76 The same text is drawn on multiple sides of a wall square. The engine stores only a single text to draw on a wall in a global variable. Even if different texts are placed on different sides of the wall, the same text is drawn on each affected side */
+ }
+ } else {
+ aspectArray[AL0310_i_SideIndex + 1] = curSensor->getAttrOrnOrdinal();
+ if (curSensor->getType() == k127_SensorWallChampionPortrait) {
+ _vm->_displayMan->_championPortraitOrdinal = _vm->indexToOrdinal(curSensor->getData());
+ }
+ }
+ }
+ curThing = getNextThing(curThing);
+ }
+ if (squareIsFakeWall && (_partyMapX != mapX) && (_partyMapY != mapY)) {
+ aspectArray[k1_FirstGroupOrObjectAspect] = Thing::_endOfList.toUint16();
+ return;
+ }
+ break;
+ case k6_ElementTypeFakeWall:
+ if (!getFlag(AL0307_uc_Square, k0x0004_FakeWallOpen)) {
+ aspectArray[k0_ElementAspect] = k0_ElementTypeWall;
+ leftRandomWallOrnamentAllowed = rightRandomWallOrnamentAllowed = frontRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0008_FakeWallRandOrnOrFootPAllowed);
+ squareIsFakeWall = true;
+ goto T0172010_ClosedFakeWall;
+ }
+ aspectArray[k0_ElementAspect] = k1_CorridorElemType;
+ AL0307_uc_FootprintsAllowed = getFlag(AL0307_uc_Square, k0x0008_FakeWallRandOrnOrFootPAllowed) ? 8 : 0;
+ // No break on purpose
+ case k1_CorridorElemType:
+ case k2_ElementTypePit:
+ case k5_ElementTypeTeleporter:
+ if (aspectArray[k0_ElementAspect] == k1_CorridorElemType) {
+ aspectArray[k4_FloorOrnOrdAspect] = getRandomOrnOrdinal(getFlag(AL0307_uc_Square, k0x0008_CorridorRandOrnAllowed), _currMap->_randFloorOrnCount, mapX, mapY, 30);
+ AL0307_uc_FootprintsAllowed = true;
+ } else if (aspectArray[k0_ElementAspect] == k2_ElementTypePit) {
+ if (getFlag(AL0307_uc_Square, k0x0008_PitOpen)) {
+ aspectArray[k2_PitInvisibleAspect] = getFlag(AL0307_uc_Square, k0x0004_PitInvisible);
+ AL0307_uc_FootprintsAllowed &= 0x0001;
+ } else {
+ aspectArray[k0_ElementAspect] = k1_CorridorElemType;
+ AL0307_uc_FootprintsAllowed = true;
+ }
+ } else { // k5_ElementTypeTeleporter
+ aspectArray[k2_TeleporterVisibleAspect] = getFlag(AL0307_uc_Square, k0x0008_TeleporterOpen) && getFlag(AL0307_uc_Square, k0x0004_TeleporterVisible);
+ AL0307_uc_FootprintsAllowed = true;
+ }
+
+ while ((curThing != Thing::_endOfList) && (curThing.getType() <= k3_SensorThingType)) {
+ if (curThing.getType() == k3_SensorThingType) {
+ Sensor *curSensor = (Sensor *)getThingData(curThing);
+ aspectArray[k4_FloorOrnOrdAspect] = curSensor->getAttrOrnOrdinal();
+ }
+ curThing = getNextThing(curThing);
+ }
+
+ AL0307_uc_ScentOrdinal = _vm->_championMan->getScentOrdinal(mapX, mapY);
+ if (AL0307_uc_FootprintsAllowed && (AL0307_uc_ScentOrdinal) && (--AL0307_uc_ScentOrdinal >= _vm->_championMan->_party._firstScentIndex) && (AL0307_uc_ScentOrdinal < _vm->_championMan->_party._lastScentIndex))
+ setFlag(aspectArray[k4_FloorOrnOrdAspect], k0x8000_FootprintsAspect);
+
+ break;
+ case k3_ElementTypeStairs:
+ aspectArray[k0_ElementAspect] = (bool((getFlag(AL0307_uc_Square, k0x0008_StairsNorthSouthOrient) >> 3)) == isOrientedWestEast(dir)) ? k18_ElementTypeStairsSide : k19_ElementTypeStaisFront;
+ aspectArray[k2_StairsUpAspect] = getFlag(AL0307_uc_Square, k0x0004_StairsUp);
+ AL0307_uc_FootprintsAllowed = false;
+ while ((curThing != Thing::_endOfList) && (curThing.getType() <= k3_SensorThingType))
+ curThing = getNextThing(curThing);
+ break;
+ case k4_DoorElemType:
+ if (bool((getFlag(AL0307_uc_Square, k0x0008_DoorNorthSouthOrient) >> 3)) == isOrientedWestEast(dir)) {
+ aspectArray[k0_ElementAspect] = k16_DoorSideElemType;
+ } else {
+ aspectArray[k0_ElementAspect] = k17_DoorFrontElemType;
+ aspectArray[k2_DoorStateAspect] = Square(AL0307_uc_Square).getDoorState();
+ aspectArray[k3_DoorThingIndexAspect] = getSquareFirstThing(mapX, mapY).getIndex();
+ }
+ AL0307_uc_FootprintsAllowed = true;
+
+ while ((curThing != Thing::_endOfList) && (curThing.getType() <= k3_SensorThingType))
+ curThing = getNextThing(curThing);
+
+ AL0307_uc_ScentOrdinal = _vm->_championMan->getScentOrdinal(mapX, mapY);
+ if (AL0307_uc_FootprintsAllowed && (AL0307_uc_ScentOrdinal) && (--AL0307_uc_ScentOrdinal >= _vm->_championMan->_party._firstScentIndex) && (AL0307_uc_ScentOrdinal < _vm->_championMan->_party._lastScentIndex))
+ setFlag(aspectArray[k4_FloorOrnOrdAspect], k0x8000_FootprintsAspect);
+ break;
+ }
+ aspectArray[k1_FirstGroupOrObjectAspect] = curThing.toUint16();
+}
+
+void DungeonMan::setSquareAspectOrnOrdinals(uint16 *aspectArray, bool leftAllowed, bool frontAllowed, bool rightAllowed, int16 dir,
+ int16 mapX, int16 mapY, bool isFakeWall) {
+
+ int16 randomWallOrnamentCount = _currMap->_randWallOrnCount;
+ aspectArray[k2_RightWallOrnOrdAspect] = getRandomOrnOrdinal(leftAllowed, randomWallOrnamentCount, mapX, ++mapY * (normalizeModulo4(++dir) + 1), 30);
+ aspectArray[k3_FrontWallOrnOrdAspect] = getRandomOrnOrdinal(frontAllowed, randomWallOrnamentCount, mapX, mapY * (normalizeModulo4(++dir) + 1), 30);
+ aspectArray[k4_LeftWallOrnOrdAspect] = getRandomOrnOrdinal(rightAllowed, randomWallOrnamentCount, mapX, mapY-- * (normalizeModulo4(++dir) + 1), 30);
+ if (isFakeWall || (mapX < 0) || (mapX >= _currMapWidth) || (mapY < 0) || (mapY >= _currMapHeight)) { /* If square is a fake wall or is out of map bounds */
+ for (int16 sideIndex = k2_RightWallOrnOrdAspect; sideIndex <= k4_LeftWallOrnOrdAspect; sideIndex++) { /* Loop to remove any random ornament that is an alcove */
+ if (isWallOrnAnAlcove(_vm->ordinalToIndex(aspectArray[sideIndex])))
+ aspectArray[sideIndex] = 0;
+ }
+ }
+}
+
+int16 DungeonMan::getRandomOrnOrdinal(bool allowed, int16 count, int16 mapX, int16 mapY, int16 modulo) {
+ int16 randomOrnamentIndex = getRandomOrnamentIndex((int16)2000 + (mapX << 5) + mapY, (int16)3000 + (_currMapIndex << (int16)6) + _currMapWidth + _currMapHeight, modulo);
+
+ if (allowed && (randomOrnamentIndex < count))
+ return _vm->indexToOrdinal(randomOrnamentIndex);
+
+ return 0;
+}
+
+
+bool DungeonMan::isWallOrnAnAlcove(int16 wallOrnIndex) {
+ if (wallOrnIndex >= 0) {
+ for (uint16 i = 0; i < k3_AlcoveOrnCount; ++i) {
+ if (_vm->_displayMan->_currMapAlcoveOrnIndices[i] == wallOrnIndex)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+uint16 *DungeonMan::getThingData(Thing thing) {
+ return _thingData[thing.getType()] + thing.getIndex() * _thingDataWordCount[thing.getType()];
+}
+
+uint16 *DungeonMan::getSquareFirstThingData(int16 mapX, int16 mapY) {
+ return getThingData(getSquareFirstThing(mapX, mapY));
+}
+
+Thing DungeonMan::getNextThing(Thing thing) {
+ return Thing(getThingData(thing)[0]);
+}
+
+void DungeonMan::decodeText(char *destString, Thing thing, TextType type) {
+ static char messageAndScrollEscReplacementStrings[32][8] = { // @ G0255_aac_Graphic559_MessageAndScrollEscapeReplacementStrings
+ {'x', 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { '?', 0, 0, 0, 0, 0, 0, 0 }, */
+ {'y', 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { '!', 0, 0, 0, 0, 0, 0, 0 }, */
+ {'T', 'H', 'E', ' ', 0, 0, 0, 0},
+ {'Y', 'O', 'U', ' ', 0, 0, 0, 0},
+ {'z', 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 0, 0, 0, 0, 0, 0, 0, 0 }, */
+ {'{', 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 0, 0, 0, 0, 0, 0, 0, 0 }, */
+ {'|', 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 0, 0, 0, 0, 0, 0, 0, 0 }, */
+ {'}', 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 0, 0, 0, 0, 0, 0, 0, 0 }, */
+ {'~', 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 0, 0, 0, 0, 0, 0, 0, 0 }, */
+ {'', 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 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, 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, 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, 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, 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, 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, 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, 0},
+ {0, 0, 0, 0, 0, 0, 0, 0}
+ };
+
+ static char escReplacementCharacters[32][2] = { // @ G0256_aac_Graphic559_EscapeReplacementCharacters
+ {'a', 0}, {'b', 0}, {'c', 0}, {'d', 0},
+ {'e', 0}, {'f', 0}, {'g', 0}, {'h', 0},
+ {'i', 0}, {'j', 0}, {'k', 0}, {'l', 0},
+ {'m', 0}, {'n', 0}, {'o', 0}, {'p', 0},
+ {'q', 0}, {'r', 0}, {'s', 0}, {'t', 0},
+ {'u', 0}, {'v', 0}, {'w', 0}, {'x', 0},
+ {'0', 0}, {'1', 0}, {'2', 0}, {'3', 0},
+ {'4', 0}, {'5', 0}, {'6', 0}, {'7', 0}
+ };
+
+ static char inscriptionEscReplacementStrings[32][8] = { // @ G0257_aac_Graphic559_InscriptionEscapeReplacementStrings
+ {28, 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 0, 0, 0, 0, 0, 0, 0, 0 }, */
+ {29, 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 0, 0, 0, 0, 0, 0, 0, 0 }, */
+ {19, 7, 4, 26, 0, 0, 0, 0},
+ {24, 14, 20, 26, 0, 0, 0, 0},
+ {30, 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 0, 0, 0, 0, 0, 0, 0, 0 }, */
+ {31, 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 0, 0, 0, 0, 0, 0, 0, 0 }, */
+ {32, 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 0, 0, 0, 0, 0, 0, 0, 0 }, */
+ {33, 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 0, 0, 0, 0, 0, 0, 0, 0 }, */
+ {34, 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 0, 0, 0, 0, 0, 0, 0, 0 }, */
+ {35, 0, 0, 0, 0, 0, 0, 0}, /* Atari ST Version 1.0 1987-12-08 1987-12-11 1.1 1.2EN 1.2GE: { 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, 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, 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, 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, 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, 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, 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, 0},
+ {0, 0, 0, 0, 0, 0, 0, 0}
+ };
+
+ TextString textString(_thingData[k2_TextstringType] + thing.getIndex() * _thingDataWordCount[k2_TextstringType]);
+ if ((textString.isVisible()) || (type & k0x8000_DecodeEvenIfInvisible)) {
+ type = (TextType)(type & ~k0x8000_DecodeEvenIfInvisible);
+ char sepChar;
+ if (type == k1_TextTypeMessage) {
+ *destString++ = '\n';
+ sepChar = ' ';
+ } else if (type == k0_TextTypeInscription) {
+ sepChar = (char)0x80;
+ } else {
+ sepChar = '\n';
+ }
+ uint16 codeCounter = 0;
+ int16 escChar = 0;
+ uint16 *codeWord = _dungeonTextData + textString.getWordOffset();
+ uint16 code = 0, codes = 0;
+ char *escReplString = nullptr;
+ for (;;) { /*infinite loop*/
+ if (!codeCounter) {
+ codes = *codeWord++;
+ code = (codes >> 10) & 0x1F;
+ } else if (codeCounter == 1) {
+ code = (codes >> 5) & 0x1F;
+ } else {
+ code = codes & 0x1F;
+ }
+ ++codeCounter;
+ codeCounter %= 3;
+
+ if (escChar) {
+ *destString = '\0';
+ if (escChar == 30) {
+ if (type != k0_TextTypeInscription)
+ escReplString = messageAndScrollEscReplacementStrings[code];
+ else
+ escReplString = inscriptionEscReplacementStrings[code];
+ } else
+ escReplString = escReplacementCharacters[code];
+
+ strcat(destString, escReplString);
+ destString += strlen(escReplString);
+ escChar = 0;
+ } else if (code < 28) {
+ if (type != k0_TextTypeInscription) {
+ if (code == 26)
+ code = ' ';
+ else if (code == 27)
+ code = '.';
+ else
+ code += 'A';
+ }
+ *destString++ = code;
+ } else if (code == 28)
+ *destString++ = sepChar;
+ else if (code <= 30)
+ escChar = code;
+ else
+ break;
+ }
+ }
+ *destString = ((type == k0_TextTypeInscription) ? 0x81 : '\0');
+}
+
+Thing DungeonMan::getUnusedThing(uint16 thingType) {
+ int16 thingCount = _dungeonFileHeader._thingCounts[getFlag(thingType, k0x7FFF_thingType)];
+ if (thingType == (k0x8000_championBones | k10_JunkThingType)) {
+ thingType = k10_JunkThingType;
+ } else if (thingType == k10_JunkThingType)
+ thingCount -= 3; /* Always keep 3 unused JUNK things for the bones of dead champions */
+
+ int16 thingIdx = thingCount;
+ int16 thingDataByteCount = _thingDataWordCount[thingType] >> 1;
+ Thing *thingPtr = (Thing *)_thingData[thingType];
+
+ Thing curThing;
+ for (;;) { /*_Infinite loop_*/
+ if (*thingPtr == Thing::_none) { /* If thing data is unused */
+ curThing = Thing((thingType << 10) | (thingCount - thingIdx));
+ break;
+ }
+ if (--thingIdx) { /* If there are thing data left to process */
+ thingPtr += thingDataByteCount; /* Proceed to the next thing data */
+ } else {
+ curThing = getDiscardThing(thingType);
+ if (curThing == Thing::_none)
+ return Thing::_none;
+
+ thingPtr = (Thing *)getThingData(curThing);
+ break;
+ }
+ }
+ memset(thingPtr, 0, thingDataByteCount * 2);
+
+ *thingPtr = Thing::_endOfList;
+ return curThing;
+}
+
+uint16 DungeonMan::getObjectWeight(Thing thing) {
+ static const uint16 junkInfo[] = { // @ G0241_auc_Graphic559_JunkInfo
+ // COMPASS - WATERSKIN - JEWEL SYMAL - ILLUMULET - ASHES
+ 1, 3, 2, 2, 4,
+ // BONES - COPPER COIN - SILVER COIN - GOLD COIN - IRON KEY
+ 15, 1, 1, 1, 2,
+ // KEY OF B - SOLID KEY - SQUARE KEY - TOURQUOISE KEY - CROSS KEY
+ 1, 1, 1, 1, 1,
+ // ONYX KEY - SKELETON KEY - GOLD KEY - WINGED KEY - TOPAZ KEY
+ 1, 1, 1, 1, 1,
+ // SAPPHIRE KEY - EMERALD KEY - RUBY KEY - RA KEY - MASTER KEY
+ 1, 1, 1, 1, 1,
+ // BOULDER - BLUE GEM - ORANGE GEM - GREEN GEM - APPLE
+ 81, 2, 3, 2, 4,
+ // CORN - BREAD - CHEESE - SCREAMER SLICE - WORM ROUND
+ 4, 3, 8, 5, 11,
+ // DRUMSTICK - DRAGON STEAK - GEM OF AGES - EKKHARD CROSS - MOONSTONE
+ 4, 6, 2, 3, 2,
+ // THE HELLION - PENDANT FERAL - MAGICAL BOX - MAGICAL BOX - MIRROR OF DAWN
+ 2, 2, 6, 9, 3,
+ // ROPE - RABBIT'S FOOT - CORBAMITE - CHOKER - LOCK PICKS
+ 10, 1, 0, 1, 1,
+ // MAGNIFIER - ZOKATHRA SPELL - BONES
+ 2, 0, 8
+ };
+
+ if (thing == Thing::_none)
+ return 0;
+
+ // Initialization is not present in original
+ // Set to 0 by default as it's the default value used for Thing::_none
+ uint16 weight = 0;
+ Junk *junk = (Junk *)getThingData(thing);
+
+ switch (thing.getType()) {
+ case k5_WeaponThingType:
+ weight = _weaponInfos[((Weapon *)junk)->getType()]._weight;
+ break;
+ case k6_ArmourThingType:
+ weight = _armourInfos[((Armour *)junk)->getType()]._weight;
+ break;
+ case k10_JunkThingType:
+ weight = junkInfo[junk->getType()];
+ if (junk->getType() == k1_JunkTypeWaterskin)
+ weight += junk->getChargeCount() << 1;
+
+ break;
+ case k9_ContainerThingType:
+ weight = 50;
+ thing = ((Container *)junk)->getSlot();
+ while (thing != Thing::_endOfList) {
+ weight += getObjectWeight(thing);
+ thing = getNextThing(thing);
+ }
+ break;
+ case k8_PotionThingType:
+ if (((Potion *)junk)->getType() == k20_PotionTypeEmptyFlask)
+ weight = 1;
+ else
+ weight = 3;
+ break;
+ case k7_ScrollThingType:
+ weight = 1;
+ break;
+ default:
+ break;
+ }
+
+ return weight; // this is garbage if none of the branches were taken
+}
+
+int16 DungeonMan::getObjectInfoIndex(Thing thing) {
+ uint16 *rawType = getThingData(thing);
+ switch (thing.getType()) {
+ case k7_ScrollThingType:
+ return k0_ObjectInfoIndexFirstScroll;
+ case k9_ContainerThingType:
+ return k1_ObjectInfoIndexFirstContainer + Container(rawType).getType();
+ case k10_JunkThingType:
+ return k127_ObjectInfoIndexFirstJunk + Junk(rawType).getType();
+ case k5_WeaponThingType:
+ return k23_ObjectInfoIndexFirstWeapon + Weapon(rawType).getType();
+ case k6_ArmourThingType:
+ return k69_ObjectInfoIndexFirstArmour + Armour(rawType).getType();
+ case k8_PotionThingType:
+ return k2_ObjectInfoIndexFirstPotion + Potion(rawType).getType();
+ default:
+ return -1;
+ }
+}
+
+void DungeonMan::linkThingToList(Thing thingToLink, Thing thingInList, int16 mapX, int16 mapY) {
+ if (thingToLink == Thing::_endOfList)
+ return;
+
+ Thing *thingPtr = (Thing *)getThingData(thingToLink);
+ *thingPtr = Thing::_endOfList;
+ /* If mapX >= 0 then the thing is linked to the list of things on the specified square else it is linked at the end of the specified thing list */
+ if (mapX >= 0) {
+ byte *currSquare = &_currMapData[mapX][mapY];
+ if (getFlag(*currSquare, k0x0010_ThingListPresent)) {
+ thingInList = getSquareFirstThing(mapX, mapY);
+ } else {
+ setFlag(*currSquare, k0x0010_ThingListPresent);
+ uint16 *tmp = _currMapColCumulativeSquareFirstThingCount + mapX + 1;
+ uint16 currColumn = _dungeonColumCount - (_dungeonMapsFirstColumnIndex[_currMapIndex] + mapX) - 1;
+ while (currColumn--) { /* For each column starting from and after the column containing the square where the thing is added */
+ (*tmp++)++; /* Increment the cumulative first thing count */
+ }
+ uint16 currMapY = 0;
+ currSquare -= mapY;
+ uint16 currSquareFirstThingIndex = _currMapColCumulativeSquareFirstThingCount[mapX];
+ while (currMapY++ != mapY) {
+ if (getFlag(*currSquare++, k0x0010_ThingListPresent))
+ currSquareFirstThingIndex++;
+ }
+ Thing *currThing = &_squareFirstThings[currSquareFirstThingIndex];
+ // the second '- 1' is for the loop initialization, > 0 is because we are copying from one behind
+ for (int16 i = _dungeonFileHeader._squareFirstThingCount - currSquareFirstThingIndex - 1 - 1; i > 0; --i)
+ currThing[i] = currThing[i - 1];
+
+ *currThing = thingToLink;
+ return;
+ }
+ }
+ Thing nextThing = getNextThing(thingInList);
+ while (nextThing != Thing::_endOfList)
+ nextThing = getNextThing(thingInList = nextThing);
+
+ thingPtr = (Thing *)getThingData(thingInList);
+ *thingPtr = thingToLink;
+}
+
+WeaponInfo *DungeonMan::getWeaponInfo(Thing thing) {
+ Weapon *weapon = (Weapon *)getThingData(thing);
+ return &_weaponInfos[weapon->getType()];
+}
+
+int16 DungeonMan::getProjectileAspect(Thing thing) {
+ ThingType thingType = thing.getType();
+ if (thingType == k15_ExplosionThingType) {
+ if (thing == Thing::_explFireBall)
+ return -_vm->indexToOrdinal(k10_ProjectileAspectExplosionFireBall);
+ if (thing == Thing::_explSlime)
+ return -_vm->indexToOrdinal(k12_ProjectileAspectExplosionSlime);
+ if (thing == Thing::_explLightningBolt)
+ return -_vm->indexToOrdinal(k3_ProjectileAspectExplosionLightningBolt);
+ if ((thing == Thing::_explPoisonBolt) || (thing == Thing::_explPoisonCloud))
+ return -_vm->indexToOrdinal(k13_ProjectileAspectExplosionPoisonBoltCloud);
+
+ return -_vm->indexToOrdinal(k11_ProjectileAspectExplosionDefault);
+ } else if (thingType == k5_WeaponThingType) {
+ WeaponInfo *weaponInfo = getWeaponInfo(thing);
+ int16 projAspOrd = weaponInfo->getProjectileAspectOrdinal();
+ if (projAspOrd)
+ return -projAspOrd;
+ }
+
+ return _objectInfos[getObjectInfoIndex(thing)]._objectAspectIndex;
+}
+
+int16 DungeonMan::getLocationAfterLevelChange(int16 mapIndex, int16 levelDelta, int16 *mapX, int16 *mapY) {
+ if (_partyMapIndex == k255_mapIndexEntrance)
+ return kM1_mapIndexNone;
+
+ Map *map = _dungeonMaps + mapIndex;
+ int16 newMapX = map->_offsetMapX + *mapX;
+ int16 newMapY = map->_offsetMapY + *mapY;
+ int16 newLevel = map->_level + levelDelta;
+ map = _dungeonMaps;
+
+ for (int16 targetMapIndex = 0; targetMapIndex < _dungeonFileHeader._mapCount; targetMapIndex++) {
+ if ((map->_level == newLevel)
+ && (newMapX >= map->_offsetMapX) && (newMapX <= map->_offsetMapX + map->_width)
+ && (newMapY >= map->_offsetMapY) && (newMapY <= map->_offsetMapY + map->_height)) {
+ *mapY = newMapY - map->_offsetMapY;
+ *mapX = newMapX - map->_offsetMapX;
+ return targetMapIndex;
+ }
+ map++;
+ }
+ return kM1_mapIndexNone;
+}
+
+Thing DungeonMan::getSquareFirstObject(int16 mapX, int16 mapY) {
+ Thing thing = getSquareFirstThing(mapX, mapY);
+ while ((thing != Thing::_endOfList) && (thing.getType() < k4_GroupThingType))
+ thing = getNextThing(thing);
+
+ return thing;
+}
+
+uint16 DungeonMan::getArmourDefense(ArmourInfo *armourInfo, bool useSharpDefense) {
+ uint16 defense = armourInfo->_defense;
+ if (useSharpDefense)
+ defense = _vm->getScaledProduct(defense, 3, getFlag(armourInfo->_attributes, k0x0007_ArmourAttributeSharpDefense) + 4);
+
+ return defense;
+}
+
+Thing DungeonMan::getDiscardThing(uint16 thingType) {
+ // CHECKME: Shouldn't it be saved in the savegames?
+ static unsigned char lastDiscardedThingMapIndex[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ if (thingType == k15_ExplosionThingType)
+ return Thing::_none;
+
+ int16 currentMapIdx = _currMapIndex;
+ uint16 mapIndex = lastDiscardedThingMapIndex[thingType];
+ if ((mapIndex == _partyMapIndex) && (++mapIndex >= _dungeonFileHeader._mapCount))
+ mapIndex = 0;
+
+ uint16 discardThingMapIndex = mapIndex;
+ for (;;) { /*_Infinite loop_*/
+ uint16 mapWidth = _dungeonMaps[mapIndex]._width;
+ uint16 mapHeight = _dungeonMaps[mapIndex]._height;
+ byte *currSquare = _dungeonMapData[mapIndex][0];
+ Thing *squareFirstThing = &_squareFirstThings[_dungeonColumnsCumulativeSquareThingCount[_dungeonMapsFirstColumnIndex[mapIndex]]];
+
+ for (int16 currMapX = 0; currMapX <= mapWidth; currMapX++) {
+ for (int16 currMapY = 0; currMapY <= mapHeight; currMapY++) {
+ if (getFlag(*currSquare++, k0x0010_ThingListPresent)) {
+ Thing squareThing = *squareFirstThing++;
+ if ((mapIndex == _partyMapIndex) && ((currMapX - _partyMapX + 5) <= 10) && ((currMapY - _partyMapY + 5) <= 10)) /* If square is too close to the party */
+ continue;
+
+ do {
+ ThingType squareThingType = squareThing.getType();
+ if (squareThingType == k3_SensorThingType) {
+ Thing *squareThingData = (Thing *)getThingData(squareThing);
+ if (((Sensor *)squareThingData)->getType()) /* If sensor is not disabled */
+ break;
+ } else if (squareThingType == thingType) {
+ Thing *squareThingData = (Thing *)getThingData(squareThing);
+ switch (thingType) {
+ case k4_GroupThingType:
+ if (((Group *)squareThingData)->getDoNotDiscard())
+ continue;
+ case k14_ProjectileThingType:
+ setCurrentMap(mapIndex);
+ if (thingType == k4_GroupThingType) {
+ _vm->_groupMan->dropGroupPossessions(currMapX, currMapY, squareThing, kM1_soundModeDoNotPlaySound);
+ _vm->_groupMan->groupDelete(currMapX, currMapY);
+ } else {
+ _vm->_projexpl->projectileDeleteEvent(squareThing);
+ unlinkThingFromList(squareThing, Thing(0), currMapX, currMapY);
+ _vm->_projexpl->projectileDelete(squareThing, 0, currMapX, currMapY);
+ }
+ break;
+ case k6_ArmourThingType:
+ if (((Armour *)squareThingData)->getDoNotDiscard())
+ continue;
+
+ setCurrentMap(mapIndex);
+ _vm->_moveSens->getMoveResult(squareThing, currMapX, currMapY, kM1_MapXNotOnASquare, 0);
+ break;
+ case k5_WeaponThingType:
+ if (((Weapon *)squareThingData)->getDoNotDiscard())
+ continue;
+
+ setCurrentMap(mapIndex);
+ _vm->_moveSens->getMoveResult(squareThing, currMapX, currMapY, kM1_MapXNotOnASquare, 0);
+ break;
+ case k10_JunkThingType:
+ if (((Junk *)squareThingData)->getDoNotDiscard())
+ continue;
+
+ setCurrentMap(mapIndex);
+ _vm->_moveSens->getMoveResult(squareThing, currMapX, currMapY, kM1_MapXNotOnASquare, 0);
+ break;
+ case k8_PotionThingType:
+ if (((Potion *)squareThingData)->getDoNotDiscard())
+ continue;
+
+ setCurrentMap(mapIndex);
+ _vm->_moveSens->getMoveResult(squareThing, currMapX, currMapY, kM1_MapXNotOnASquare, 0);
+ break;
+ }
+ setCurrentMap(currentMapIdx);
+ lastDiscardedThingMapIndex[thingType] = mapIndex;
+ return Thing(squareThing.getTypeAndIndex());
+ }
+ } while ((squareThing = getNextThing(squareThing)) != Thing::_endOfList);
+ }
+ }
+ }
+ if ((mapIndex == _partyMapIndex) || (_dungeonFileHeader._mapCount <= 1)) {
+ lastDiscardedThingMapIndex[thingType] = mapIndex;
+ return Thing::_none;
+ }
+
+ do {
+ if (++mapIndex >= _dungeonFileHeader._mapCount)
+ mapIndex = 0;
+ } while (mapIndex == _partyMapIndex);
+
+ if (mapIndex == discardThingMapIndex)
+ mapIndex = _partyMapIndex;
+ }
+}
+
+uint16 DungeonMan::getCreatureAttributes(Thing thing) {
+ Group *currGroup = (Group *)getThingData(thing);
+ return _creatureInfos[currGroup->_type]._attributes;
+}
+
+void DungeonMan::setGroupCells(Group *group, uint16 cells, uint16 mapIndex) {
+ if (mapIndex == _partyMapIndex)
+ _vm->_groupMan->_activeGroups[group->getActiveGroupIndex()]._cells = cells;
+ else
+ group->_cells = cells;
+}
+
+void DungeonMan::setGroupDirections(Group *group, int16 dir, uint16 mapIndex) {
+ if (mapIndex == _partyMapIndex)
+ _vm->_groupMan->_activeGroups[group->getActiveGroupIndex()]._directions = (Direction)dir;
+ else
+ group->setDir(normalizeModulo4(dir));
+}
+
+bool DungeonMan::isCreatureAllowedOnMap(Thing thing, uint16 mapIndex) {
+ int16 creatureType = ((Group *)getThingData(thing))->_type;
+ Map *map = &_dungeonMaps[mapIndex];
+ byte *allowedCreatureType = _dungeonMapData[mapIndex][map->_width] + map->_height + 1;
+ for (int16 L0234_i_Counter = map->_creatureTypeCount; L0234_i_Counter > 0; L0234_i_Counter--) {
+ if (*allowedCreatureType++ == creatureType)
+ return true;
+ }
+ return false;
+}
+
+void DungeonMan::unlinkThingFromList(Thing thingToUnlink, Thing thingInList, int16 mapX, int16 mapY) {
+ if (thingToUnlink == Thing::_endOfList)
+ return;
+
+ uint16 tmp = thingToUnlink.toUint16();
+ clearFlag(tmp, 0xC000);
+ thingToUnlink = Thing(tmp);
+
+ Thing *thingPtr = nullptr;
+ if (mapX >= 0) {
+ thingPtr = (Thing *)getThingData(thingToUnlink);
+ uint16 firstThingIndex = getSquareFirstThingIndex(mapX, mapY);
+ Thing *currThing = &_squareFirstThings[firstThingIndex]; /* BUG0_01 Coding error without consequence. The engine does not check that there are things at the specified square coordinates. f160_getSquareFirstThingIndex would return -1 for an empty square. No consequence as the function is never called with the coordinates of an empty square (except in the case of BUG0_59) */
+ if ((*thingPtr == Thing::_endOfList) && (((Thing *)currThing)->getTypeAndIndex() == thingToUnlink.toUint16())) { /* If the thing to unlink is the last thing on the square */
+ clearFlag(_currMapData[mapX][mapY], k0x0010_ThingListPresent);
+ uint16 squareFirstThingIdx = _dungeonFileHeader._squareFirstThingCount - 1;
+ for (uint16 i = 0; i < squareFirstThingIdx - firstThingIndex; ++i)
+ currThing[i] = currThing[i + 1];
+
+ _squareFirstThings[squareFirstThingIdx] = Thing::_none;
+ uint16 *cumulativeFirstThingCount = _currMapColCumulativeSquareFirstThingCount + mapX + 1;
+ uint16 currColumn = _dungeonColumCount - (_dungeonMapsFirstColumnIndex[_currMapIndex] + mapX) - 1;
+ while (currColumn--) { /* For each column starting from and after the column containing the square where the thing is unlinked */
+ (*cumulativeFirstThingCount++)--; /* Decrement the cumulative first thing count */
+ }
+ *thingPtr = Thing::_endOfList;
+ return;
+ }
+ if (((Thing *)currThing)->getTypeAndIndex() == thingToUnlink.toUint16()) {
+ *currThing = *thingPtr;
+ *thingPtr = Thing::_endOfList;
+ return;
+ }
+ thingInList = *currThing;
+ }
+
+ Thing currThing = getNextThing(thingInList);
+ while (currThing.getTypeAndIndex() != thingToUnlink.toUint16()) {
+ if ((currThing == Thing::_endOfList) || (currThing == Thing::_none)) {
+ *thingPtr = Thing::_endOfList;
+ return;
+ }
+ currThing = getNextThing(thingInList = currThing);
+ }
+ thingPtr = (Thing *)getThingData(thingInList);
+ *thingPtr = getNextThing(currThing);
+ thingPtr = (Thing *)getThingData(thingToUnlink);
+ *thingPtr = Thing::_endOfList;
+}
+
+int16 DungeonMan::getStairsExitDirection(int16 mapX, int16 mapY) {
+ bool northSouthOrientedStairs = !getFlag(getSquare(mapX, mapY).toByte(), k0x0008_StairsNorthSouthOrient);
+
+ if (northSouthOrientedStairs) {
+ mapX = mapX + _vm->_dirIntoStepCountEast[kDirEast];
+ mapY = mapY + _vm->_dirIntoStepCountNorth[kDirEast];
+ } else {
+ mapX = mapX + _vm->_dirIntoStepCountEast[kDirNorth];
+ mapY = mapY + _vm->_dirIntoStepCountNorth[kDirNorth];
+ }
+ int16 squareType = Square(getSquare(mapX, mapY)).getType();
+
+ int16 retval = ((squareType == k0_ElementTypeWall) || (squareType == k3_ElementTypeStairs)) ? 1 : 0;
+ retval <<= 1;
+ retval += (northSouthOrientedStairs ? 1 : 0);
+
+ return retval;
+}
+
+Thing DungeonMan::getObjForProjectileLaucherOrObjGen(uint16 iconIndex) {
+ int16 thingType = k5_WeaponThingType;
+ if ((iconIndex >= kDMIconIndiceWeaponTorchUnlit) && (iconIndex <= kDMIconIndiceWeaponTorchLit))
+ iconIndex = kDMIconIndiceWeaponTorchUnlit;
+
+ int16 junkType;
+
+ switch (iconIndex) {
+ case kDMIconIndiceWeaponRock:
+ junkType = k30_WeaponTypeRock;
+ break;
+ case kDMIconIndiceJunkBoulder:
+ junkType = k25_JunkTypeBoulder;
+ thingType = k10_JunkThingType;
+ break;
+ case kDMIconIndiceWeaponArrow:
+ junkType = k27_WeaponTypeArrow;
+ break;
+ case kDMIconIndiceWeaponSlayer:
+ junkType = k28_WeaponTypeSlayer;
+ break;
+ case kDMIconIndiceWeaponPoisonDart:
+ junkType = k31_WeaponTypePoisonDart;
+ break;
+ case kDMIconIndiceWeaponThrowingStar:
+ junkType = k32_WeaponTypeThrowingStar;
+ break;
+ case kDMIconIndiceWeaponDagger:
+ junkType = k8_WeaponTypeDagger;
+ break;
+ case kDMIconIndiceWeaponTorchUnlit:
+ junkType = k2_WeaponTypeTorch;
+ break;
+ default:
+ return Thing::_none;
+ }
+
+ Thing unusedThing = getUnusedThing(thingType);
+ if (unusedThing == Thing::_none)
+ return Thing::_none;
+
+ Junk *junkPtr = (Junk *)getThingData(unusedThing);
+ junkPtr->setType(junkType); /* Also works for WEAPON in cases other than Boulder */
+ if ((iconIndex == kDMIconIndiceWeaponTorchUnlit) && ((Weapon *)junkPtr)->isLit()) /* BUG0_65 Torches created by object generator or projectile launcher sensors have no charges. Charges are only defined if the Torch is lit which is not possible at the time it is created */
+ ((Weapon *)junkPtr)->setChargeCount(15);
+
+ return unusedThing;
+}
+
+int16 DungeonMan::getRandomOrnamentIndex(uint16 val1, uint16 val2, int16 modulo) {
+ // TODO: Use ScummVM random number generator
+ return ((((((val1 * 31417) & 0xFFFF) >> 1) + ((val2 * 11) & 0xFFFF)
+ + _dungeonFileHeader._ornamentRandomSeed) & 0xFFFF) >> 2) % modulo; /* Pseudorandom number generator */
+}
+
+}
diff --git a/engines/dm/dungeonman.h b/engines/dm/dungeonman.h
new file mode 100644
index 0000000000..100ce75f68
--- /dev/null
+++ b/engines/dm/dungeonman.h
@@ -0,0 +1,740 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_DUNGEONMAN_H
+#define DM_DUNGEONMAN_H
+
+#include "dm/dm.h"
+#include "dm/gfx.h"
+
+namespace DM {
+
+/* Object info */
+#define k0_ObjectInfoIndexFirstScroll 0 // @ C000_OBJECT_INFO_INDEX_FIRST_SCROLL
+#define k1_ObjectInfoIndexFirstContainer 1 // @ C001_OBJECT_INFO_INDEX_FIRST_CONTAINER
+#define k2_ObjectInfoIndexFirstPotion 2 // @ C002_OBJECT_INFO_INDEX_FIRST_POTION
+#define k23_ObjectInfoIndexFirstWeapon 23 // @ C023_OBJECT_INFO_INDEX_FIRST_WEAPON
+#define k69_ObjectInfoIndexFirstArmour 69 // @ C069_OBJECT_INFO_INDEX_FIRST_ARMOUR
+#define k127_ObjectInfoIndexFirstJunk 127 // @ C127_OBJECT_INFO_INDEX_FIRST_JUNK
+
+#define kM1_MapXNotOnASquare -1 // @ CM1_MAPX_NOT_ON_A_SQUARE
+
+enum ElementType {
+ kM2_ElementTypeChampion = -2, // @ CM2_ELEMENT_CHAMPION /* Values -2 and -1 are only used as projectile impact types */
+ kM1_ElementTypeCreature = -1, // @ CM1_ELEMENT_CREATURE
+ k0_ElementTypeWall = 0, // @ C00_ELEMENT_WALL /* Values 0-6 are used as square types and projectile impact types. Values 0-2 and 5-6 are also used for square aspect */
+ k1_ElementTypeCorridor = 1, // @ C01_ELEMENT_CORRIDOR
+ k2_ElementTypePit = 2, // @ C02_ELEMENT_PIT
+ k3_ElementTypeStairs = 3, // @ C03_ELEMENT_STAIRS
+ // TODO: refactor direction into a class
+ k4_ElementTypeDoor = 4, // @ C04_ELEMENT_DOOR
+ k5_ElementTypeTeleporter = 5, // @ C05_ELEMENT_TELEPORTER
+ k6_ElementTypeFakeWall = 6, // @ C06_ELEMENT_FAKEWALL
+ k16_ElementTypeDoorSide = 16, // @ C16_ELEMENT_DOOR_SIDE /* Values 16-19 are only used for square aspect */
+ k17_ElementTypeDoorFront = 17, // @ C17_ELEMENT_DOOR_FRONT
+ k18_ElementTypeStairsSide = 18, // @ C18_ELEMENT_STAIRS_SIDE
+ k19_ElementTypeStaisFront = 19 // @ C19_ELEMENT_STAIRS_FRONT
+};
+
+
+enum ObjectAllowedSlot {
+ k0x0001_ObjectAllowedSlotMouth = 0x0001, // @ MASK0x0001_MOUTH
+ k0x0002_ObjectAllowedSlotHead = 0x0002, // @ MASK0x0002_HEAD
+ k0x0004_ObjectAllowedSlotNeck = 0x0004, // @ MASK0x0004_NECK
+ k0x0008_ObjectAllowedSlotTorso = 0x0008, // @ MASK0x0008_TORSO
+ k0x0010_ObjectAllowedSlotLegs = 0x0010, // @ MASK0x0010_LEGS
+ k0x0020_ObjectAllowedSlotFeet = 0x0020, // @ MASK0x0020_FEET
+ k0x0040_ObjectAllowedSlotQuiverLine_1 = 0x0040, // @ MASK0x0040_QUIVER_LINE1
+ k0x0080_ObjectAllowedSlotQuiverLine_2 = 0x0080, // @ MASK0x0080_QUIVER_LINE2
+ k0x0100_ObjectAllowedSlotPouchPassAndThroughDoors = 0x0100, // @ MASK0x0100_POUCH_PASS_AND_THROUGH_DOORS
+ k0x0200_ObjectAllowedSlotHands = 0x0200, // @ MASK0x0200_HANDS
+ k0x0400_ObjectAllowedSlotContainer = 0x0400 // @ MASK0x0400_CONTAINER
+};
+
+class ObjectInfo {
+public:
+ int16 _type;
+ uint16 _objectAspectIndex;
+ uint16 _actionSetIndex;
+ uint16 _allowedSlots;
+ ObjectInfo(int16 type, uint16 objectAspectIndex, uint16 actionSetIndex, uint16 allowedSlots)
+ : _type(type), _objectAspectIndex(objectAspectIndex), _actionSetIndex(actionSetIndex), _allowedSlots(allowedSlots) {}
+ ObjectInfo() : _type(0), _objectAspectIndex(0), _actionSetIndex(0), _allowedSlots(0) {}
+ bool getAllowedSlot(ObjectAllowedSlot slot) { return _allowedSlots & slot; }
+ uint16 getAllowedSlots() { return _allowedSlots; }
+ void setAllowedSlot(ObjectAllowedSlot slot, bool val) {
+ if (val) {
+ _allowedSlots |= slot;
+ } else {
+ _allowedSlots &= ~slot;
+ }
+ }
+}; // @ OBJECT_INFO
+
+enum ArmourAttribute {
+ k0x0080_ArmourAttributeIsAShield = 0x0080, // @ MASK0x0080_IS_A_SHIELD
+ k0x0007_ArmourAttributeSharpDefense = 0x0007 // @ MASK0x0007_SHARP_DEFENSE
+};
+
+class ArmourInfo {
+public:
+ uint16 _weight;
+ uint16 _defense;
+ uint16 _attributes;
+
+ ArmourInfo(uint16 weight, uint16 defense, uint16 attributes)
+ :_weight(weight), _defense(defense), _attributes(attributes) {}
+ ArmourInfo() :_weight(0), _defense(0), _attributes(0) {}
+
+ uint16 getAttribute(ArmourAttribute attribute) { return _attributes & attribute; }
+ void setAttribute(ArmourAttribute attribute) { _attributes |= attribute; }
+}; // @ ARMOUR_INFO
+
+#define kM1_WeaponClassNone -1
+/* Class 0: SWING weapons */
+#define k0_WeaponClassSwingWeapon 0 // @ C000_CLASS_SWING_WEAPON
+/* Class 1 to 15: THROW weapons */
+#define k2_WeaponClassDaggerAndAxes 2 // @ C002_CLASS_DAGGER_AND_AXES
+#define k10_WeaponClassBowAmmunition 10 // @ C010_CLASS_BOW_AMMUNITION
+#define k11_WeaponClassSlingAmmunition 11 // @ C011_CLASS_SLING_AMMUNITION
+#define k12_WeaponClassPoisinDart 12 // @ C012_CLASS_POISON_DART
+/* Class 16 to 111: SHOOT weapons */
+#define k16_WeaponClassFirstBow 16 // @ C016_CLASS_FIRST_BOW
+#define k31_WeaponClassLastBow 31 // @ C031_CLASS_LAST_BOW
+#define k32_WeaponClassFirstSling 32 // @ C032_CLASS_FIRST_SLING
+#define k47_WeaponClassLastSling 47 // @ C047_CLASS_LAST_SLING
+/* Class 112 to 255: Magic and special weapons */
+#define k112_WeaponClassFirstMagicWeapon 112 // @ C112_CLASS_FIRST_MAGIC_WEAPON
+
+class WeaponInfo {
+
+public:
+ uint16 _weight;
+ uint16 _class;
+ uint16 _strength;
+ uint16 _kineticEnergy;
+private:
+ uint16 _attributes; /* Bits 15-13 Unreferenced */
+public:
+ WeaponInfo(uint16 weight, uint16 wClass, uint16 strength, uint16 kineticEnergy, uint16 attributes)
+ : _weight(weight), _class(wClass), _strength(strength), _kineticEnergy(kineticEnergy), _attributes(attributes) {}
+ WeaponInfo() : _weight(0), _class(0), _strength(0), _kineticEnergy(0), _attributes(0) {}
+
+ uint16 getShootAttack() { return _attributes & 0xFF; } // @ M65_SHOOT_ATTACK
+ uint16 getProjectileAspectOrdinal() { return (_attributes >> 8) & 0x1F; } // @ M66_PROJECTILE_ASPECT_ORDINAL
+}; // @ WEAPON_INFO
+
+enum TextType {
+ /* Used for text on walls */
+ k0_TextTypeInscription = 0, // @ C0_TEXT_TYPE_INSCRIPTION
+ /* Used for messages displayed when the party walks on a square */
+ k1_TextTypeMessage = 1, // @ C1_TEXT_TYPE_MESSAGE
+ /* Used for text on scrolls and champion information */
+ k2_TextTypeScroll = 2 // @ C2_TEXT_TYPE_SCROLL
+};
+
+enum SquareAspectIndice {
+ k0_ElementAspect = 0, // @ C0_ELEMENT
+ k1_FirstGroupOrObjectAspect = 1, // @ C1_FIRST_GROUP_OR_OBJECT
+ k2_RightWallOrnOrdAspect = 2, // @ C2_RIGHT_WALL_ORNAMENT_ORDINAL
+ k3_FrontWallOrnOrdAspect = 3, // @ C3_FRONT_WALL_ORNAMENT_ORDINAL
+ k4_LeftWallOrnOrdAspect = 4, // @ C4_LEFT_WALL_ORNAMENT_ORDINAL
+ k2_PitInvisibleAspect = 2, // @ C2_PIT_INVISIBLE
+ k2_TeleporterVisibleAspect = 2, // @ C2_TELEPORTER_VISIBLE
+ k2_StairsUpAspect = 2, // @ C2_STAIRS_UP
+ k2_DoorStateAspect = 2, // @ C2_DOOR_STATE
+ k3_DoorThingIndexAspect = 3, // @ C3_DOOR_THING_INDEX
+ k4_FloorOrnOrdAspect = 4, // @ C4_FLOOR_ORNAMENT_ORDINAL
+ k0x8000_FootprintsAspect = 0x8000 // @ MASK0x8000_FOOTPRINTS
+};
+
+#define k15_immuneToFire 15 // @ C15_IMMUNE_TO_FIRE
+#define k15_immuneToPoison 15 // @ C15_IMMUNE_TO_POISON
+
+class CreatureInfo {
+public:
+ byte _creatureAspectIndex;
+ byte _attackSoundOrdinal;
+ uint16 _attributes; /* Bits 15-14 Unreferenced */
+ uint16 _graphicInfo; /* Bits 11 and 6 Unreferenced */
+ byte _movementTicks; /* Value 255 means the creature cannot move */
+ byte _attackTicks; /* Minimum ticks between attacks */
+ byte _defense;
+ byte _baseHealth;
+ byte _attack;
+ byte _poisonAttack;
+ byte _dexterity;
+ uint16 _ranges; /* Bits 7-4 Unreferenced */
+ uint16 _properties;
+ uint16 _resistances; /* Bits 15-12 and 3-0 Unreferenced */
+ uint16 _animationTicks; /* Bits 15-12 Unreferenced */
+ uint16 _woundProbabilities; /* Contains 4 probabilities to wound a champion's Head (Bits 15-12), Legs (Bits 11-8), Torso (Bits 7-4) and Feet (Bits 3-0) */
+ byte _attackType;
+
+ uint16 getFearResistance() { return (_properties >> 4) & 0xF; }
+ uint16 getExperience() { return (_properties >> 8) & 0xF; }
+ uint16 getWariness() { return (_properties >> 12) & 0xF; }
+ uint16 getFireResistance() { return (_resistances >> 4) & 0xF; }
+ uint16 getPoisonResistance() { return (_resistances >> 8) & 0xF; }
+ static uint16 getHeight(uint16 attrib) { return (attrib >> 7) & 0x3; }
+ uint16 getSightRange() { return (_ranges) & 0xF; }
+ uint16 getSmellRange() { return (_ranges >> 8) & 0xF; }
+ uint16 getAttackRange() { return (_ranges >> 12) & 0xF; }
+}; // @ CREATURE_INFO
+
+class Door {
+ Thing _nextThing;
+ uint16 _attributes;
+public:
+ explicit Door(uint16 *rawDat) : _nextThing(rawDat[0]), _attributes(rawDat[1]) {}
+ Thing getNextThing() { return _nextThing; }
+ bool isMeleeDestructible() { return (_attributes >> 8) & 1; }
+ bool isMagicDestructible() { return (_attributes >> 7) & 1; }
+ bool hasButton() { return (_attributes >> 6) & 1; }
+ bool opensVertically() { return (_attributes >> 5) & 1; }
+ byte getOrnOrdinal() { return (_attributes >> 1) & 0xF; }
+ byte getType() { return _attributes & 1; }
+}; // @ DOOR
+
+enum TeleporterScope {
+ k0x0001_TelepScopeCreatures = 1, // @ MASK0x0001_SCOPE_CREATURES
+ k0x0002_TelepScopeObjOrParty = 2 // @ MASK0x0002_SCOPE_OBJECTS_OR_PARTY
+};
+
+class Teleporter {
+ Thing _nextThing;
+ uint16 _attributes;
+ uint16 _destMapIndex;
+public:
+ explicit Teleporter(uint16 *rawDat) : _nextThing(rawDat[0]), _attributes(rawDat[1]), _destMapIndex(rawDat[2]) {}
+ Thing getNextThing() { return _nextThing; }
+ bool isAudible() { return (_attributes >> 15) & 1; }
+ TeleporterScope getScope() { return (TeleporterScope)((_attributes >> 13) & 1); }
+ bool getAbsoluteRotation() { return (_attributes >> 12) & 1; }
+ Direction getRotation() { return (Direction)((_attributes >> 10) & 1); }
+ byte getTargetMapY() { return (_attributes >> 5) & 0xF; }
+ byte getTargetMapX() { return _attributes & 0xF; }
+ uint16 getTargetMapIndex() { return _destMapIndex >> 8; }
+}; // @ TELEPORTER
+
+class TextString {
+ Thing _nextThing;
+ uint16 _textDataRef;
+public:
+ explicit TextString(uint16 *rawDat) : _nextThing(rawDat[0]), _textDataRef(rawDat[1]) {}
+
+ Thing getNextThing() { return _nextThing; }
+ uint16 getWordOffset() { return _textDataRef >> 3; }
+ bool isVisible() { return _textDataRef & 1; }
+ void setVisible(bool visible) { _textDataRef = (_textDataRef & ~1) | (visible ? 1 : 0); }
+}; // @ TEXTSTRING
+
+enum SensorActionType {
+ kM1_SensorEffNone = -1, // @ CM1_EFFECT_NONE
+ k0_SensorEffSet = 0, // @ C00_EFFECT_SET
+ k1_SensorEffClear = 1, // @ C01_EFFECT_CLEAR
+ k2_SensorEffToggle = 2, // @ C02_EFFECT_TOGGLE
+ k3_SensorEffHold = 3, // @ C03_EFFECT_HOLD
+ k10_SensorEffAddExp = 10 // @ C10_EFFECT_ADD_EXPERIENCE
+};
+
+enum SensorType {
+ k0_SensorDisabled = 0, // @ C000_SENSOR_DISABLED /* Never triggered, may be used for a floor or wall ornament */
+ k1_SensorFloorTheronPartyCreatureObj = 1, // @ C001_SENSOR_FLOOR_THERON_PARTY_CREATURE_OBJECT /* Triggered by party/thing F0276_SENSOR_ProcessThingAdditionOrRemoval */
+ k2_SensorFloorTheronPartyCreature = 2, // @ C002_SENSOR_FLOOR_THERON_PARTY_CREATURE /* Triggered by party/thing F0276_SENSOR_ProcessThingAdditionOrRemoval */
+ k3_SensorFloorParty = 3, // @ C003_SENSOR_FLOOR_PARTY /* Triggered by party/thing F0276_SENSOR_ProcessThingAdditionOrRemoval */
+ k4_SensorFloorObj = 4, // @ C004_SENSOR_FLOOR_OBJECT /* Triggered by party/thing F0276_SENSOR_ProcessThingAdditionOrRemoval */
+ k5_SensorFloorPartyOnStairs = 5, // @ C005_SENSOR_FLOOR_PARTY_ON_STAIRS /* Triggered by party/thing F0276_SENSOR_ProcessThingAdditionOrRemoval */
+ k6_SensorFloorGroupGenerator = 6, // @ C006_SENSOR_FLOOR_GROUP_GENERATOR /* Triggered by event F0245_TIMELINE_ProcessEvent5_Square_Corridor */
+ k7_SensorFloorCreature = 7, // @ C007_SENSOR_FLOOR_CREATURE /* Triggered by party/thing F0276_SENSOR_ProcessThingAdditionOrRemoval */
+ k8_SensorFloorPartyPossession = 8, // @ C008_SENSOR_FLOOR_PARTY_POSSESSION /* Triggered by party/thing F0276_SENSOR_ProcessThingAdditionOrRemoval */
+ k9_SensorFloorVersionChecker = 9, // @ C009_SENSOR_FLOOR_VERSION_CHECKER /* Triggered by party/thing F0276_SENSOR_ProcessThingAdditionOrRemoval */
+ k1_SensorWallOrnClick = 1, // @ C001_SENSOR_WALL_ORNAMENT_CLICK /* Triggered by player click F0275_SENSOR_IsTriggeredByClickOnWall */
+ k2_SensorWallOrnClickWithAnyObj = 2, // @ C002_SENSOR_WALL_ORNAMENT_CLICK_WITH_ANY_OBJECT /* Triggered by player click F0275_SENSOR_IsTriggeredByClickOnWall */
+ k3_SensorWallOrnClickWithSpecObj = 3, // @ C003_SENSOR_WALL_ORNAMENT_CLICK_WITH_SPECIFIC_OBJECT /* Triggered by player click F0275_SENSOR_IsTriggeredByClickOnWall */
+ k4_SensorWallOrnClickWithSpecObjRemoved = 4, // @ C004_SENSOR_WALL_ORNAMENT_CLICK_WITH_SPECIFIC_OBJECT_REMOVED /* Triggered by player click F0275_SENSOR_IsTriggeredByClickOnWall */
+ k5_SensorWallAndOrGate = 5, // @ C005_SENSOR_WALL_AND_OR_GATE /* Triggered by event F0248_TIMELINE_ProcessEvent6_Square_Wall */
+ k6_SensorWallCountdown = 6, // @ C006_SENSOR_WALL_COUNTDOWN /* Triggered by event F0248_TIMELINE_ProcessEvent6_Square_Wall */
+ k7_SensorWallSingleProjLauncherNewObj = 7, // @ C007_SENSOR_WALL_SINGLE_PROJECTILE_LAUNCHER_NEW_OBJECT /* Triggered by event F0248_TIMELINE_ProcessEvent6_Square_Wall */
+ k8_SensorWallSingleProjLauncherExplosion = 8, // @ C008_SENSOR_WALL_SINGLE_PROJECTILE_LAUNCHER_EXPLOSION /* Triggered by event F0248_TIMELINE_ProcessEvent6_Square_Wall */
+ k9_SensorWallDoubleProjLauncherNewObj = 9, // @ C009_SENSOR_WALL_DOUBLE_PROJECTILE_LAUNCHER_NEW_OBJECT /* Triggered by event F0248_TIMELINE_ProcessEvent6_Square_Wall */
+ k10_SensorWallDoubleProjLauncherExplosion = 10, // @ C010_SENSOR_WALL_DOUBLE_PROJECTILE_LAUNCHER_EXPLOSION /* Triggered by event F0248_TIMELINE_ProcessEvent6_Square_Wall */
+ k11_SensorWallOrnClickWithSpecObjRemovedRotateSensors = 11, // @ C011_SENSOR_WALL_ORNAMENT_CLICK_WITH_SPECIFIC_OBJECT_REMOVED_ROTATE_SENSORS /* Triggered by player click F0275_SENSOR_IsTriggeredByClickOnWall */
+ k12_SensorWallObjGeneratorRotateSensors = 12, // @ C012_SENSOR_WALL_OBJECT_GENERATOR_ROTATE_SENSORS /* Triggered by player click F0275_SENSOR_IsTriggeredByClickOnWall */
+ k13_SensorWallSingleObjStorageRotateSensors = 13, // @ C013_SENSOR_WALL_SINGLE_OBJECT_STORAGE_ROTATE_SENSORS /* Triggered by player click F0275_SENSOR_IsTriggeredByClickOnWall */
+ k14_SensorWallSingleProjLauncherSquareObj = 14, // @ C014_SENSOR_WALL_SINGLE_PROJECTILE_LAUNCHER_SQUARE_OBJECT /* Triggered by event F0248_TIMELINE_ProcessEvent6_Square_Wall */
+ k15_SensorWallDoubleProjLauncherSquareObj = 15, // @ C015_SENSOR_WALL_DOUBLE_PROJECTILE_LAUNCHER_SQUARE_OBJECT /* Triggered by event F0248_TIMELINE_ProcessEvent6_Square_Wall */
+ k16_SensorWallObjExchanger = 16, // @ C016_SENSOR_WALL_OBJECT_EXCHANGER /* Triggered by player click F0275_SENSOR_IsTriggeredByClickOnWall */
+ k17_SensorWallOrnClickWithSpecObjRemovedSensor = 17, // @ C017_SENSOR_WALL_ORNAMENT_CLICK_WITH_SPECIFIC_OBJECT_REMOVED_REMOVE_SENSOR /* Triggered by player click F0275_SENSOR_IsTriggeredByClickOnWall */
+ k18_SensorWallEndGame = 18, // @ C018_SENSOR_WALL_END_GAME /* Triggered by event F0248_TIMELINE_ProcessEvent6_Square_Wall */
+ k127_SensorWallChampionPortrait = 127 // @ C127_SENSOR_WALL_CHAMPION_PORTRAIT /* Triggered by player click F0275_SENSOR_IsTriggeredByClickOnWall */
+};
+
+class Sensor {
+ Thing _nextThing;
+ uint16 _datAndType;
+ uint16 _attributes; // A
+ uint16 _action; // B
+public:
+ explicit Sensor(uint16 *rawDat) : _nextThing(rawDat[0]), _datAndType(rawDat[1]), _attributes(rawDat[2]), _action(rawDat[3]) {}
+
+ Thing getNextThing() { return _nextThing; }
+ void setNextThing(Thing thing) { _nextThing = thing; }
+ SensorType getType() { return (SensorType)(_datAndType & 0x7F); } // @ M39_TYPE
+ uint16 getData() { return (_datAndType >> 7) & 0x1FF; } // @ M40_DATA
+ static uint16 getDataMask1(uint16 data) { return (data >> 7) & 0xF; } // @ M42_MASK1
+ static uint16 getDataMask2(uint16 data) { return (data >> 11) & 0xF; } // @ M43_MASK2
+ void setData(uint16 dat) { _datAndType = dat; } // @ M41_SET_DATA
+ void setTypeDisabled() { _datAndType &= 0xFF80; } // @ M44_SET_TYPE_DISABLED
+
+ bool getAttrOnlyOnce() { return (_attributes >> 2) & 1; }
+ uint16 getAttrEffectA() { return (_attributes >> 3) & 0x3; }
+ bool getAttrRevertEffectA() { return (_attributes >> 5) & 0x1; }
+ bool getAttrAudibleA() { return (_attributes >> 6) & 0x1; }
+ uint16 getAttrValue() { return (_attributes >> 7) & 0xF; }
+ bool getAttrLocalEffect() { return (_attributes >> 11) & 1; }
+ uint16 getAttrOrnOrdinal() { return _attributes >> 12; }
+
+ uint16 getActionTargetMapY() { return (_action >> 11); }
+ uint16 getActionTargetMapX() { return (_action >> 6) & 0x1F; }
+ Direction getActionTargetCell() { return (Direction)((_action >> 4) & 3); }
+ uint16 getActionHealthMultiplier() { return ((_action >> 4) & 0xF); } // @ M45_HEALTH_MULTIPLIER
+ uint16 getActionTicks() { return ((_action >> 4) >> 4) & 0xFFF; } // @ M46_TICKS
+ uint16 getActionKineticEnergy() { return ((_action >> 4) & 0xFF); }// @ M47_KINETIC_ENERGY
+ uint16 getActionStepEnergy() { return ((_action >> 4) >> 8) & 0xFF; }// @ M48_STEP_ENERGY
+ uint16 getActionLocalEffect() { return (_action >> 4); } // @ M49_LOCAL_EFFECT
+
+ void setDatAndTypeWithOr(uint16 val) { _datAndType |= val; }
+
+}; // @ SENSOR
+
+
+#define k0x8000_randomDrop 0x8000 // @ MASK0x8000_RANDOM_DROP
+
+enum WeaponType {
+ k2_WeaponTypeTorch = 2, // @ C02_WEAPON_TORCH
+ k8_WeaponTypeDagger = 8, // @ C08_WEAPON_DAGGER
+ k9_WeaponTypeFalchion = 9, // @ C09_WEAPON_FALCHION
+ k10_WeaponTypeSword = 10, // @ C10_WEAPON_SWORD
+ k23_WeaponTypeClub = 23, // @ C23_WEAPON_CLUB
+ k24_WeaponTypeStoneClub = 24, // @ C24_WEAPON_STONE_CLUB
+ k27_WeaponTypeArrow = 27, // @ C27_WEAPON_ARROW
+ k28_WeaponTypeSlayer = 28, // @ C28_WEAPON_SLAYER
+ k30_WeaponTypeRock = 30, // @ C30_WEAPON_ROCK
+ k31_WeaponTypePoisonDart = 31, // @ C31_WEAPON_POISON_DART
+ k32_WeaponTypeThrowingStar = 32 // @ C32_WEAPON_THROWING_STAR
+};
+class Weapon {
+ Thing _nextThing;
+ uint16 _desc;
+public:
+ explicit Weapon(uint16 *rawDat) : _nextThing(rawDat[0]), _desc(rawDat[1]) {}
+
+ WeaponType getType() { return (WeaponType)(_desc & 0x7F); }
+ void setType(uint16 val) { _desc = (_desc & ~0x7F) | (val & 0x7F); }
+ bool isLit() { return (_desc >> 15) & 1; }
+ void setLit(bool val) {
+ if (val)
+ _desc |= (1 << 15);
+ else
+ _desc &= (~(1 << 15));
+ }
+ uint16 getChargeCount() { return (_desc >> 10) & 0xF; }
+ uint16 setChargeCount(uint16 val) { _desc = (_desc & ~(0xF << 10)) | ((val & 0xF) << 10); return (val & 0xF); }
+ Thing getNextThing() { return _nextThing; }
+ void setNextThing(Thing val) { _nextThing = val; }
+ uint16 getCursed() { return (_desc >> 8) & 1; }
+ void setCursed(uint16 val) { _desc = (_desc & ~(1 << 8)) | ((val & 1) << 8); }
+ uint16 getPoisoned() { return (_desc >> 9) & 1; }
+ uint16 getBroken() { return (_desc >> 14) & 1; }
+ uint16 getDoNotDiscard() { return (_desc >> 7) & 1; }
+ void setDoNotDiscard(uint16 val) { _desc = (_desc & ~(1 << 7)) | ((val & 1) << 7); }
+}; // @ WEAPON
+
+enum ArmourType {
+ k30_ArmourTypeWoodenShield = 30, // @ C30_ARMOUR_WOODEN_SHIELD
+ k38_ArmourTypeArmet = 38, // @ C38_ARMOUR_ARMET
+ k39_ArmourTypeTorsoPlate = 39, // @ C39_ARMOUR_TORSO_PLATE
+ k40_ArmourTypeLegPlate = 40, // @ C40_ARMOUR_LEG_PLATE
+ k41_ArmourTypeFootPlate = 41 // @ C41_ARMOUR_FOOT_PLATE
+};
+class Armour {
+ Thing _nextThing;
+ uint16 _attributes;
+public:
+ explicit Armour(uint16 *rawDat) : _nextThing(rawDat[0]), _attributes(rawDat[1]) {}
+
+ ArmourType getType() { return (ArmourType)(_attributes & 0x7F); }
+ Thing getNextThing() { return _nextThing; }
+ uint16 getCursed() { return (_attributes >> 8) & 1; }
+ uint16 getBroken() { return (_attributes >> 13) & 1; }
+ uint16 getDoNotDiscard() { return (_attributes >> 7) & 1; }
+ uint16 getChargeCount() { return (_attributes >> 9) & 0xF; }
+ void setChargeCount(uint16 val) { _attributes = (_attributes & ~(0xF << 9)) | ((val & 0xF) << 9); }
+}; // @ ARMOUR
+
+class Scroll {
+ Thing _nextThing;
+ uint16 _attributes;
+public:
+ explicit Scroll(uint16 *rawDat) : _nextThing(rawDat[0]), _attributes(rawDat[1]) {}
+ void set(Thing next, uint16 attribs) {
+ _nextThing = next;
+ _attributes = attribs;
+ }
+ Thing getNextThing() { return _nextThing; }
+ uint16 getClosed() { return (_attributes >> 10) & 0x3F; } // ??? dunno why, the original bitfield is 6 bits long
+ void setClosed(bool val) {
+ if (val)
+ _attributes |= (1 << 10);
+ else
+ _attributes &= (~(0x3F << 10));
+ }
+ uint16 getTextStringThingIndex() { return _attributes & 0x3FF; }
+}; // @ SCROLL
+
+enum PotionType {
+ k3_PotionTypeVen = 3, // @ C03_POTION_VEN_POTION,
+ k6_PotionTypeRos = 6, // @ C06_POTION_ROS_POTION,
+ k7_PotionTypeKu = 7, // @ C07_POTION_KU_POTION,
+ k8_PotionTypeDane = 8, // @ C08_POTION_DANE_POTION,
+ k9_PotionTypeNeta = 9, // @ C09_POTION_NETA_POTION,
+ k10_PotionTypeAntivenin = 10, // @ C10_POTION_ANTIVENIN,
+ k11_PotionTypeMon = 11, // @ C11_POTION_MON_POTION,
+ k12_PotionTypeYa = 12, // @ C12_POTION_YA_POTION,
+ k13_PotionTypeEe = 13, // @ C13_POTION_EE_POTION,
+ k14_PotionTypeVi = 14, // @ C14_POTION_VI_POTION,
+ k15_PotionTypeWaterFlask = 15, // @ C15_POTION_WATER_FLASK,
+ k19_PotionTypeFulBomb = 19, // @ C19_POTION_FUL_BOMB,
+ k20_PotionTypeEmptyFlask = 20 // @ C20_POTION_EMPTY_FLASK,
+};
+class Potion {
+public:
+ Thing _nextThing;
+ uint16 _attributes;
+ explicit Potion(uint16 *rawDat) : _nextThing(rawDat[0]), _attributes(rawDat[1]) {}
+
+ PotionType getType() { return (PotionType)((_attributes >> 8) & 0x7F); }
+ void setType(PotionType val) { _attributes = (_attributes & ~(0x7F << 8)) | ((val & 0x7F) << 8); }
+ Thing getNextThing() { return _nextThing; }
+ uint16 getPower() { return _attributes & 0xFF; }
+ void setPower(uint16 val) { _attributes = (_attributes & ~0xFF) | (val & 0xFF); }
+ uint16 getDoNotDiscard() { return (_attributes >> 15) & 1; }
+}; // @ POTION
+
+class Container {
+ Thing _nextThing;
+ Thing _slot;
+ uint16 _type;
+public:
+ explicit Container(uint16 *rawDat) : _nextThing(rawDat[0]), _slot(rawDat[1]), _type(rawDat[2]) {}
+
+ uint16 getType() { return (_type >> 1) & 0x3; }
+ Thing &getSlot() { return _slot; }
+ Thing &getNextThing() { return _nextThing; }
+}; // @ CONTAINER
+
+enum JunkType {
+ k1_JunkTypeWaterskin = 1, // @ C01_JUNK_WATERSKIN,
+ k5_JunkTypeBones = 5, // @ C05_JUNK_BONES,
+ k25_JunkTypeBoulder = 25, // @ C25_JUNK_BOULDER,
+ k33_JunkTypeScreamerSlice = 33, // @ C33_JUNK_SCREAMER_SLICE,
+ k34_JunkTypeWormRound = 34, // @ C34_JUNK_WORM_ROUND,
+ k35_JunkTypeDrumstickShank = 35, // @ C35_JUNK_DRUMSTICK_SHANK,
+ k36_JunkTypeDragonSteak = 36, // @ C36_JUNK_DRAGON_STEAK,
+ k42_JunkTypeMagicalBoxBlue = 42, // @ C42_JUNK_MAGICAL_BOX_BLUE,
+ k43_JunkTypeMagicalBoxGreen = 43, // @ C43_JUNK_MAGICAL_BOX_GREEN,
+ k51_JunkTypeZokathra = 51 // @ C51_JUNK_ZOKATHRA,
+};
+
+class Junk {
+ Thing _nextThing;
+ uint16 _attributes;
+public:
+ explicit Junk(uint16 *rawDat) : _nextThing(rawDat[0]), _attributes(rawDat[1]) {}
+
+ JunkType getType() { return (JunkType)(_attributes & 0x7F); }
+ void setType(uint16 val) { _attributes = (_attributes & ~0x7F) | (val & 0x7F); }
+ uint16 getChargeCount() { return (_attributes >> 14) & 0x3; }
+ void setChargeCount(uint16 val) { _attributes = (_attributes & ~(0x3 << 14)) | ((val & 0x3) << 14); }
+ uint16 getDoNotDiscard() { return (_attributes >> 7) & 1; }
+ void setDoNotDiscard(uint16 val) { _attributes = (_attributes & ~(1 << 7)) | ((val & 1) << 7); }
+
+ Thing getNextThing() { return _nextThing; }
+ void setNextThing(Thing thing) { _nextThing = thing; }
+}; // @ JUNK
+
+#define kM1_soundModeDoNotPlaySound -1 // @ CM1_MODE_DO_NOT_PLAY_SOUND
+#define k0_soundModePlayImmediately 0 // @ C00_MODE_PLAY_IMMEDIATELY
+#define k1_soundModePlayIfPrioritized 1 // @ C01_MODE_PLAY_IF_PRIORITIZED
+#define k2_soundModePlayOneTickLater 2 // @ C02_MODE_PLAY_ONE_TICK_LATER
+
+class Projectile {
+public:
+ Thing _nextThing;
+ Thing _slot;
+ uint16 _kineticEnergy;
+ uint16 _attack;
+ uint16 _eventIndex;
+ explicit Projectile(uint16 *rawDat) : _nextThing(rawDat[0]), _slot(rawDat[1]), _kineticEnergy(rawDat[2]),
+ _attack(rawDat[3]), _eventIndex(rawDat[4]) {}
+
+}; // @ PROJECTILE
+
+#define k0_ExplosionType_Fireball 0 // @ C000_EXPLOSION_FIREBALL
+#define k1_ExplosionType_Slime 1 // @ C001_EXPLOSION_SLIME
+#define k2_ExplosionType_LightningBolt 2 // @ C002_EXPLOSION_LIGHTNING_BOLT
+#define k3_ExplosionType_HarmNonMaterial 3 // @ C003_EXPLOSION_HARM_NON_MATERIAL
+#define k4_ExplosionType_OpenDoor 4 // @ C004_EXPLOSION_OPEN_DOOR
+#define k6_ExplosionType_PoisonBolt 6 // @ C006_EXPLOSION_POISON_BOLT
+#define k7_ExplosionType_PoisonCloud 7 // @ C007_EXPLOSION_POISON_CLOUD
+#define k40_ExplosionType_Smoke 40 // @ C040_EXPLOSION_SMOKE
+#define k50_ExplosionType_Fluxcage 50 // @ C050_EXPLOSION_FLUXCAGE
+#define k100_ExplosionType_RebirthStep1 100 // @ C100_EXPLOSION_REBIRTH_STEP1
+#define k101_ExplosionType_RebirthStep2 101 // @ C101_EXPLOSION_REBIRTH_STEP2
+
+class Explosion {
+ Thing _nextThing;
+ uint16 _attributes;
+public:
+ explicit Explosion(uint16 *rawDat) : _nextThing(rawDat[0]), _attributes(rawDat[1]) {}
+
+ Thing getNextThing() { return _nextThing; }
+ Thing setNextThing(Thing val) { return _nextThing = val; }
+ uint16 getType() { return _attributes & 0x7F; }
+ uint16 setType(uint16 val) { _attributes = (_attributes & ~0x7F) | (val & 0x7F); return (val & 0x7F); }
+ uint16 getAttack() { return (_attributes >> 8) & 0xFF; }
+ void setAttack(uint16 val) { _attributes = (_attributes & ~(0xFF << 8)) | ((val & 0xFF) << 8); }
+ uint16 getCentered() { return (_attributes >> 7) & 0x1; }
+ void setCentered(uint16 val) { _attributes = (_attributes & ~(1 << 7)) | ((val & 1) << 7); }
+}; // @ EXPLOSION
+
+
+enum SquareMask {
+ k0x0001_WallWestRandOrnAllowed = 0x1, // @ MASK0x0001_WALL_WEST_RANDOM_ORNAMENT_ALLOWED
+ k0x0002_WallSouthRandOrnAllowed = 0x2, // @ MASK0x0002_WALL_SOUTH_RANDOM_ORNAMENT_ALLOWED
+ k0x0004_WallEastRandOrnAllowed = 0x4, // @ MASK0x0004_WALL_EAST_RANDOM_ORNAMENT_ALLOWED
+ k0x0008_WallNorthRandOrnAllowed = 0x8, // @ MASK0x0008_WALL_NORTH_RANDOM_ORNAMENT_ALLOWED
+ k0x0008_CorridorRandOrnAllowed = 0x8, // @ MASK0x0008_CORRIDOR_RANDOM_ORNAMENT_ALLOWED
+ k0x0001_PitImaginary = 0x1, // @ MASK0x0001_PIT_IMAGINARY
+ k0x0004_PitInvisible = 0x4, // @ MASK0x0004_PIT_INVISIBLE
+ k0x0008_PitOpen = 0x8, // @ MASK0x0008_PIT_OPEN
+ k0x0004_StairsUp = 0x4, // @ MASK0x0004_STAIRS_UP
+ k0x0008_StairsNorthSouthOrient = 0x8, // @ MASK0x0008_STAIRS_NORTH_SOUTH_ORIENTATION
+ k0x0008_DoorNorthSouthOrient = 0x8, // @ MASK0x0008_DOOR_NORTH_SOUTH_ORIENTATION
+ k0x0004_TeleporterVisible = 0x4, // @ MASK0x0004_TELEPORTER_VISIBLE
+ k0x0008_TeleporterOpen = 0x8, // @ MASK0x0008_TELEPORTER_OPEN
+ k0x0001_FakeWallImaginary = 0x1, // @ MASK0x0001_FAKEWALL_IMAGINARY
+ k0x0004_FakeWallOpen = 0x4, // @ MASK0x0004_FAKEWALL_OPEN
+ k0x0008_FakeWallRandOrnOrFootPAllowed = 0x8, // @ MASK0x0008_FAKEWALL_RANDOM_ORNAMENT_OR_FOOTPRINTS_ALLOWED
+ k0x0010_ThingListPresent = 0x10, // @ MASK0x0010_THING_LIST_PRESENT
+ k0x8000_DecodeEvenIfInvisible = 0x8000 // @ MASK0x8000_DECODE_EVEN_IF_INVISIBLE
+};
+
+enum SquareType {
+ kM2_ChampionElemType = -2, // @ CM2_ELEMENT_CHAMPION
+ kM1_CreatureElemType = -1, // @ CM1_ELEMENT_CREATURE
+ k0_WallElemType = 0, // @ C00_ELEMENT_WALL
+ k1_CorridorElemType = 1, // @ C01_ELEMENT_CORRIDOR
+ k2_PitElemType = 2, // @ C02_ELEMENT_PIT
+ k3_StairsElemType = 3, // @ C03_ELEMENT_STAIRS
+ k4_DoorElemType = 4, // @ C04_ELEMENT_DOOR
+ k5_TeleporterElemType = 5, // @ C05_ELEMENT_TELEPORTER
+ k6_FakeWallElemType = 6, // @ C06_ELEMENT_FAKEWALL
+ k16_DoorSideElemType = 16, // @ C16_ELEMENT_DOOR_SIDE
+ k17_DoorFrontElemType = 17, // @ C17_ELEMENT_DOOR_FRONT
+ k18_StairsSideElemType = 18, // @ C18_ELEMENT_STAIRS_SIDE
+ k19_StairsFrontElemType = 19 // @ C19_ELEMENT_STAIRS_FRONT
+}; // @ C[-2..19]_ELEMENT_...
+
+#define k0x8000_championBones 0x8000 // @ MASK0x8000_CHAMPION_BONES
+#define k0x7FFF_thingType 0x7FFF // @ MASK0x7FFF_THING_TYPE
+
+class Square {
+ byte _data;
+public:
+ explicit Square(byte dat = 0) : _data(dat) {}
+ explicit Square(SquareType type) { setType(type); }
+ explicit Square(byte element, byte mask) : _data((element << 5) | mask) {}
+ Square &set(byte dat) { this->_data = dat; return *this; }
+ Square &set(SquareMask mask) { _data |= mask; return *this; }
+ byte get(SquareMask mask) { return _data & mask; }
+ byte getDoorState() { return _data & 0x7; } // @ M36_DOOR_STATE
+ void setDoorState(byte state) { _data = ((_data & ~0x7) | state); } // @ M37_SET_DOOR_STATE
+ SquareType getType() { return (SquareType)(_data >> 5); } // @ M34_SQUARE_TYPE
+ void setType(SquareType type) { _data = (_data & 0x1F) | type << 5; }
+ byte toByte() { return _data; } // I don't like 'em casts
+}; // wrapper for bytes which are used as squares
+
+struct DungeonFileHeader {
+ uint16 _ornamentRandomSeed;
+ uint16 _rawMapDataSize;
+ uint8 _mapCount;
+ uint16 _textDataWordCount;
+ uint16 _partyStartLocation;
+ uint16 _squareFirstThingCount; // @ SquareFirstThingCount
+ uint16 _thingCounts[16]; // @ ThingCount[16]
+}; // @ DUNGEON_HEADER
+
+struct Map {
+ uint32 _rawDunDataOffset;
+ uint8 _offsetMapX, _offsetMapY;
+
+ uint8 _level; // only used in DMII
+ uint8 _width, _height; // !!! THESRE ARE INCLUSIVE BOUNDARIES
+ // orn short for Ornament
+ uint8 _wallOrnCount; /* May be used in a Sensor on a Wall or closed Fake Wall square */
+ uint8 _randWallOrnCount; /* Used only on some Wall squares and some closed Fake Wall squares */
+ uint8 _floorOrnCount; /* May be used in a Sensor on a Pit, open Fake Wall, Corridor or Teleporter square */
+ uint8 _randFloorOrnCount; /* Used only on some Corridor squares and some open Fake Wall squares */
+
+ uint8 _doorOrnCount;
+ uint8 _creatureTypeCount;
+ uint8 _difficulty;
+
+ FloorSet _floorSet;
+ WallSet _wallSet;
+ uint8 _doorSet0, _doorSet1;
+}; // @ MAP
+
+
+class DoorInfo {
+public:
+ byte _attributes;
+ byte _defense;
+ DoorInfo(byte b1, byte b2) : _attributes(b1), _defense(b2) {}
+ DoorInfo() { resetToZero(); }
+ void resetToZero() { _attributes = _defense = 0; }
+}; // @ DOOR_INFO
+
+class Group;
+
+class DungeonMan {
+ DMEngine *_vm;
+
+ DungeonMan(const DungeonMan &other); // no implementation on purpose
+ void operator=(const DungeonMan &rhs); // no implementation on purpose
+
+ Square getRelSquare(Direction dir, int16 stepsForward, int16 stepsRight, int16 posX, int16 posY); // @ F0152_DUNGEON_GetRelativeSquare
+
+ void decompressDungeonFile(); // @ F0455_FLOPPY_DecompressDungeon
+
+ int16 getSquareFirstThingIndex(int16 mapX, int16 mapY); // @ F0160_DUNGEON_GetSquareFirstThingIndex
+
+ int16 getRandomOrnOrdinal(bool allowed, int16 count, int16 mapX, int16 mapY, int16 modulo); // @ F0170_DUNGEON_GetRandomOrnamentOrdinal
+ void setSquareAspectOrnOrdinals(uint16 *aspectArray, bool leftAllowed, bool frontAllowed, bool rightAllowed, int16 dir,
+ int16 mapX, int16 mapY, bool isFakeWall); // @ F0171_DUNGEON_SetSquareAspectRandomWallOrnamentOrdinals
+
+public:
+ explicit DungeonMan(DMEngine *dmEngine);
+ ~DungeonMan();
+
+ Square getSquare(int16 mapX, int16 mapY); // @ F0151_DUNGEON_GetSquare
+ void setCurrentMap(uint16 mapIndex); // @ F0173_DUNGEON_SetCurrentMap
+ Thing getSquareFirstThing(int16 mapX, int16 mapY); // @ F0161_DUNGEON_GetSquareFirstThing
+ Thing getNextThing(Thing thing); // @ F0159_DUNGEON_GetNextThing(THING P0280_T_Thing)
+ uint16 *getThingData(Thing thing); // @ F0156_DUNGEON_GetThingData
+ uint16 *getSquareFirstThingData(int16 mapX, int16 mapY); // @ F0157_DUNGEON_GetSquareFirstThingData
+
+ // TODO: this does stuff other than load the file!
+ void loadDungeonFile(Common::InSaveFile *file); // @ F0434_STARTEND_IsLoadDungeonSuccessful_CPSC
+ void setCurrentMapAndPartyMap(uint16 mapIndex); // @ F0174_DUNGEON_SetCurrentMapAndPartyMap
+
+ bool isWallOrnAnAlcove(int16 wallOrnIndex); // @ F0149_DUNGEON_IsWallOrnamentAnAlcove
+ void mapCoordsAfterRelMovement(Direction dir, int16 stepsForward, int16 stepsRight, int16 &posX, int16 &posY); // @ F0150_DUNGEON_UpdateMapCoordinatesAfterRelativeMovement
+ SquareType getRelSquareType(Direction dir, int16 stepsForward, int16 stepsRight, int16 posX, int16 posY) {
+ return Square(getRelSquare(dir, stepsForward, stepsRight, posX, posY)).getType();
+ } // @ F0153_DUNGEON_GetRelativeSquareType
+ void setSquareAspect(uint16 *aspectArray, Direction dir, int16 mapX, int16 mapY); // @ F0172_DUNGEON_SetSquareAspect
+ void decodeText(char *destString, Thing thing, TextType type); // F0168_DUNGEON_DecodeText
+ Thing getUnusedThing(uint16 thingType); // @ F0166_DUNGEON_GetUnusedThing
+
+ uint16 getObjectWeight(Thing thing); // @ F0140_DUNGEON_GetObjectWeight
+ int16 getObjectInfoIndex(Thing thing); // @ F0141_DUNGEON_GetObjectInfoIndex
+ void linkThingToList(Thing thingToLink, Thing thingInList, int16 mapX, int16 mapY); // @ F0163_DUNGEON_LinkThingToList
+ WeaponInfo *getWeaponInfo(Thing thing); // @ F0158_DUNGEON_GetWeaponInfo
+ int16 getProjectileAspect(Thing thing); // @ F0142_DUNGEON_GetProjectileAspect
+ int16 getLocationAfterLevelChange(int16 mapIndex, int16 levelDelta, int16 *mapX, int16 *mapY); // @ F0154_DUNGEON_GetLocationAfterLevelChange
+ Thing getSquareFirstObject(int16 mapX, int16 mapY); // @ F0162_DUNGEON_GetSquareFirstObject
+ uint16 getArmourDefense(ArmourInfo *armourInfo, bool useSharpDefense); // @ F0143_DUNGEON_GetArmourDefense
+ Thing getDiscardThing(uint16 thingType); // @ F0165_DUNGEON_GetDiscardedThing
+ uint16 getCreatureAttributes(Thing thing); // @ F0144_DUNGEON_GetCreatureAttributes
+ void setGroupCells(Group *group, uint16 cells, uint16 mapIndex); // @ F0146_DUNGEON_SetGroupCells
+ void setGroupDirections(Group *group, int16 dir, uint16 mapIndex); // @ F0148_DUNGEON_SetGroupDirections
+ bool isCreatureAllowedOnMap(Thing thing, uint16 mapIndex); // @ F0139_DUNGEON_IsCreatureAllowedOnMap
+ void unlinkThingFromList(Thing thingToUnlink, Thing thingInList, int16 mapX, int16 mapY); // @ F0164_DUNGEON_UnlinkThingFromList
+ int16 getStairsExitDirection(int16 mapX, int16 mapY); // @ F0155_DUNGEON_GetStairsExitDirection
+ Thing getObjForProjectileLaucherOrObjGen(uint16 iconIndex); // @ F0167_DUNGEON_GetObjectForProjectileLauncherOrObjectGenerator
+ int16 getRandomOrnamentIndex(uint16 val1, uint16 val2, int16 modulo); // @ F0169_DUNGEON_GetRandomOrnamentIndex
+
+ uint32 _rawDunFileDataSize; // @ probably NONE
+ byte *_rawDunFileData; // @ ???
+ DungeonFileHeader _dungeonFileHeader; // @ G0278_ps_DungeonHeader
+
+ uint16 *_dungeonMapsFirstColumnIndex; // @ G0281_pui_DungeonMapsFirstColumnIndex
+ uint16 _dungeonColumCount; // @ G0282_ui_DungeonColumnCount
+ uint16 *_dungeonColumnsCumulativeSquareThingCount; // @ G0280_pui_DungeonColumnsCumulativeSquareThingCount
+ Thing *_squareFirstThings; // @ G0283_pT_SquareFirstThings
+ uint16 *_dungeonTextData; // @ G0260_pui_DungeonTextData
+ uint16 *_thingData[16]; // @ G0284_apuc_ThingData
+ byte ***_dungeonMapData; // @ G0279_pppuc_DungeonMapData
+
+ Direction _partyDir; // @ G0308_i_PartyDirection
+ int16 _partyMapX; // @ G0306_i_PartyMapX
+ int16 _partyMapY; // @ G0307_i_PartyMapY
+ uint8 _partyMapIndex; // @ G0309_i_PartyMapIndex
+ int16 _currMapIndex; // @ G0272_i_CurrentMapIndex
+ byte **_currMapData; // @ G0271_ppuc_CurrentMapData
+ Map *_currMap; // @ G0269_ps_CurrentMap
+ uint16 _currMapWidth; // @ G0273_i_CurrentMapWidth
+ uint16 _currMapHeight; // @ G0274_i_CurrentMapHeight
+ uint16 *_currMapColCumulativeSquareFirstThingCount; // @G0270_pui_CurrentMapColumnsCumulativeSquareFirstThingCount
+
+ Map *_dungeonMaps; // @ G0277_ps_DungeonMaps
+ byte *_dungeonRawMapData; // @ G0276_puc_DungeonRawMapData
+
+ int16 _currMapInscriptionWallOrnIndex; // @ G0265_i_CurrentMapInscriptionWallOrnamentIndex
+ Box _dungeonViewClickableBoxes[6]; // G0291_aauc_DungeonViewClickableBoxes
+ bool _isFacingAlcove; // @ G0286_B_FacingAlcove
+ bool _isFacingViAltar; // @ G0287_B_FacingViAltar
+ bool _isFacingFountain; // @ G0288_B_FacingFountain
+ ElementType _squareAheadElement; // @ G0285_i_SquareAheadElement
+ Thing _pileTopObject[5]; // @ G0292_aT_PileTopObject
+ DoorInfo _currMapDoorInfo[2]; // @ G0275_as_CurrentMapDoorInfo
+
+ ObjectInfo _objectInfos[180]; // @ G0237_as_Graphic559_ObjectInfo
+ ArmourInfo _armourInfos[58]; // @ G0239_as_Graphic559_ArmourInfo
+ WeaponInfo _weaponInfos[46]; // @ G0238_as_Graphic559_WeaponInfo
+ CreatureInfo _creatureInfos[k27_CreatureTypeCount]; // @ G0243_as_Graphic559_CreatureInfo
+ byte _thingDataWordCount[16]; // @ G0235_auc_Graphic559_ThingDataByteCount
+
+ void setupConstants();
+};
+
+}
+
+#endif
diff --git a/engines/dm/eventman.cpp b/engines/dm/eventman.cpp
new file mode 100644
index 0000000000..e7095ce4a7
--- /dev/null
+++ b/engines/dm/eventman.cpp
@@ -0,0 +1,1659 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "common/system.h"
+#include "graphics/cursorman.h"
+#include "graphics/thumbnail.h"
+
+#include "dm/eventman.h"
+#include "dm/dungeonman.h"
+#include "dm/movesens.h"
+#include "dm/objectman.h"
+#include "dm/inventory.h"
+#include "dm/menus.h"
+#include "dm/timeline.h"
+#include "dm/projexpl.h"
+#include "dm/text.h"
+#include "dm/group.h"
+#include "dm/dialog.h"
+#include "dm/sounds.h"
+
+
+namespace DM {
+
+void EventManager::initArrays() {
+ KeyboardInput primaryKeyboardInputInterface[7] = { // @ G0458_as_Graphic561_PrimaryKeyboardInput_Interface
+ /* { Command, Code } */
+ KeyboardInput(k7_CommandToggleInventoryChampion_0, Common::KEYCODE_F1, 0), /* F1 (<CSI>1~) Atari ST: Code = 0x3B00 */
+ KeyboardInput(k8_CommandToggleInventoryChampion_1, Common::KEYCODE_F2, 0), /* F2 (<CSI>2~) Atari ST: Code = 0x3C00 */
+ KeyboardInput(k9_CommandToggleInventoryChampion_2, Common::KEYCODE_F3, 0), /* F3 (<CSI>3~) Atari ST: Code = 0x3D00 */
+ KeyboardInput(k10_CommandToggleInventoryChampion_3, Common::KEYCODE_F4, 0), /* F4 (<CSI>4~) Atari ST: Code = 0x3E00 */
+ KeyboardInput(k140_CommandSaveGame, Common::KEYCODE_s, Common::KBD_CTRL), /* CTRL-S Atari ST: Code = 0x0013 */
+ KeyboardInput(k147_CommandFreezeGame, Common::KEYCODE_ESCAPE, 0), /* Esc (0x1B) Atari ST: Code = 0x001B */
+ KeyboardInput(k0_CommandNone, Common::KEYCODE_INVALID, 0)
+ };
+
+ KeyboardInput secondaryKeyboardInputMovement[19] = { // @ G0459_as_Graphic561_SecondaryKeyboardInput_Movement
+ /* { Command, Code } */
+ KeyboardInput(k1_CommandTurnLeft, Common::KEYCODE_KP4, 0), /* Numeric pad 4 Atari ST: Code = 0x5200 */
+ KeyboardInput(k3_CommandMoveForward, Common::KEYCODE_KP5, 0), /* Numeric pad 5 Atari ST: Code = 0x4800 */
+ KeyboardInput(k2_CommandTurnRight, Common::KEYCODE_KP6, 0), /* Numeric pad 6 Atari ST: Code = 0x4700 */
+ KeyboardInput(k6_CommandMoveLeft, Common::KEYCODE_KP1, 0), /* Numeric pad 1 Atari ST: Code = 0x4B00 */
+ KeyboardInput(k5_CommandMoveBackward, Common::KEYCODE_KP2, 0), /* Numeric pad 2 Atari ST: Code = 0x5000 */
+ KeyboardInput(k4_CommandMoveRight, Common::KEYCODE_KP3, 0), /* Numeric pad 3 Atari ST: Code = 0x4D00. Remaining entries below not present */
+ KeyboardInput(k3_CommandMoveForward, Common::KEYCODE_w, 0), /* Up Arrow (<CSI>A) */ /*Differs for testing convenience*/
+ KeyboardInput(k3_CommandMoveForward, Common::KEYCODE_w, Common::KBD_SHIFT), /* Shift Up Arrow (<CSI>T) */ /*Differs for testing convenience*/
+ KeyboardInput(k6_CommandMoveLeft, Common::KEYCODE_a, 0), /* Backward Arrow (<CSI>D) */ /*Differs for testing convenience*/
+ KeyboardInput(k6_CommandMoveLeft, Common::KEYCODE_a, Common::KBD_SHIFT), /* Shift Forward Arrow (<CSI> A) */ /*Differs for testing convenience*/
+ KeyboardInput(k4_CommandMoveRight, Common::KEYCODE_d, 0), /* Forward Arrow (<CSI>C) */ /*Differs for testing convenience*/
+ KeyboardInput(k4_CommandMoveRight, Common::KEYCODE_d, Common::KBD_SHIFT), /* Shift Backward Arrow (<CSI> @) */ /*Differs for testing convenience*/
+ KeyboardInput(k5_CommandMoveBackward, Common::KEYCODE_s, 0), /* Down arrow (<CSI>B) */ /*Differs for testing convenience*/
+ KeyboardInput(k5_CommandMoveBackward, Common::KEYCODE_s, Common::KBD_SHIFT), /* Shift Down arrow (<CSI>S) */ /*Differs for testing convenience*/
+ KeyboardInput(k1_CommandTurnLeft, Common::KEYCODE_q, 0), /* Del (0x7F) */ /*Differs for testing convenience*/
+ KeyboardInput(k1_CommandTurnLeft, Common::KEYCODE_q, Common::KBD_SHIFT), /* Shift Del (0x7F) */ /*Differs for testing convenience*/
+ KeyboardInput(k2_CommandTurnRight, Common::KEYCODE_e, 0), /* Help (<CSI>?~) */ /*Differs for testing convenience*/
+ KeyboardInput(k2_CommandTurnRight, Common::KEYCODE_e, Common::KBD_SHIFT), /* Shift Help (<CSI>?~) */ /*Differs for testing convenience*/
+ KeyboardInput(k0_CommandNone, Common::KEYCODE_INVALID, 0)
+ };
+ KeyboardInput primaryKeyboardInputPartySleeping[3] = { // @ G0460_as_Graphic561_PrimaryKeyboardInput_PartySleeping
+ /* { Command, Code } */
+ KeyboardInput(k146_CommandWakeUp, Common::KEYCODE_RETURN, 0), /* Return */
+ KeyboardInput(k147_CommandFreezeGame, Common::KEYCODE_ESCAPE, 0), /* Esc */
+ KeyboardInput(k0_CommandNone, Common::KEYCODE_INVALID, 0)
+ };
+ KeyboardInput primaryKeyboardInputFrozenGame[2] = { // @ G0461_as_Graphic561_PrimaryKeyboardInput_FrozenGame
+ /* { Command, Code } */
+ KeyboardInput(k148_CommandUnfreezeGame, Common::KEYCODE_ESCAPE, 0), /* Esc */
+ KeyboardInput(k0_CommandNone, Common::KEYCODE_INVALID, 0)
+ };
+ MouseInput primaryMouseInputEntrance[4] = { // @ G0445_as_Graphic561_PrimaryMouseInput_Entrance[4]
+ /* { Command, Box.X1, Box.X2, Box.Y1, Box.Y2, Button } */
+ MouseInput(k200_CommandEntranceEnterDungeon, 244, 298, 45, 58, k1_LeftMouseButton),
+ // Strangerke - C201_COMMAND_ENTRANCE_RESUME isn't present in the demo
+ MouseInput(k201_CommandEntranceResume, 244, 298, 76, 93, k1_LeftMouseButton),
+ MouseInput(k202_CommandEntranceDrawCredits, 248, 293, 187, 199, k1_LeftMouseButton),
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput primaryMouseInputRestartGame[2] = { // @ G0446_as_Graphic561_PrimaryMouseInput_RestartGame[2]
+ /* { Command, Box.X1, Box.X2, Box.Y1, Box.Y2, Button } */
+ MouseInput(k215_CommandRestartGame, 103, 217, 145, 159, k1_LeftMouseButton),
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput primaryMouseInputInterface[20] = { // @ G0447_as_Graphic561_PrimaryMouseInput_Interface[20]
+ /* { Command, Box.X1, Box.X2, Box.Y1, Box.Y2, Button } */
+ MouseInput(k12_CommandClickInChampion_0_StatusBox, 0, 42, 0, 28, k1_LeftMouseButton),
+ MouseInput(k13_CommandClickInChampion_1_StatusBox, 69, 111, 0, 28, k1_LeftMouseButton),
+ MouseInput(k14_CommandClickInChampion_2_StatusBox, 138, 180, 0, 28, k1_LeftMouseButton),
+ MouseInput(k15_CommandClickInChampion_3_StatusBox, 207, 249, 0, 28, k1_LeftMouseButton),
+ MouseInput(k125_CommandClickOnChamptionIcon_Top_Left, 274, 299, 0, 13, k1_LeftMouseButton),
+ MouseInput(k126_CommandClickOnChamptionIcon_Top_Right, 301, 319, 0, 13, k1_LeftMouseButton),
+ MouseInput(k127_CommandClickOnChamptionIcon_Lower_Right, 301, 319, 15, 28, k1_LeftMouseButton),
+ MouseInput(k128_CommandClickOnChamptionIcon_Lower_Left, 274, 299, 15, 28, k1_LeftMouseButton),
+ MouseInput(k7_CommandToggleInventoryChampion_0, 43, 66, 0, 28, k1_LeftMouseButton), /* Atari ST: Only present in CSB 2.x and with Box.X1 = 44. swapped with 4 next entries */
+ MouseInput(k8_CommandToggleInventoryChampion_1, 112, 135, 0, 28, k1_LeftMouseButton), /* Atari ST: Only present in CSB 2.x and with Box.X1 = 113. swapped with 4 next entries */
+ MouseInput(k9_CommandToggleInventoryChampion_2, 181, 204, 0, 28, k1_LeftMouseButton), /* Atari ST: Only present in CSB 2.x and with Box.X1 = 182. swapped with 4 next entries */
+ MouseInput(k10_CommandToggleInventoryChampion_3, 250, 273, 0, 28, k1_LeftMouseButton), /* Atari ST: Only present in CSB 2.x and with Box.X1 = 251. swapped with 4 next entries */
+ MouseInput(k7_CommandToggleInventoryChampion_0, 0, 66, 0, 28, k2_RightMouseButton), /* Atari ST: swapped with 4 previous entries */
+ MouseInput(k8_CommandToggleInventoryChampion_1, 69, 135, 0, 28, k2_RightMouseButton), /* Atari ST: swapped with 4 previous entries */
+ MouseInput(k9_CommandToggleInventoryChampion_2, 138, 204, 0, 28, k2_RightMouseButton), /* Atari ST: swapped with 4 previous entries */
+ MouseInput(k10_CommandToggleInventoryChampion_3, 207, 273, 0, 28, k2_RightMouseButton), /* Atari ST: swapped with 4 previous entries */
+ MouseInput(k100_CommandClickInSpellArea, 233, 319, 42, 73, k1_LeftMouseButton),
+ MouseInput(k111_CommandClickInActionArea, 233, 319, 77, 121, k1_LeftMouseButton),
+ MouseInput(k147_CommandFreezeGame, 0, 1, 198, 199, k1_LeftMouseButton),
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput secondaryMouseInputMovement[9] = { // @ G0448_as_Graphic561_SecondaryMouseInput_Movement[9]
+ /* { Command, Box.X1, Box.X2, Box.Y1, Box.Y2, Button } */
+ MouseInput(k1_CommandTurnLeft, 234, 261, 125, 145, k1_LeftMouseButton),
+ MouseInput(k3_CommandMoveForward, 263, 289, 125, 145, k1_LeftMouseButton),
+ MouseInput(k2_CommandTurnRight, 291, 318, 125, 145, k1_LeftMouseButton),
+ MouseInput(k6_CommandMoveLeft, 234, 261, 147, 167, k1_LeftMouseButton),
+ MouseInput(k5_CommandMoveBackward, 263, 289, 147, 167, k1_LeftMouseButton),
+ MouseInput(k4_CommandMoveRight, 291, 318, 147, 167, k1_LeftMouseButton),
+ MouseInput(k80_CommandClickInDungeonView, 0, 223, 33, 168, k1_LeftMouseButton),
+ MouseInput(k83_CommandToggleInventoryLeader, 0, 319, 33, 199, k2_RightMouseButton),
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput secondaryMouseInputChampionInventory[38] = { // @ G0449_as_Graphic561_SecondaryMouseInput_ChampionInventory[38]
+ /* { Command, Box.X1, Box.X2, Box.Y1, Box.Y2, Button } */
+ MouseInput(k11_CommandCloseInventory, 0, 319, 0, 199, k2_RightMouseButton),
+ MouseInput(k140_CommandSaveGame, 174, 182, 36, 44, k1_LeftMouseButton),
+ MouseInput(k145_CommandSleep, 188, 204, 36, 44, k1_LeftMouseButton),
+ MouseInput(k11_CommandCloseInventory, 210, 218, 36, 44, k1_LeftMouseButton),
+ MouseInput(k28_CommandClickOnSlotBoxInventoryReadyHand , 6, 21, 86, 101, k1_LeftMouseButton),
+ MouseInput(k29_CommandClickOnSlotBoxInventoryActionHand, 62, 77, 86, 101, k1_LeftMouseButton),
+ MouseInput(k30_CommandClickOnSlotBoxInventoryHead, 34, 49, 59, 74, k1_LeftMouseButton),
+ MouseInput(k31_CommandClickOnSlotBoxInventoryTorso, 34, 49, 79, 94, k1_LeftMouseButton),
+ MouseInput(k32_CommandClickOnSlotBoxInventoryLegs, 34, 49, 99, 114, k1_LeftMouseButton),
+ MouseInput(k33_CommandClickOnSlotBoxInventoryFeet, 34, 49, 119, 134, k1_LeftMouseButton),
+ MouseInput(k34_CommandClickOnSlotBoxInventoryPouch_2, 6, 21, 123, 138, k1_LeftMouseButton),
+ MouseInput(k70_CommandClickOnMouth, 56, 71, 46, 61, k1_LeftMouseButton),
+ MouseInput(k71_CommandClickOnEye, 12, 27, 46, 61, k1_LeftMouseButton),
+ MouseInput(k35_CommandClickOnSlotBoxInventoryQuiverLine_2_1, 79, 94, 106, 121, k1_LeftMouseButton),
+ MouseInput(k36_CommandClickOnSlotBoxInventoryQuiverLine_1_2, 62, 77, 123, 138, k1_LeftMouseButton),
+ MouseInput(k37_CommandClickOnSlotBoxInventoryQuiverLine_2_2, 79, 94, 123, 138, k1_LeftMouseButton),
+ MouseInput(k38_CommandClickOnSlotBoxInventoryNeck, 6, 21, 66, 81, k1_LeftMouseButton),
+ MouseInput(k39_CommandClickOnSlotBoxInventoryPouch_1, 6, 21, 106, 121, k1_LeftMouseButton),
+ MouseInput(k40_CommandClickOnSlotBoxInventoryQuiverLine_1_1, 62, 77, 106, 121, k1_LeftMouseButton),
+ MouseInput(k41_CommandClickOnSlotBoxInventoryBackpackLine_1_1, 66, 81, 66, 81, k1_LeftMouseButton),
+ MouseInput(k42_CommandClickOnSlotBoxInventoryBackpackLine_2_2, 83, 98, 49, 64, k1_LeftMouseButton),
+ MouseInput(k43_CommandClickOnSlotBoxInventoryBackpackLine_2_3, 100, 115, 49, 64, k1_LeftMouseButton),
+ MouseInput(k44_CommandClickOnSlotBoxInventoryBackpackLine_2_4, 117, 132, 49, 64, k1_LeftMouseButton),
+ MouseInput(k45_CommandClickOnSlotBoxInventoryBackpackLine_2_5, 134, 149, 49, 64, k1_LeftMouseButton),
+ MouseInput(k46_CommandClickOnSlotBoxInventoryBackpackLine_2_6, 151, 166, 49, 64, k1_LeftMouseButton),
+ MouseInput(k47_CommandClickOnSlotBoxInventoryBackpackLine_2_7, 168, 183, 49, 64, k1_LeftMouseButton),
+ MouseInput(k48_CommandClickOnSlotBoxInventoryBackpackLine_2_8, 185, 200, 49, 64, k1_LeftMouseButton),
+ MouseInput(k49_CommandClickOnSlotBoxInventoryBackpackLine_2_9, 202, 217, 49, 64, k1_LeftMouseButton),
+ MouseInput(k50_CommandClickOnSlotBoxInventoryBackpackLine_1_2, 83, 98, 66, 81, k1_LeftMouseButton),
+ MouseInput(k51_CommandClickOnSlotBoxInventoryBackpackLine_1_3, 100, 115, 66, 81, k1_LeftMouseButton),
+ MouseInput(k52_CommandClickOnSlotBoxInventoryBackpackLine_1_4, 117, 132, 66, 81, k1_LeftMouseButton),
+ MouseInput(k53_CommandClickOnSlotBoxInventoryBackpackLine_1_5, 134, 149, 66, 81, k1_LeftMouseButton),
+ MouseInput(k54_CommandClickOnSlotBoxInventoryBackpackLine_1_6, 151, 166, 66, 81, k1_LeftMouseButton),
+ MouseInput(k55_CommandClickOnSlotBoxInventoryBackpackLine_1_7, 168, 183, 66, 81, k1_LeftMouseButton),
+ MouseInput(k56_CommandClickOnSlotBoxInventoryBackpackLine_1_8, 185, 200, 66, 81, k1_LeftMouseButton),
+ MouseInput(k57_CommandClickOnSlotBoxInventoryBackpackLine_1_9, 202, 217, 66, 81, k1_LeftMouseButton),
+ MouseInput(k81_CommandClickInPanel, 96, 223, 83, 167, k1_LeftMouseButton),
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput primaryMouseInputPartySleeping[3] = { // @ G0450_as_Graphic561_PrimaryMouseInput_PartySleeping[3]
+ /* { Command, Box.X1, Box.X2, Box.Y1, Box.Y2, Button } */
+ MouseInput(k146_CommandWakeUp, 0, 223, 33, 168, k1_LeftMouseButton),
+ MouseInput(k146_CommandWakeUp, 0, 223, 33, 168, k2_RightMouseButton),
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput primaryMouseInputFrozenGame[3] = { // @ G0451_as_Graphic561_PrimaryMouseInput_FrozenGame[3]
+ /* { Command, Box.X1, Box.X2, Box.Y1, Box.Y2, Button } */
+ MouseInput(k148_CommandUnfreezeGame, 0, 319, 0, 199, k1_LeftMouseButton),
+ MouseInput(k148_CommandUnfreezeGame, 0, 319, 0, 199, k2_RightMouseButton),
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput mouseInputActionAreaNames[5] = { // @ G0452_as_Graphic561_MouseInput_ActionAreaNames[5]
+ /* { Command, Box.X1, Box.X2, Box.Y1, Box.Y2, Button } */
+ MouseInput(k112_CommandClickInActionAreaPass, 285, 318, 77, 83, k1_LeftMouseButton),
+ MouseInput(k113_CommandClickInActionAreaAction_0, 234, 318, 86, 96, k1_LeftMouseButton),
+ MouseInput(k114_CommandClickInActionAreaAction_1, 234, 318, 98, 108, k1_LeftMouseButton),
+ MouseInput(k115_CommandClickInActionAreaAction_2, 234, 318, 110, 120, k1_LeftMouseButton),
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput mouseInputActionAreaIcons[5] = { // @ G0453_as_Graphic561_MouseInput_ActionAreaIcons[5]
+ /* { Command, Box.X1, Box.X2, Box.Y1, Box.Y2, Button } */
+ MouseInput(k116_CommandClickInActionAreaChampion_0_Action, 233, 252, 86, 120, k1_LeftMouseButton),
+ MouseInput(k117_CommandClickInActionAreaChampion_1_Action, 255, 274, 86, 120, k1_LeftMouseButton),
+ MouseInput(k118_CommandClickInActionAreaChampion_2_Action, 277, 296, 86, 120, k1_LeftMouseButton),
+ MouseInput(k119_CommandClickInActionAreaChampion_3_Action, 299, 318, 86, 120, k1_LeftMouseButton),
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput mouseInputSpellArea[9] = { // @ G0454_as_Graphic561_MouseInput_SpellArea[9]
+ /* { Command, Box.X1, Box.X2, Box.Y1, Box.Y2, Button } */
+ MouseInput(k101_CommandClickInSpellAreaSymbol_1, 235, 247, 51, 61, k1_LeftMouseButton),
+ MouseInput(k102_CommandClickInSpellAreaSymbol_2, 249, 261, 51, 61, k1_LeftMouseButton),
+ MouseInput(k103_CommandClickInSpellAreaSymbol_3, 263, 275, 51, 61, k1_LeftMouseButton),
+ MouseInput(k104_CommandClickInSpellAreaSymbol_4, 277, 289, 51, 61, k1_LeftMouseButton),
+ MouseInput(k105_CommandClickInSpellAreaSymbol_5, 291, 303, 51, 61, k1_LeftMouseButton),
+ MouseInput(k106_CommandClickInSpellAreaSymbol_6, 305, 317, 51, 61, k1_LeftMouseButton),
+ MouseInput(k108_CommandClickInSpeallAreaCastSpell, 234, 303, 63, 73, k1_LeftMouseButton),
+ MouseInput(k107_CommandClickInSpellAreaRecantSymbol, 305, 318, 63, 73, k1_LeftMouseButton),
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput mouseInputChampionNamesHands[13] = { // @ G0455_as_Graphic561_MouseInput_ChampionNamesHands[13]
+ /* { Command, Box.X1, Box.X2, Box.Y1, Box.Y2, Button } */
+ MouseInput(k16_CommandSetLeaderChampion_0, 0, 42, 0, 6, k1_LeftMouseButton),
+ MouseInput(k17_CommandSetLeaderChampion_1, 69, 111, 0, 6, k1_LeftMouseButton),
+ MouseInput(k18_CommandSetLeaderChampion_2, 138, 180, 0, 6, k1_LeftMouseButton),
+ MouseInput(k19_CommandSetLeaderChampion_3, 207, 249, 0, 6, k1_LeftMouseButton),
+ MouseInput(k20_CommandClickOnSlotBoxChampion_0_StatusBoxReadyHand, 4, 19, 10, 25, k1_LeftMouseButton),
+ MouseInput(k21_CommandClickOnSlotBoxChampion_0_StatusBoxActionHand, 24, 39, 10, 25, k1_LeftMouseButton),
+ MouseInput(k22_CommandClickOnSlotBoxChampion_1_StatusBoxReadyHand, 73, 88, 10, 25, k1_LeftMouseButton),
+ MouseInput(k23_CommandClickOnSlotBoxChampion_1_StatusBoxActionHand, 93, 108, 10, 25, k1_LeftMouseButton),
+ MouseInput(k24_CommandClickOnSlotBoxChampion_2_StatusBoxReadyHand, 142, 157, 10, 25, k1_LeftMouseButton),
+ MouseInput(k25_CommandClickOnSlotBoxChampion_2_StatusBoxActionHand, 162, 177, 10, 25, k1_LeftMouseButton),
+ MouseInput(k26_CommandClickOnSlotBoxChampion_3_StatusBoxReadyHand, 211, 226, 10, 25, k1_LeftMouseButton),
+ MouseInput(k27_CommandClickOnSlotBoxChampion_3_StatusBoxActionHand, 231, 246, 10, 25, k1_LeftMouseButton),
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput mouseInputPanelChest[9] = { // @ G0456_as_Graphic561_MouseInput_PanelChest[9]
+ /* { Command, Box.X1, Box.X2, Box.Y1, Box.Y2, Button } */
+ MouseInput(k58_CommandClickOnSlotBoxChest_1, 117, 132, 92, 107, k1_LeftMouseButton),
+ MouseInput(k59_CommandClickOnSlotBoxChest_2, 106, 121, 109, 124, k1_LeftMouseButton),
+ MouseInput(k60_CommandClickOnSlotBoxChest_3, 111, 126, 126, 141, k1_LeftMouseButton),
+ MouseInput(k61_CommandClickOnSlotBoxChest_4, 128, 143, 131, 146, k1_LeftMouseButton),
+ MouseInput(k62_CommandClickOnSlotBoxChest_5, 145, 160, 134, 149, k1_LeftMouseButton),
+ MouseInput(k63_CommandClickOnSlotBoxChest_6, 162, 177, 136, 151, k1_LeftMouseButton),
+ MouseInput(k64_CommandClickOnSlotBoxChest_7, 179, 194, 137, 152, k1_LeftMouseButton),
+ MouseInput(k65_CommandClickOnSlotBoxChest_8, 196, 211, 138, 153, k1_LeftMouseButton),
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput mouseInputPanelResurrectReincarnateCancel[4] = { // @ G0457_as_Graphic561_MouseInput_PanelResurrectReincarnateCancel[4]
+ /* { Command, Box.X1, Box.X2, Box.Y1, Box.Y2, Button } */
+ MouseInput(k160_CommandClickInPanelResurrect, 108, 158, 90, 138, k1_LeftMouseButton), /* Atari ST: Box = 104, 158, 86, 142 */
+ MouseInput(k161_CommandClickInPanelReincarnate, 161, 211, 90, 138, k1_LeftMouseButton), /* Atari ST: Box = 163, 217, 86, 142 */
+ MouseInput(k162_CommandClickInPanelCancel, 108, 211, 141, 153, k1_LeftMouseButton), /* Atari ST: Box = 104, 217, 146, 156 */
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput primaryMouseInputViewportDialog1Choice[2] = { // @ G0471_as_Graphic561_PrimaryMouseInput_ViewportDialog1Choice[2]
+ MouseInput(k210_CommandClickOnDialogChoice_1, 16, 207, 138, 152, k1_LeftMouseButton), /* Bottom button */
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput primaryMouseInputViewportDialog2Choices[3] = { // @ G0472_as_Graphic561_PrimaryMouseInput_ViewportDialog2Choices[3]
+ MouseInput(k210_CommandClickOnDialogChoice_1, 16, 207, 101, 115, k1_LeftMouseButton), /* Top button */
+ MouseInput(k211_CommandClickOnDialogChoice_2, 16, 207, 138, 152, k1_LeftMouseButton), /* Bottom button */
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput primaryMouseInputViewportDialog3Choices[4] = { // @ G0473_as_Graphic561_PrimaryMouseInput_ViewportDialog3Choices[4]
+ MouseInput(k210_CommandClickOnDialogChoice_1, 16, 207, 101, 115, k1_LeftMouseButton), /* Top button */
+ MouseInput(k211_CommandClickOnDialogChoice_2, 16, 101, 138, 152, k1_LeftMouseButton), /* Lower left button */
+ MouseInput(k212_CommandClickOnDialogChoice_3, 123, 207, 138, 152, k1_LeftMouseButton), /* Lower right button */
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput primaryMouseInputViewportDialog4Choices[5] = { // @ G0474_as_Graphic561_PrimaryMouseInput_ViewportDialog4Choices[5]
+ MouseInput(k210_CommandClickOnDialogChoice_1, 16, 101, 101, 115, k1_LeftMouseButton), /* Top left button */
+ MouseInput(k211_CommandClickOnDialogChoice_2, 123, 207, 101, 115, k1_LeftMouseButton), /* Top right button */
+ MouseInput(k212_CommandClickOnDialogChoice_3, 16, 101, 138, 152, k1_LeftMouseButton), /* Lower left button */
+ MouseInput(k213_CommandClickOnDialogChoice_4, 123, 207, 138, 152, k1_LeftMouseButton), /* Lower right button */
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput primaryMouseInputScreenDialog1Choice[2] = { // @ G0475_as_Graphic561_PrimaryMouseInput_ScreenDialog1Choice[2]
+ MouseInput(k210_CommandClickOnDialogChoice_1, 63, 254, 138, 152, k1_LeftMouseButton), /* Bottom button */
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput primaryMouseInputScreenDialog2Choices[3] = { // @ G0476_as_Graphic561_PrimaryMouseInput_ScreenDialog2Choices[3]
+ MouseInput(k210_CommandClickOnDialogChoice_1, 63, 254, 101, 115, k1_LeftMouseButton), /* Top button */
+ MouseInput(k211_CommandClickOnDialogChoice_2, 63, 254, 138, 152, k1_LeftMouseButton), /* Bottom button */
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput primaryMouseInputScreenDialog3Choices[4] = { // @ G0477_as_Graphic561_PrimaryMouseInput_ScreenDialog3Choices[4]
+ MouseInput(k210_CommandClickOnDialogChoice_1, 63, 254, 101, 115, k1_LeftMouseButton), /* Top button */
+ MouseInput(k211_CommandClickOnDialogChoice_2, 63, 148, 138, 152, k1_LeftMouseButton), /* Lower left button */
+ MouseInput(k212_CommandClickOnDialogChoice_3, 170, 254, 138, 152, k1_LeftMouseButton), /* Lower right button */
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+ MouseInput primaryMouseInputScreenDialog4Choices[5] = { // @ G0478_as_Graphic561_PrimaryMouseInput_ScreenDialog4Choices[5]
+ MouseInput(k210_CommandClickOnDialogChoice_1, 63, 148, 101, 115, k1_LeftMouseButton), /* Top left button */
+ MouseInput(k211_CommandClickOnDialogChoice_2, 170, 254, 101, 115, k1_LeftMouseButton), /* Top right button */
+ MouseInput(k212_CommandClickOnDialogChoice_3, 63, 148, 138, 152, k1_LeftMouseButton), /* Lower left button */
+ MouseInput(k213_CommandClickOnDialogChoice_4, 170, 254, 138, 152, k1_LeftMouseButton), /* Lower right button */
+ MouseInput(k0_CommandNone, 0, 0, 0, 0, k0_NoneMouseButton)
+ };
+
+ MouseInput *primaryMouseInputDialogSets[2][4] = { // @ G0480_aaps_PrimaryMouseInput_DialogSets
+ {
+ _primaryMouseInputViewportDialog1Choice,
+ _primaryMouseInputViewportDialog2Choices,
+ _primaryMouseInputViewportDialog3Choices,
+ _primaryMouseInputViewportDialog4Choices
+ },
+ {
+ _primaryMouseInputScreenDialog1Choice,
+ _primaryMouseInputScreenDialog2Choices,
+ _primaryMouseInputScreenDialog3Choices,
+ _primaryMouseInputScreenDialog4Choices
+ }
+ };
+
+ for (int i = 0; i < 2; i++) {
+ _primaryKeyboardInputFrozenGame[i] = primaryKeyboardInputFrozenGame[i];
+ _primaryMouseInputRestartGame[i] = primaryMouseInputRestartGame[i];
+ _primaryMouseInputViewportDialog1Choice[i] = primaryMouseInputViewportDialog1Choice[i];
+ _primaryMouseInputScreenDialog1Choice[i] = primaryMouseInputScreenDialog1Choice[i];
+ for (int j = 0; j < 4; j++)
+ _primaryMouseInputDialogSets[i][j] = primaryMouseInputDialogSets[i][j];
+ }
+
+ for (int i = 0; i < 3 ; i++) {
+ _primaryKeyboardInputPartySleeping[i] = primaryKeyboardInputPartySleeping[i];
+ _primaryMouseInputPartySleeping[i] = primaryMouseInputPartySleeping[i];
+ _primaryMouseInputFrozenGame[i] = primaryMouseInputFrozenGame[i];
+ _primaryMouseInputViewportDialog2Choices[i] = primaryMouseInputViewportDialog2Choices[i];
+ _primaryMouseInputScreenDialog2Choices[i] = primaryMouseInputScreenDialog2Choices[i];
+ }
+
+ for (int i = 0; i < 4; i++) {
+ _primaryMouseInputEntrance[i] = primaryMouseInputEntrance[i];
+ _mouseInputPanelResurrectReincarnateCancel[i] = mouseInputPanelResurrectReincarnateCancel[i];
+ _primaryMouseInputViewportDialog3Choices[i] = primaryMouseInputViewportDialog3Choices[i];
+ _primaryMouseInputScreenDialog3Choices[i] = primaryMouseInputScreenDialog3Choices[i];
+ }
+
+ for (int i = 0; i < 5; i++) {
+ _mouseInputActionAreaNames[i] = mouseInputActionAreaNames[i];
+ _mouseInputActionAreaIcons[i] = mouseInputActionAreaIcons[i];
+ _primaryMouseInputViewportDialog4Choices[i] = primaryMouseInputViewportDialog4Choices[i];
+ _primaryMouseInputScreenDialog4Choices[i] = primaryMouseInputScreenDialog4Choices[i];
+ }
+
+ for (int i = 0; i < 7; i++)
+ _primaryKeyboardInputInterface[i] = primaryKeyboardInputInterface[i];
+
+ for (int i = 0; i < 9; i++) {
+ _secondaryMouseInputMovement[i] = secondaryMouseInputMovement[i];
+ _mouseInputSpellArea[i] = mouseInputSpellArea[i];
+ _mouseInputPanelChest[i] = mouseInputPanelChest[i];
+ }
+
+ for (int i = 0; i < 13; i++)
+ _mouseInputChampionNamesHands[i] = mouseInputChampionNamesHands[i];
+
+ for (int i = 0; i < 19; i++)
+ _secondaryKeyboardInputMovement[i] = secondaryKeyboardInputMovement[i];
+
+ for (int i = 0; i < 20; i++)
+ _primaryMouseInputInterface[i] = primaryMouseInputInterface[i];
+
+ for (int i = 0; i < 38; i++)
+ _secondaryMouseInputChampionInventory[i] = secondaryMouseInputChampionInventory[i];
+}
+EventManager::EventManager(DMEngine *vm) : _vm(vm) {
+ _mousePos = Common::Point(0, 0);
+ _dummyMapIndex = 0;
+ _pendingClickPresent = false;
+ _pendingClickPos = Common::Point(0, 0);
+ _mousePointerOriginalColorsObject = nullptr;
+ _mousePointerOriginalColorsChampionIcon = nullptr;
+ _mousePointerTempBuffer = nullptr;
+ _isCommandQueueLocked = true;
+ _mousePointerType = 0;
+ _previousMousePointerType = 0;
+ _primaryMouseInput = nullptr;
+ _secondaryMouseInput = nullptr;
+ _mousePointerBitmapUpdated = true;
+ _refreshMousePointerInMainLoop = false;
+ _highlightBoxEnabled = false;
+ _useChampionIconOrdinalAsMousePointerBitmap = 0;
+ _pendingClickButton = k0_NoneMouseButton;
+ _useObjectAsMousePointerBitmap = false;
+ _useHandAsMousePointerBitmap = false;
+ _preventBuildPointerScreenArea = false;
+ _primaryKeyboardInput = nullptr;
+ _secondaryKeyboardInput = nullptr;
+ _ignoreMouseMovements = false;
+ warning("_g587_hideMousePointerRequestCount should start with value 1");
+ _hideMousePointerRequestCount = 0;
+ _mouseButtonStatus = 0;
+ _highlightScreenBox.setToZero();
+
+ initArrays();
+}
+
+EventManager::~EventManager() {
+ delete[] _mousePointerOriginalColorsObject;
+ delete[] _mousePointerTempBuffer;
+ delete[] _mousePointerOriginalColorsChampionIcon;
+}
+
+void EventManager::initMouse() {
+ static uint16 gK150_PalMousePointer[16] = {0x000, 0x666, 0x888, 0x620, 0x0CC, 0x840, 0x080, 0x0C0, 0xF00, 0xFA0, 0xC86, 0xFF0, 0x000, 0xAAA, 0x00F, 0xFFF}; // @ K0150_aui_Palette_MousePointer
+
+ if (!_mousePointerOriginalColorsObject)
+ _mousePointerOriginalColorsObject = new byte[32 * 18];
+ if (!_mousePointerTempBuffer)
+ _mousePointerTempBuffer = new byte[32 * 18];
+ if (!_mousePointerOriginalColorsChampionIcon)
+ _mousePointerOriginalColorsChampionIcon = new byte[32 * 18];
+
+ _mousePointerType = k0_pointerArrow;
+ _previousMousePointerType = k1_pointerHand;
+
+ byte mousePalette[16 * 3];
+ for (int i = 0; i < 16; ++i) {
+ mousePalette[i * 3] = (gK150_PalMousePointer[i] >> 8) * (256 / 16);
+ mousePalette[i * 3 + 1] = (gK150_PalMousePointer[i] >> 4) * (256 / 16);
+ mousePalette[i * 3 + 2] = gK150_PalMousePointer[i] * (256 / 16);
+ }
+ CursorMan.pushCursorPalette(mousePalette, 0, 16);
+
+ _mousePos = Common::Point(0, 0);
+ buildpointerScreenArea(_mousePos.x, _mousePos.y);
+ CursorMan.showMouse(false);
+
+ setMousePos(Common::Point(320 / 2, 200 / 2));
+}
+
+void EventManager::setMousePointerToNormal(int16 mousePointer) {
+ _preventBuildPointerScreenArea = true;
+ _useObjectAsMousePointerBitmap = false;
+ _useHandAsMousePointerBitmap = (mousePointer == k1_pointerHand);
+ _mousePointerBitmapUpdated = true;
+ _preventBuildPointerScreenArea = false;
+ buildpointerScreenArea(_mousePos.x, _mousePos.y);
+}
+
+void EventManager::setPointerToObject(byte *bitmap) {
+ static byte palChangesMousepointerOjbectIconShadow[16] = {120, 120, 120, 120, 120, 120, 120, 120,
+ 120, 120, 120, 120, 0, 120, 120, 120}; // @ K0027_auc_PaletteChanges_MousePointerObjectIconShadow
+ static byte palChangesMousePointerIcon[16] = {120, 10, 20, 30, 40, 50, 60, 70, 80, 90,
+ 100, 110, 0, 130, 140, 150}; // @ G0044_auc_Graphic562_PaletteChanges_MousePointerIcon
+ static Box boxMousePointerObjectShadow(2, 17, 2, 17); // @ G0619_s_Box_MousePointer_ObjectShadow
+ static Box boxMousePointerObject(0, 15, 0, 15); // @ G0620_s_Box_MousePointer_Object
+
+ _preventBuildPointerScreenArea = true;
+ _useObjectAsMousePointerBitmap = true;
+ _useHandAsMousePointerBitmap = false;
+ _mousePointerBitmapUpdated = true;
+ _vm->_displayMan->_useByteBoxCoordinates = true;
+ byte *L0051_puc_Bitmap = _mousePointerOriginalColorsObject;
+ memset(L0051_puc_Bitmap, 0, 32 * 18);
+
+ _vm->_displayMan->blitToBitmapShrinkWithPalChange(bitmap, _mousePointerTempBuffer, 16, 16, 16, 16, palChangesMousepointerOjbectIconShadow);
+ _vm->_displayMan->blitToBitmap(_mousePointerTempBuffer, L0051_puc_Bitmap, boxMousePointerObjectShadow, 0, 0, 8, 16, kM1_ColorNoTransparency, 16, 18);
+ _vm->_displayMan->blitToBitmapShrinkWithPalChange(bitmap, _mousePointerTempBuffer, 16, 16, 16, 16, palChangesMousePointerIcon);
+ _vm->_displayMan->blitToBitmap(_mousePointerTempBuffer, L0051_puc_Bitmap, boxMousePointerObject, 0, 0, 8, 16, k0_ColorBlack, 16, 18);
+
+ _preventBuildPointerScreenArea = false;
+ buildpointerScreenArea(_mousePos.x, _mousePos.y);
+}
+
+void EventManager::mouseDropChampionIcon() {
+ _preventBuildPointerScreenArea = true;
+ uint16 championIconIndex = _vm->ordinalToIndex(_useChampionIconOrdinalAsMousePointerBitmap);
+ _useChampionIconOrdinalAsMousePointerBitmap = _vm->indexToOrdinal(kDMChampionNone);
+ _mousePointerBitmapUpdated = true;
+ bool useByteBoxCoordinatesBackup = _vm->_displayMan->_useByteBoxCoordinates;
+ _vm->_displayMan->blitToScreen(_mousePointerOriginalColorsChampionIcon, &_vm->_championMan->_boxChampionIcons[championIconIndex << 2], 16, k12_ColorDarkestGray, 18);
+ _vm->_displayMan->_useByteBoxCoordinates = useByteBoxCoordinatesBackup;
+ _preventBuildPointerScreenArea = false;
+}
+
+void EventManager::buildpointerScreenArea(int16 mousePosX, int16 mousePosY) {
+ static unsigned char bitmapArrowPointer[288] = { // @ G0042_auc_Graphic562_Bitmap_ArrowPointer
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
+ 0x78, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00,
+ 0x7F, 0x80, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00,
+ 0x7E, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00,
+ 0x6C, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xC0, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00,
+ 0x84, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
+ 0x80, 0x40, 0x00, 0x00, 0x83, 0xC0, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0xA9, 0x00, 0x00, 0x00,
+ 0xC9, 0x00, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00,
+ 0xF0, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00,
+ 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00,
+ 0xFE, 0x00, 0x00, 0x00, 0xEF, 0x00, 0x00, 0x00, 0xCF, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00,
+ 0x07, 0x80, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ static unsigned char bitmapHanPointer[288] = { // @ G0043_auc_Graphic562_Bitmap_HandPointer
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00,
+ 0x35, 0x40, 0x00, 0x00, 0x1A, 0xA0, 0x00, 0x00, 0x0D, 0x50, 0x00, 0x00, 0x0E, 0xA8, 0x00, 0x00,
+ 0x07, 0xF8, 0x00, 0x00, 0xC7, 0xFC, 0x00, 0x00, 0x67, 0xFC, 0x00, 0x00, 0x77, 0xFC, 0x00, 0x00,
+ 0x3F, 0xFC, 0x00, 0x00, 0x3F, 0xFC, 0x00, 0x00, 0x1F, 0xFE, 0x00, 0x00, 0x07, 0xFF, 0x00, 0x00,
+ 0x01, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x4A, 0xA0, 0x00, 0x00, 0x25, 0x50, 0x00, 0x00,
+ 0x12, 0xA8, 0x00, 0x00, 0x11, 0x54, 0x00, 0x00, 0xC8, 0x04, 0x00, 0x00, 0x28, 0x02, 0x00, 0x00,
+ 0x98, 0x02, 0x00, 0x00, 0x88, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00,
+ 0x20, 0x01, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
+ 0x7F, 0xE0, 0x00, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x1F, 0xFC, 0x00, 0x00,
+ 0xCF, 0xFC, 0x00, 0x00, 0xEF, 0xFE, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x00, 0xFF, 0xFE, 0x00, 0x00,
+ 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0x00, 0x00,
+ 0x07, 0xFF, 0x00, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ _preventBuildPointerScreenArea = true;
+ if (_useChampionIconOrdinalAsMousePointerBitmap) {
+ if ((mousePosY > 28) || (mousePosX < 274)) {
+ _mousePointerType = k4_pointerTypeAutoselect;
+ mouseDropChampionIcon();
+ } else
+ _mousePointerType = k2_pointerTypeChampionIcon;
+ } else if (mousePosY >= 169)
+ _mousePointerType = k0_pointerTypeArrow;
+ else if (mousePosX >= 274)
+ _mousePointerType = k0_pointerTypeArrow;
+ else if (mousePosY <= 28) {
+ uint16 championIdx = mousePosX / 69;
+ uint16 xOverChampionStatusBox = mousePosX % 69;
+ if (championIdx >= _vm->_championMan->_partyChampionCount)
+ _mousePointerType = k4_pointerTypeAutoselect;
+ else if (xOverChampionStatusBox > 42)
+ _mousePointerType = k4_pointerTypeAutoselect;
+ else {
+ championIdx++;
+ if (championIdx == _vm->_inventoryMan->_inventoryChampionOrdinal)
+ _mousePointerType = k0_pointerTypeArrow;
+ else if (mousePosY <= 6)
+ _mousePointerType = k0_pointerTypeArrow;
+ else
+ _mousePointerType = k4_pointerTypeAutoselect;
+ }
+ } else if (mousePosX >= 224)
+ _mousePointerType = k0_pointerTypeArrow;
+ else
+ _mousePointerType = k4_pointerTypeAutoselect;
+
+ if (_mousePointerType == k4_pointerTypeAutoselect)
+ _mousePointerType = (_useObjectAsMousePointerBitmap) ? k1_pointerTypeObjectIcon : (_useHandAsMousePointerBitmap) ? k3_pointerTypeHand : k0_pointerTypeArrow;
+
+ if (_mousePointerBitmapUpdated || (_mousePointerType != _previousMousePointerType)) {
+ _mousePointerBitmapUpdated = false;
+ switch (_mousePointerType) {
+ case k0_pointerTypeArrow:
+ setMousePointerFromSpriteData(bitmapArrowPointer);
+ break;
+ case k1_pointerTypeObjectIcon:
+ CursorMan.replaceCursor(_mousePointerOriginalColorsObject, 32, 18, 0, 0, 0);
+ break;
+ case k2_pointerTypeChampionIcon:
+ CursorMan.replaceCursor(_mousePointerOriginalColorsChampionIcon, 32, 18, 0, 0, 0);
+ break;
+ case k3_pointerTypeHand:
+ setMousePointerFromSpriteData(bitmapHanPointer);
+ break;
+ }
+ }
+ _previousMousePointerType = _mousePointerType;
+ _preventBuildPointerScreenArea = false;
+}
+
+void EventManager::setMousePointer() {
+ if (_vm->_championMan->_leaderEmptyHanded)
+ setMousePointerToNormal((_vm->_championMan->_leaderIndex == kDMChampionNone) ? k0_pointerArrow : k1_pointerHand);
+ else
+ setPointerToObject(_vm->_objectMan->_objectIconForMousePointer);
+}
+
+void EventManager::showMouse() {
+ if (_hideMousePointerRequestCount++ == 0)
+ CursorMan.showMouse(true);
+}
+
+void EventManager::hideMouse() {
+ if (_hideMousePointerRequestCount-- == 1)
+ CursorMan.showMouse(false);
+}
+
+bool EventManager::isMouseButtonDown(MouseButton button) {
+ return (button != k0_NoneMouseButton) ? (_mouseButtonStatus & button) : (_mouseButtonStatus == 0);
+}
+
+void EventManager::setMousePos(Common::Point pos) {
+ g_system->warpMouse(pos.x, pos.y);
+}
+
+Common::EventType EventManager::processInput(Common::Event *grabKey, Common::Event *grabMouseClick) {
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_KEYDOWN: {
+ if (event.synthetic)
+ break;
+
+ if (event.kbd.keycode == Common::KEYCODE_d && event.kbd.hasFlags(Common::KBD_CTRL)) {
+ _vm->_console->attach();
+ return Common::EVENT_INVALID;
+ }
+
+ if (grabKey) {
+ *grabKey = event;
+ return event.type;
+ }
+
+ if (_primaryKeyboardInput) {
+ KeyboardInput *input = _primaryKeyboardInput;
+ while (input->_commandToIssue != k0_CommandNone) {
+ if ((input->_key == event.kbd.keycode) && (input->_modifiers == (event.kbd.flags & input->_modifiers))) {
+ processPendingClick(); // possible fix to BUG0_73
+ _commandQueue.push(Command(Common::Point(-1, -1), input->_commandToIssue));
+ break;
+ }
+ input++;
+ }
+ }
+
+ if (_secondaryKeyboardInput) {
+ KeyboardInput *input = _secondaryKeyboardInput;
+ while (input->_commandToIssue != k0_CommandNone) {
+ if ((input->_key == event.kbd.keycode) && (input->_modifiers == (event.kbd.flags & input->_modifiers))) {
+ processPendingClick(); // possible fix to BUG0_73
+ _commandQueue.push(Command(Common::Point(-1, -1), input->_commandToIssue));
+ break;
+ }
+ input++;
+ }
+ }
+ break;
+ }
+ case Common::EVENT_MOUSEMOVE:
+ if (!_ignoreMouseMovements)
+ _mousePos = event.mouse;
+ break;
+ case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_RBUTTONDOWN: {
+ MouseButton button = (event.type == Common::EVENT_LBUTTONDOWN) ? k1_LeftMouseButton : k2_RightMouseButton;
+ _mouseButtonStatus |= button;
+ if (grabMouseClick) {
+ *grabMouseClick = event;
+ return event.type;
+ }
+ _pendingClickPresent = true;
+ _pendingClickPos = _mousePos;
+ _pendingClickButton = button;
+ break;
+ }
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_RBUTTONUP: {
+ MouseButton button = (event.type == Common::EVENT_LBUTTONDOWN) ? k1_LeftMouseButton : k2_RightMouseButton;
+ _mouseButtonStatus &= ~button;
+ resetPressingEyeOrMouth();
+ break;
+ }
+ case Common::EVENT_QUIT:
+ _vm->_engineShouldQuit = true;
+ break;
+ default:
+ break;
+ }
+ }
+ if (_ignoreMouseMovements)
+ setMousePos(_mousePos);
+ return Common::EVENT_INVALID;
+}
+
+
+void EventManager::processPendingClick() {
+ if (_pendingClickPresent) {
+ _pendingClickPresent = false;
+ processClick(_pendingClickPos, _pendingClickButton);
+ }
+}
+
+void EventManager::processClick(Common::Point mousePos, MouseButton button) {
+ CommandType commandType;
+
+ commandType = getCommandTypeFromMouseInput(_primaryMouseInput, mousePos, button);
+ if (commandType == k0_CommandNone)
+ commandType = getCommandTypeFromMouseInput(_secondaryMouseInput, mousePos, button);
+
+ if (commandType != k0_CommandNone)
+ _commandQueue.push(Command(mousePos, commandType));
+
+ _isCommandQueueLocked = false;
+}
+
+CommandType EventManager::getCommandTypeFromMouseInput(MouseInput *input, Common::Point mousePos, MouseButton button) {
+ if (!input)
+ return k0_CommandNone;
+
+ CommandType commandType = k0_CommandNone;
+ while ((commandType = input->_commandTypeToIssue) != k0_CommandNone) {
+ if (input->_hitbox.isPointInside(mousePos) && input->_button == button)
+ break;
+ input++;
+ }
+ return commandType;
+}
+
+void EventManager::processCommandQueue() {
+ static KeyboardInput *primaryKeyboardInputBackup;
+ static KeyboardInput *secondaryKeyboardInputBackup;
+ static MouseInput *primaryMouseInputBackup;
+ static MouseInput *secondaryMouseInputBackup;
+
+ _isCommandQueueLocked = true;
+ if (_commandQueue.empty()) { /* If the command queue is empty */
+ _isCommandQueueLocked = false;
+ processPendingClick();
+ return;
+ }
+
+ Command cmd = _commandQueue.pop();
+ CommandType cmdType = cmd._type;
+ if ((cmdType >= k3_CommandMoveForward) && (cmdType <= k6_CommandMoveLeft) && (_vm->_disabledMovementTicks || (_vm->_projectileDisableMovementTicks && (_vm->_lastProjectileDisabledMovementDirection == (normalizeModulo4(_vm->_dungeonMan->_partyDir + cmdType - k3_CommandMoveForward)))))) { /* If movement is disabled */
+ _isCommandQueueLocked = false;
+ processPendingClick();
+ return;
+ }
+
+ int16 commandX = cmd._pos.x;
+ int16 commandY = cmd._pos.y;
+ _isCommandQueueLocked = false;
+ processPendingClick();
+ if ((cmdType == k2_CommandTurnRight) || (cmdType == k1_CommandTurnLeft)) {
+ commandTurnParty(cmdType);
+ return;
+ }
+
+ if ((cmdType >= k3_CommandMoveForward) && (cmdType <= k6_CommandMoveLeft)) {
+ commandMoveParty(cmdType);
+ return;
+ }
+
+ if ((cmdType >= k12_CommandClickInChampion_0_StatusBox) && (cmdType <= k15_CommandClickInChampion_3_StatusBox)) {
+ int16 championIdx = cmdType - k12_CommandClickInChampion_0_StatusBox;
+ if ((championIdx < _vm->_championMan->_partyChampionCount) && !_vm->_championMan->_candidateChampionOrdinal)
+ commandProcessTypes12to27_clickInChampionStatusBox(championIdx, commandX, commandY);
+
+ return;
+ }
+
+ if ((cmdType >= k125_CommandClickOnChamptionIcon_Top_Left) && (cmdType <= k128_CommandClickOnChamptionIcon_Lower_Left)) {
+ mouseProcessCommands125To128_clickOnChampionIcon(cmdType - k125_CommandClickOnChamptionIcon_Top_Left);
+
+ return;
+ }
+
+ if ((cmdType >= k28_CommandClickOnSlotBoxInventoryReadyHand) && (cmdType < (k65_CommandClickOnSlotBoxChest_8 + 1))) {
+ if (_vm->_championMan->_leaderIndex != kDMChampionNone)
+ _vm->_championMan->clickOnSlotBox(cmdType - k20_CommandClickOnSlotBoxChampion_0_StatusBoxReadyHand);
+
+ return;
+ }
+
+ if ((cmdType >= k7_CommandToggleInventoryChampion_0) && (cmdType <= k11_CommandCloseInventory)) {
+ if (cmdType == k11_CommandCloseInventory) {
+ delete _vm->_saveThumbnail;
+ _vm->_saveThumbnail = nullptr;
+ } else if (!_vm->_saveThumbnail) {
+ _vm->_saveThumbnail = new Common::MemoryWriteStreamDynamic();
+ Graphics::saveThumbnail(*_vm->_saveThumbnail);
+ }
+
+ int16 championIndex = cmdType - k7_CommandToggleInventoryChampion_0;
+ if (((championIndex == kDMChampionCloseInventory) || (championIndex < _vm->_championMan->_partyChampionCount)) && !_vm->_championMan->_candidateChampionOrdinal)
+ _vm->_inventoryMan->toggleInventory((ChampionIndex)championIndex);
+
+ return;
+ }
+
+ if (cmdType == k83_CommandToggleInventoryLeader) {
+ if (_vm->_championMan->_leaderIndex != kDMChampionNone)
+ _vm->_inventoryMan->toggleInventory(_vm->_championMan->_leaderIndex);
+
+ return;
+ }
+
+ if (cmdType == k100_CommandClickInSpellArea) {
+ if ((!_vm->_championMan->_candidateChampionOrdinal) && (_vm->_championMan->_magicCasterChampionIndex != kDMChampionNone))
+ commandProcessType100_clickInSpellArea(commandX, commandY);
+
+ return;
+ }
+
+ if (cmdType == k111_CommandClickInActionArea) {
+ if (!_vm->_championMan->_candidateChampionOrdinal)
+ commandProcessType111To115_ClickInActionArea(commandX, commandY);
+
+ return;
+ }
+
+ if (cmdType == k70_CommandClickOnMouth) {
+ _vm->_inventoryMan->clickOnMouth();
+ return;
+ }
+
+ if (cmdType == k71_CommandClickOnEye) {
+ _vm->_inventoryMan->clickOnEye();
+ return;
+ }
+
+ if (cmdType == k80_CommandClickInDungeonView) {
+ commandProcessType80ClickInDungeonView(commandX, commandY);
+ return;
+ }
+ if (cmdType == k81_CommandClickInPanel) {
+ commandProcess81ClickInPanel(commandX, commandY);
+ return;
+ }
+
+ if (_vm->_pressingEye || _vm->_pressingMouth)
+ return;
+
+ if (cmdType == k145_CommandSleep) {
+ if (!_vm->_championMan->_candidateChampionOrdinal) {
+ if (_vm->_inventoryMan->_inventoryChampionOrdinal)
+ _vm->_inventoryMan->toggleInventory(kDMChampionCloseInventory);
+
+ _vm->_menuMan->drawDisabledMenu();
+ _vm->_championMan->_partyIsSleeping = true;
+ drawSleepScreen();
+ _vm->_displayMan->drawViewport(k2_viewportAsBeforeSleepOrFreezeGame);
+ _vm->_waitForInputMaxVerticalBlankCount = 0;
+ _primaryMouseInput = _primaryMouseInputPartySleeping;
+ _secondaryMouseInput = 0;
+ _primaryKeyboardInput = _primaryKeyboardInputPartySleeping;
+ _secondaryKeyboardInput = nullptr;
+ discardAllInput();
+ }
+ return;
+ }
+
+ if (cmdType == k146_CommandWakeUp) {
+ _vm->_championMan->wakeUp();
+ return;
+ }
+
+ if (cmdType == k140_CommandSaveGame) {
+ if ((_vm->_championMan->_partyChampionCount > 0) && !_vm->_championMan->_candidateChampionOrdinal)
+ _vm->saveGame();
+
+ return;
+ }
+
+ if (cmdType == k147_CommandFreezeGame) {
+ _vm->_gameTimeTicking = false;
+ _vm->_menuMan->drawDisabledMenu();
+ _vm->_displayMan->fillBitmap(_vm->_displayMan->_bitmapViewport, k0_ColorBlack, 112, 136);
+
+ switch (_vm->getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY:
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, 81, 69, k4_ColorCyan, k0_ColorBlack,
+ "GAME FROZEN", k136_heightViewport);
+ break;
+ case Common::DE_DEU:
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, 66, 69, k4_ColorCyan, k0_ColorBlack,
+ "SPIEL ANGEHALTEN", k136_heightViewport);
+ break;
+ case Common::FR_FRA:
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, 84, 69, k4_ColorCyan, k0_ColorBlack,
+ "JEU BLOQUE", k136_heightViewport);
+ break;
+ }
+ _vm->_displayMan->drawViewport(k2_viewportAsBeforeSleepOrFreezeGame);
+ primaryMouseInputBackup = _primaryMouseInput;
+ secondaryMouseInputBackup = _secondaryMouseInput;
+ primaryKeyboardInputBackup = _primaryKeyboardInput;
+ secondaryKeyboardInputBackup = _secondaryKeyboardInput;
+ _primaryMouseInput = _primaryMouseInputFrozenGame;
+ _secondaryMouseInput = 0;
+ _primaryKeyboardInput = _primaryKeyboardInputFrozenGame;
+ _secondaryKeyboardInput = nullptr;
+ discardAllInput();
+ return;
+ }
+
+ if (cmdType == k148_CommandUnfreezeGame) {
+ _vm->_gameTimeTicking = true;
+ _vm->_menuMan->drawEnabledMenus();
+ _primaryMouseInput = primaryMouseInputBackup;
+ _secondaryMouseInput = secondaryMouseInputBackup;
+ _primaryKeyboardInput = primaryKeyboardInputBackup;
+ _secondaryKeyboardInput = secondaryKeyboardInputBackup;
+ discardAllInput();
+ return;
+ }
+
+ if (cmdType == k200_CommandEntranceEnterDungeon) {
+ _vm->_newGameFl = k1_modeLoadDungeon;
+ return;
+ }
+
+ if (cmdType == k201_CommandEntranceResume) {
+ _vm->_newGameFl = k0_modeLoadSavedGame;
+ return;
+ }
+
+ if (cmdType == k202_CommandEntranceDrawCredits) {
+ _vm->entranceDrawCredits();
+ return;
+ }
+
+ if ((cmdType >= k210_CommandClickOnDialogChoice_1) && (cmdType <= k213_CommandClickOnDialogChoice_4)) {
+ _vm->_dialog->_selectedDialogChoice = cmdType - (k210_CommandClickOnDialogChoice_1 - 1);
+ return;
+ }
+
+ if (cmdType == k215_CommandRestartGame)
+ _vm->_restartGameRequest = true;
+}
+
+void EventManager::commandTurnParty(CommandType cmdType) {
+ _vm->_stopWaitingForPlayerInput = true;
+ if (cmdType == k1_CommandTurnLeft)
+ commandHighlightBoxEnable(234, 261, 125, 145);
+ else
+ commandHighlightBoxEnable(291, 318, 125, 145);
+
+ uint16 partySquare = _vm->_dungeonMan->getSquare(_vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY).toByte();
+ if (Square(partySquare).getType() == k3_StairsElemType) {
+ commandTakeStairs(getFlag(partySquare, k0x0004_StairsUp));
+ return;
+ }
+
+ _vm->_moveSens->processThingAdditionOrRemoval(_vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, Thing::_party, true, false);
+ _vm->_championMan->setPartyDirection(normalizeModulo4(_vm->_dungeonMan->_partyDir + ((cmdType == k2_CommandTurnRight) ? 1 : 3)));
+ _vm->_moveSens->processThingAdditionOrRemoval(_vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, Thing::_party, true, true);
+}
+
+void EventManager::commandMoveParty(CommandType cmdType) {
+ static Box boxMovementArrows[4] = { // @ G0463_as_Graphic561_Box_MovementArrows
+ /* { X1, X2, Y1, Y2 } */
+ Box(263, 289, 125, 145), /* Forward */
+ Box(291, 318, 147, 167), /* Right */
+ Box(263, 289, 147, 167), /* Backward */
+ Box(234, 261, 147, 167) /* Left */
+ };
+
+ static int16 movementArrowToStepForwardCount[4] = { // @ G0465_ai_Graphic561_MovementArrowToStepForwardCount
+ 1, /* Forward */
+ 0, /* Right */
+ -1, /* Backward */
+ 0 /* Left */
+ };
+
+ static int16 movementArrowToSepRightCount[4] = { // @ G0466_ai_Graphic561_MovementArrowToStepRightCount
+ 0, /* Forward */
+ 1, /* Right */
+ 0, /* Backward */
+ -1 /* Left */
+ };
+
+ _vm->_stopWaitingForPlayerInput = true;
+ Champion *championsPtr = _vm->_championMan->_champions;
+ for (uint16 idx = kDMChampionFirst; idx < _vm->_championMan->_partyChampionCount; idx++) {
+ _vm->_championMan->decrementStamina(idx, ((championsPtr->_load * 3) / _vm->_championMan->getMaximumLoad(championsPtr)) + 1); /* BUG0_50 When a champion is brought back to life at a Vi Altar, his current stamina is lower than what it was before dying. Each time the party moves the current stamina of all champions is decreased, including for dead champions, by an amount that depends on the current load of the champion. For a dead champion the load before he died is used */
+ championsPtr++;
+ }
+ uint16 movementArrowIdx = cmdType - k3_CommandMoveForward;
+ Box *highlightBox = &boxMovementArrows[movementArrowIdx];
+ commandHighlightBoxEnable(highlightBox->_x1, highlightBox->_x2, highlightBox->_y1, highlightBox->_y2);
+ int16 partyMapX = _vm->_dungeonMan->_partyMapX;
+ int16 partyMapY = _vm->_dungeonMan->_partyMapY;
+ uint16 AL1115_ui_Square = _vm->_dungeonMan->getSquare(partyMapX, partyMapY).toByte();
+ bool isStairsSquare = (Square(AL1115_ui_Square).getType() == k3_StairsElemType);
+ if (isStairsSquare && (movementArrowIdx == 2)) { /* If moving backward while in stairs */
+ commandTakeStairs(getFlag(AL1115_ui_Square, k0x0004_StairsUp));
+ return;
+ }
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(_vm->_dungeonMan->_partyDir, movementArrowToStepForwardCount[movementArrowIdx], movementArrowToSepRightCount[movementArrowIdx], partyMapX, partyMapY);
+ int16 partySquareType = Square(AL1115_ui_Square = _vm->_dungeonMan->getSquare(partyMapX, partyMapY).toByte()).getType();
+ if (partySquareType == k3_ElementTypeStairs) {
+ _vm->_moveSens->getMoveResult(Thing::_party, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, kM1_MapXNotOnASquare, 0);
+ _vm->_dungeonMan->_partyMapX = partyMapX;
+ _vm->_dungeonMan->_partyMapY = partyMapY;
+ commandTakeStairs(getFlag(AL1115_ui_Square, k0x0004_StairsUp));
+ return;
+ }
+
+ bool isMovementBlocked = false;
+ if (partySquareType == k0_ElementTypeWall)
+ isMovementBlocked = true;
+ else if (partySquareType == k4_DoorElemType) {
+ byte doorState = Square(AL1115_ui_Square).getDoorState();
+ isMovementBlocked = (doorState != k0_doorState_OPEN) && (doorState != k1_doorState_FOURTH) && (doorState != k5_doorState_DESTROYED);
+ } else if (partySquareType == k6_ElementTypeFakeWall)
+ isMovementBlocked = (!getFlag(AL1115_ui_Square, k0x0004_FakeWallOpen) && !getFlag(AL1115_ui_Square, k0x0001_FakeWallImaginary));
+
+ if (_vm->_championMan->_partyChampionCount) {
+ if (isMovementBlocked) {
+ movementArrowIdx += (_vm->_dungeonMan->_partyDir + 2);
+ int16 L1124_i_FirstDamagedChampionIndex = _vm->_championMan->getTargetChampionIndex(partyMapX, partyMapY, normalizeModulo4(movementArrowIdx));
+ int16 L1125_i_SecondDamagedChampionIndex = _vm->_championMan->getTargetChampionIndex(partyMapX, partyMapY, returnNextVal(movementArrowIdx));
+ int16 damage = _vm->_championMan->addPendingDamageAndWounds_getDamage(L1124_i_FirstDamagedChampionIndex, 1, kDMWoundTorso | kDMWoundLegs, kDMAttackTypeSelf);
+ if (L1124_i_FirstDamagedChampionIndex != L1125_i_SecondDamagedChampionIndex)
+ damage |= _vm->_championMan->addPendingDamageAndWounds_getDamage(L1125_i_SecondDamagedChampionIndex, 1, kDMWoundTorso | kDMWoundLegs, kDMAttackTypeSelf);
+
+ if (damage)
+ _vm->_sound->requestPlay(k18_soundPARTY_DAMAGED, partyMapX, partyMapY, k0_soundModePlayImmediately);
+ } else {
+ isMovementBlocked = (_vm->_groupMan->groupGetThing(partyMapX, partyMapY) != Thing::_endOfList);
+ if (isMovementBlocked)
+ _vm->_groupMan->processEvents29to41(partyMapX, partyMapY, kM1_TMEventTypeCreateReactionEvent31ParyIsAdjacent, 0);
+ }
+ }
+
+ // DEBUG CODE: check for Console flag
+ if (isMovementBlocked && !_vm->_console->_debugNoclip) {
+ discardAllInput();
+ _vm->_stopWaitingForPlayerInput = false;
+ return;
+ }
+
+ if (isStairsSquare)
+ _vm->_moveSens->getMoveResult(Thing::_party, kM1_MapXNotOnASquare, 0, partyMapX, partyMapY);
+ else
+ _vm->_moveSens->getMoveResult(Thing::_party, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, partyMapX, partyMapY);
+
+ uint16 disabledMovtTicks = 1;
+ championsPtr = _vm->_championMan->_champions;
+ for (uint16 idx = kDMChampionFirst; idx < _vm->_championMan->_partyChampionCount; idx++) {
+ if (championsPtr->_currHealth)
+ disabledMovtTicks = MAX((int32)disabledMovtTicks, (int32)_vm->_championMan->getMovementTicks(championsPtr));
+
+ championsPtr++;
+ }
+ _vm->_disabledMovementTicks = disabledMovtTicks;
+ _vm->_projectileDisableMovementTicks = 0;
+}
+
+bool EventManager::isLeaderHandObjThrown(int16 posX, int16 posY) {
+#define k0_sideLeft 0 // @ C0_SIDE_LEFT
+#define k1_sideRight 1 // @ C0_SIDE_LEFT
+
+ if ((posY < 47) || (posY > 102))
+ return false;
+
+ bool objectThrownFl;
+ if (posX <= 111) {
+ if (_vm->_dungeonMan->_squareAheadElement == k17_ElementTypeDoorFront) {
+ if (posX < 64)
+ return false;
+ } else if (posX < 32)
+ return false;
+
+ // Strangerke: Only present in CSB2.1... But it fixes a bug so we keep it
+ objectThrownFl = _vm->_championMan->isLeaderHandObjectThrown(k0_sideLeft);
+ } else {
+ if (_vm->_dungeonMan->_squareAheadElement == k17_ElementTypeDoorFront) {
+ if (posX > 163)
+ return false;
+ } else if (posX > 191)
+ return false;
+
+ objectThrownFl = _vm->_championMan->isLeaderHandObjectThrown(k1_sideRight);
+ }
+
+ if (objectThrownFl)
+ _vm->_stopWaitingForPlayerInput = true;
+
+ return objectThrownFl;
+}
+
+void EventManager::setMousePointerFromSpriteData(byte *mouseSprite) {
+ byte bitmap[16 * 18];
+ memset(bitmap, 0, sizeof(bitmap));
+ for (int16 imgPart = 1; imgPart < 3; ++imgPart) {
+ for (byte *line = mouseSprite + 72 * imgPart, *pixel = bitmap;
+ line < mouseSprite + 72 * (imgPart + 1);
+ line += 4) {
+
+ uint16 words[2];
+ words[0] = READ_BE_UINT16(line);
+ words[1] = READ_BE_UINT16(line + 2);
+ for (int16 i = 15; i >= 0; --i, ++pixel) {
+ uint16 val = (((words[0] >> i) & 1) | (((words[1] >> i) & 1) << 1)) << (imgPart & 0x2);
+ if (val)
+ *pixel = val + 8;
+ }
+ }
+ }
+
+ CursorMan.replaceCursor(bitmap, 16, 18, 0, 0, 0);
+}
+
+void EventManager::commandSetLeader(ChampionIndex champIndex) {
+ ChampionMan &cm = *_vm->_championMan;
+ ChampionIndex leaderIndex;
+
+ if ((cm._leaderIndex == champIndex) || ((champIndex != kDMChampionNone) && !cm._champions[champIndex]._currHealth))
+ return;
+
+ if (cm._leaderIndex != kDMChampionNone) {
+ leaderIndex = cm._leaderIndex;
+ cm._champions[leaderIndex].setAttributeFlag(kDMAttributeLoad, true);
+ cm._champions[leaderIndex].setAttributeFlag(kDMAttributeNameTitle, true);
+ cm._champions[leaderIndex]._load -= _vm->_dungeonMan->getObjectWeight(cm._leaderHandObject);
+ cm._leaderIndex = kDMChampionNone;
+ cm.drawChampionState(leaderIndex);
+ }
+ if (champIndex == kDMChampionNone) {
+ cm._leaderIndex = kDMChampionNone;
+ return;
+ }
+ cm._leaderIndex = champIndex;
+ Champion *champion = &cm._champions[cm._leaderIndex];
+ champion->_dir = _vm->_dungeonMan->_partyDir;
+ cm._champions[champIndex]._load += _vm->_dungeonMan->getObjectWeight(cm._leaderHandObject);
+ if (_vm->indexToOrdinal(champIndex) != cm._candidateChampionOrdinal) {
+ champion->setAttributeFlag(kDMAttributeIcon, true);
+ champion->setAttributeFlag(kDMAttributeNameTitle, true);
+ cm.drawChampionState(champIndex);
+ }
+}
+
+void EventManager::commandProcessType80ClickInDungeonViewTouchFrontWall() {
+ uint16 mapX = _vm->_dungeonMan->_partyMapX + _vm->_dirIntoStepCountEast[_vm->_dungeonMan->_partyDir];
+ uint16 mapY = _vm->_dungeonMan->_partyMapY + _vm->_dirIntoStepCountNorth[_vm->_dungeonMan->_partyDir];
+
+ if ((mapX >= 0) && (mapX < _vm->_dungeonMan->_currMapWidth)
+ && (mapY >= 0) && (mapY < _vm->_dungeonMan->_currMapHeight))
+ _vm->_stopWaitingForPlayerInput = _vm->_moveSens->sensorIsTriggeredByClickOnWall(mapX, mapY, returnOppositeDir(_vm->_dungeonMan->_partyDir));
+}
+
+void EventManager::commandProcessType80ClickInDungeonView(int16 posX, int16 posY) {
+ Box boxObjectPiles[4] = { // @ G0462_as_Graphic561_Box_ObjectPiles
+ /* { X1, X2, Y1, Y2 } */
+ Box(24, 111, 148, 168), /* Front left */
+ Box(112, 199, 148, 168), /* Front right */
+ Box(112, 183, 122, 147), /* Back right */
+ Box(40, 111, 122, 147) /* Back left */
+ };
+
+ if (_vm->_dungeonMan->_squareAheadElement == k17_ElementTypeDoorFront) {
+ if (_vm->_championMan->_leaderIndex == kDMChampionNone)
+ return;
+
+ int16 L1155_i_MapX = _vm->_dungeonMan->_partyMapX + _vm->_dirIntoStepCountEast[_vm->_dungeonMan->_partyDir];
+ int16 L1156_i_MapY = _vm->_dungeonMan->_partyMapY + _vm->_dirIntoStepCountNorth[_vm->_dungeonMan->_partyDir];
+
+ if (_vm->_championMan->_leaderEmptyHanded) {
+ Junk *junkPtr = (Junk*)_vm->_dungeonMan->getSquareFirstThingData(L1155_i_MapX, L1156_i_MapY);
+ if ((((Door*)junkPtr)->hasButton()) && _vm->_dungeonMan->_dungeonViewClickableBoxes[k5_ViewCellDoorButtonOrWallOrn].isPointInside(posX, posY - 33)) {
+ _vm->_stopWaitingForPlayerInput = true;
+ _vm->_sound->requestPlay(k01_soundSWITCH, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, k1_soundModePlayIfPrioritized);
+ _vm->_moveSens->addEvent(k10_TMEventTypeDoor, L1155_i_MapX, L1156_i_MapY, 0, k2_SensorEffToggle, _vm->_gameTime + 1);
+ return;
+ }
+ } else if (isLeaderHandObjThrown(posX, posY))
+ return;
+ }
+
+ if (_vm->_championMan->_leaderEmptyHanded) {
+ for (uint16 currViewCell = k0_ViewCellFronLeft; currViewCell < k5_ViewCellDoorButtonOrWallOrn + 1; currViewCell++) {
+ if (_vm->_dungeonMan->_dungeonViewClickableBoxes[currViewCell].isPointInside(posX, posY - 33)) {
+ if (currViewCell == k5_ViewCellDoorButtonOrWallOrn) {
+ if (!_vm->_dungeonMan->_isFacingAlcove)
+ commandProcessType80ClickInDungeonViewTouchFrontWall();
+ } else
+ processType80_clickInDungeonView_grabLeaderHandObject(currViewCell);
+
+ return;
+ }
+ }
+ } else {
+ Thing thingHandObject = _vm->_championMan->_leaderHandObject;
+ Junk *junkPtr = (Junk*)_vm->_dungeonMan->getThingData(thingHandObject);
+ if (_vm->_dungeonMan->_squareAheadElement == k0_ElementTypeWall) {
+ for (uint16 currViewCell = k0_ViewCellFronLeft; currViewCell < k1_ViewCellFrontRight + 1; currViewCell++) {
+ if (boxObjectPiles[currViewCell].isPointInside(posX, posY)) {
+ processType80_clickInDungeonViewDropLeaderHandObject(currViewCell);
+ return;
+ }
+ }
+ if (_vm->_dungeonMan->_dungeonViewClickableBoxes[k5_ViewCellDoorButtonOrWallOrn].isPointInside(posX, posY - 33)) {
+ if (_vm->_dungeonMan->_isFacingAlcove)
+ processType80_clickInDungeonViewDropLeaderHandObject(k4_ViewCellAlcove);
+ else {
+ if (_vm->_dungeonMan->_isFacingFountain) {
+ uint16 iconIdx = _vm->_objectMan->getIconIndex(thingHandObject);
+ uint16 weight = _vm->_dungeonMan->getObjectWeight(thingHandObject);
+ if ((iconIdx >= kDMIconIndiceJunkWater) && (iconIdx <= kDMIconIndiceJunkWaterSkin))
+ junkPtr->setChargeCount(3); /* Full */
+ else if (iconIdx == kDMIconIndicePotionEmptyFlask)
+ ((Potion*)junkPtr)->setType(k15_PotionTypeWaterFlask);
+ else {
+ commandProcessType80ClickInDungeonViewTouchFrontWall();
+ return;
+ }
+ _vm->_championMan->drawChangedObjectIcons();
+ _vm->_championMan->_champions[_vm->_championMan->_leaderIndex]._load += _vm->_dungeonMan->getObjectWeight(thingHandObject) - weight;
+ }
+ commandProcessType80ClickInDungeonViewTouchFrontWall();
+ }
+ }
+ } else {
+ if (isLeaderHandObjThrown(posX, posY))
+ return;
+
+ for (uint16 currViewCell = k0_ViewCellFronLeft; currViewCell < k3_ViewCellBackLeft + 1; currViewCell++) {
+ if (boxObjectPiles[currViewCell].isPointInside(posX, posY)) {
+ processType80_clickInDungeonViewDropLeaderHandObject(currViewCell);
+ return;
+ }
+ }
+ }
+ }
+}
+
+void EventManager::commandProcessCommands160To162ClickInResurrectReincarnatePanel(CommandType commandType) {
+ ChampionMan &champMan = *_vm->_championMan;
+ InventoryMan &invMan = *_vm->_inventoryMan;
+ DisplayMan &dispMan = *_vm->_displayMan;
+ DungeonMan &dunMan = *_vm->_dungeonMan;
+
+ uint16 championIndex = champMan._partyChampionCount - 1;
+ Champion *champ = &champMan._champions[championIndex];
+ if (commandType == k162_CommandClickInPanelCancel) {
+ invMan.toggleInventory(kDMChampionCloseInventory);
+ champMan._candidateChampionOrdinal = _vm->indexToOrdinal(kDMChampionNone);
+ if (champMan._partyChampionCount == 1) {
+ commandSetLeader(kDMChampionNone);
+ }
+ champMan._partyChampionCount--;
+ Box box;
+ box._y1 = 0;
+ box._y2 = 28;
+ box._x1 = championIndex * k69_ChampionStatusBoxSpacing;
+ box._x2 = box._x1 + 66;
+ dispMan._useByteBoxCoordinates = false;
+ dispMan.fillScreenBox(box, k0_ColorBlack);
+ dispMan.fillScreenBox(_vm->_championMan->_boxChampionIcons[champMan.getChampionIconIndex(champ->_cell, dunMan._partyDir) * 2], k0_ColorBlack);
+ _vm->_menuMan->drawEnabledMenus();
+ showMouse();
+ return;
+ }
+
+ champMan._candidateChampionOrdinal = _vm->indexToOrdinal(kDMChampionNone);
+ int16 mapX = dunMan._partyMapX + _vm->_dirIntoStepCountEast[dunMan._partyDir];
+ int16 mapY = dunMan._partyMapY + _vm->_dirIntoStepCountNorth[dunMan._partyDir];
+
+ for (uint16 slotIndex = kDMSlotReadyHand; slotIndex < kDMSlotChest1; slotIndex++) {
+ Thing thing = champ->getSlot((ChampionSlot)slotIndex);
+ if (thing != Thing::_none) {
+ _vm->_dungeonMan->unlinkThingFromList(thing, Thing(0), mapX, mapY);
+ }
+ }
+ Thing thing = dunMan.getSquareFirstThing(mapX, mapY);
+ for (;;) { // infinite
+ if (thing.getType() == k3_SensorThingType) {
+ ((Sensor*)dunMan.getThingData(thing))->setTypeDisabled();
+ break;
+ }
+ thing = dunMan.getNextThing(thing);
+ }
+
+ if (commandType == k161_CommandClickInPanelReincarnate) {
+ champMan.renameChampion(champ);
+ if (_vm->_engineShouldQuit)
+ return;
+ champ->resetSkillsToZero();
+
+ for (uint16 i = 0; i < 12; i++) {
+ uint16 statIndex = _vm->getRandomNumber(7);
+ champ->getStatistic((ChampionStatType)statIndex, kDMStatCurrent)++; // returns reference
+ champ->getStatistic((ChampionStatType)statIndex, kDMStatMaximum)++; // returns reference
+ }
+ }
+
+ if (champMan._partyChampionCount == 1) {
+ _vm->_projexpl->_lastPartyMovementTime = _vm->_gameTime;
+ commandSetLeader(kDMChampionFirst);
+ _vm->_menuMan->setMagicCasterAndDrawSpellArea(kDMChampionFirst);
+ } else
+ _vm->_menuMan->drawSpellAreaControls(champMan._magicCasterChampionIndex);
+
+ _vm->_textMan->printLineFeed();
+ Color champColor = _vm->_championMan->_championColor[championIndex];
+ _vm->_textMan->printMessage(champColor, champ->_name);
+
+ switch (_vm->getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY:
+ _vm->_textMan->printMessage(champColor, (commandType == k160_CommandClickInPanelResurrect) ? " RESURRECTED." : " REINCARNATED.");
+ break;
+ case Common::DE_DEU:
+ _vm->_textMan->printMessage(champColor, (commandType == k160_CommandClickInPanelResurrect) ? " VOM TODE ERWECKT." : " REINKARNIERT.");
+ break;
+ case Common::FR_FRA:
+ _vm->_textMan->printMessage(champColor, (commandType == k160_CommandClickInPanelResurrect) ? " RESSUSCITE." : " REINCARNE.");
+ break;
+ }
+
+ invMan.toggleInventory(kDMChampionCloseInventory);
+ _vm->_menuMan->drawEnabledMenus();
+ setMousePointerToNormal((_vm->_championMan->_leaderIndex == kDMChampionNone) ? k0_pointerArrow : k1_pointerHand);
+}
+
+void EventManager::commandProcess81ClickInPanel(int16 x, int16 y) {
+ ChampionMan &champMan = *_vm->_championMan;
+ InventoryMan &invMan = *_vm->_inventoryMan;
+
+ CommandType commandType;
+ switch (invMan._panelContent) {
+ case k4_PanelContentChest:
+ if (champMan._leaderIndex == kDMChampionNone) // if no leader
+ return;
+ commandType = getCommandTypeFromMouseInput(_mouseInputPanelChest, Common::Point(x, y), k1_LeftMouseButton);
+ if (commandType != k0_CommandNone)
+ _vm->_championMan->clickOnSlotBox(commandType - k20_CommandClickOnSlotBoxChampion_0_StatusBoxReadyHand);
+ break;
+ case k5_PanelContentResurrectReincarnate:
+ if (!champMan._leaderEmptyHanded)
+ break;
+ commandType = getCommandTypeFromMouseInput(_mouseInputPanelResurrectReincarnateCancel, Common::Point(x, y), k1_LeftMouseButton);
+ if (commandType != k0_CommandNone)
+ commandProcessCommands160To162ClickInResurrectReincarnatePanel(commandType);
+ break;
+ default:
+ break;
+ }
+}
+
+void EventManager::processType80_clickInDungeonView_grabLeaderHandObject(uint16 viewCell) {
+ if (_vm->_championMan->_leaderIndex == kDMChampionNone)
+ return;
+
+ int16 mapX = _vm->_dungeonMan->_partyMapX;
+ int16 mapY = _vm->_dungeonMan->_partyMapY;
+ if (viewCell >= k2_ViewCellBackRight) {
+ mapX += _vm->_dirIntoStepCountEast[_vm->_dungeonMan->_partyDir], mapY += _vm->_dirIntoStepCountNorth[_vm->_dungeonMan->_partyDir];
+ Thing groupThing = _vm->_groupMan->groupGetThing(mapX, mapY);
+ if ((groupThing != Thing::_endOfList) &&
+ !_vm->_moveSens->isLevitating(groupThing) &&
+ _vm->_groupMan->getCreatureOrdinalInCell((Group*)_vm->_dungeonMan->getThingData(groupThing), normalizeModulo4(viewCell + _vm->_dungeonMan->_partyDir))) {
+ return; /* It is not possible to grab an object on floor if there is a non levitating creature on its cell */
+ }
+ }
+
+ Thing topPileThing = _vm->_dungeonMan->_pileTopObject[viewCell];
+ if (_vm->_objectMan->getIconIndex(topPileThing) != kDMIconIndiceNone) {
+ _vm->_moveSens->getMoveResult(topPileThing, mapX, mapY, kM1_MapXNotOnASquare, 0);
+ _vm->_championMan->putObjectInLeaderHand(topPileThing, true);
+ }
+
+ _vm->_stopWaitingForPlayerInput = true;
+}
+
+void EventManager::processType80_clickInDungeonViewDropLeaderHandObject(uint16 viewCell) {
+ if (_vm->_championMan->_leaderIndex == kDMChampionNone)
+ return;
+
+ int16 mapX = _vm->_dungeonMan->_partyMapX;
+ int16 mapY = _vm->_dungeonMan->_partyMapY;
+ bool droppingIntoAnAlcove = (viewCell == k4_ViewCellAlcove);
+ if (droppingIntoAnAlcove)
+ viewCell = k2_ViewCellBackRight;
+
+ if (viewCell > k1_ViewCellFrontRight)
+ mapX += _vm->_dirIntoStepCountEast[_vm->_dungeonMan->_partyDir], mapY += _vm->_dirIntoStepCountNorth[_vm->_dungeonMan->_partyDir];
+
+ uint16 currCell = normalizeModulo4(_vm->_dungeonMan->_partyDir + viewCell);
+ Thing removedThing = _vm->_championMan->getObjectRemovedFromLeaderHand();
+ _vm->_moveSens->getMoveResult(thingWithNewCell(removedThing, currCell), kM1_MapXNotOnASquare, 0, mapX, mapY);
+ if (droppingIntoAnAlcove && _vm->_dungeonMan->_isFacingViAltar && (_vm->_objectMan->getIconIndex(removedThing) == kDMIconIndiceJunkChampionBones)) {
+ Junk *removedJunk = (Junk*)_vm->_dungeonMan->getThingData(removedThing);
+ TimelineEvent newEvent;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + 1);
+ newEvent._type = k13_TMEventTypeViAltarRebirth;
+ newEvent._priority = removedJunk->getChargeCount();
+ newEvent._B._location._mapX = mapX;
+ newEvent._B._location._mapY = mapY;
+ newEvent._C.A._cell = currCell;
+ newEvent._C.A._effect = k2_SensorEffToggle;
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ }
+ _vm->_stopWaitingForPlayerInput = true;
+}
+
+bool EventManager::hasPendingClick(Common::Point& point, MouseButton button) {
+ if (_pendingClickButton && button == _pendingClickButton)
+ point = _pendingClickPos;
+
+ return _pendingClickPresent;
+}
+
+void EventManager::drawSleepScreen() {
+ _vm->_displayMan->fillBitmap(_vm->_displayMan->_bitmapViewport, k0_ColorBlack, 112, 136);
+ switch (_vm->getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY:
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, 93, 69, k4_ColorCyan, k0_ColorBlack, "WAKE UP", k136_heightViewport);
+ break;
+ case Common::DE_DEU:
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, 96, 69, k4_ColorCyan, k0_ColorBlack, "WECKEN", k136_heightViewport);
+ break;
+ case Common::FR_FRA:
+ _vm->_textMan->printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, 72, 69, k4_ColorCyan, k0_ColorBlack, "REVEILLEZ-VOUS", k136_heightViewport);
+ break;
+ }
+}
+
+void EventManager::discardAllInput() {
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event) && !_vm->_engineShouldQuit) {
+ if (event.type == Common::EVENT_QUIT)
+ _vm->_engineShouldQuit = true;
+ }
+ _commandQueue.clear();
+}
+
+void EventManager::commandTakeStairs(bool stairsGoDown) {
+ _vm->_moveSens->getMoveResult(Thing::_party, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, kM1_MapXNotOnASquare, 0);
+ _vm->_newPartyMapIndex = _vm->_dungeonMan->getLocationAfterLevelChange(_vm->_dungeonMan->_partyMapIndex, stairsGoDown ? -1 : 1, &_vm->_dungeonMan->_partyMapX, &_vm->_dungeonMan->_partyMapY);
+ _vm->_dungeonMan->setCurrentMap(_vm->_newPartyMapIndex);
+ _vm->_championMan->setPartyDirection(_vm->_dungeonMan->getStairsExitDirection(_vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY));
+ _vm->_dungeonMan->setCurrentMap(_vm->_dungeonMan->_partyMapIndex);
+}
+
+void EventManager::commandProcessTypes12to27_clickInChampionStatusBox(uint16 champIndex, int16 posX, int16 posY) {
+ if (_vm->indexToOrdinal(champIndex) == _vm->_inventoryMan->_inventoryChampionOrdinal) {
+ commandSetLeader((ChampionIndex)champIndex);
+ } else {
+ uint16 commandType = getCommandTypeFromMouseInput(_mouseInputChampionNamesHands, Common::Point(posX, posY), k1_LeftMouseButton);
+ if ((commandType >= k16_CommandSetLeaderChampion_0) && (commandType <= k19_CommandSetLeaderChampion_3))
+ commandSetLeader((ChampionIndex)(commandType - k16_CommandSetLeaderChampion_0));
+ else if ((commandType >= k20_CommandClickOnSlotBoxChampion_0_StatusBoxReadyHand) && (commandType <= k27_CommandClickOnSlotBoxChampion_3_StatusBoxActionHand))
+ _vm->_championMan->clickOnSlotBox(commandType - k20_CommandClickOnSlotBoxChampion_0_StatusBoxReadyHand);
+ }
+}
+
+void EventManager::mouseProcessCommands125To128_clickOnChampionIcon(uint16 champIconIndex) {
+ static Box championIconShadowBox = Box(2, 20, 2, 15);
+ static Box championIconBox = Box(0, 18, 0, 13);
+ static byte mousePointerIconShadowBox[16] = {0, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 120, 0, 120, 120, 120};
+
+ _preventBuildPointerScreenArea = true;
+ if (!_useChampionIconOrdinalAsMousePointerBitmap) {
+ if (_vm->_championMan->getIndexInCell(normalizeModulo4(champIconIndex + _vm->_dungeonMan->_partyDir)) == kDMChampionNone) {
+ _preventBuildPointerScreenArea = false;
+ return;
+ }
+ _mousePointerBitmapUpdated = true;
+ _useChampionIconOrdinalAsMousePointerBitmap = true;
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ byte *tmpBitmap = _mousePointerTempBuffer;
+ memset(tmpBitmap, 0, 32 * 18);
+ Box *curChampionIconBox = &_vm->_championMan->_boxChampionIcons[champIconIndex];
+
+ _vm->_displayMan->blitToBitmap(_vm->_displayMan->_bitmapScreen, tmpBitmap, championIconShadowBox, curChampionIconBox->_x1, curChampionIconBox->_y1, k160_byteWidthScreen, k16_byteWidth, k0_ColorBlack, 200, 18);
+ _vm->_displayMan->blitToBitmapShrinkWithPalChange(tmpBitmap, _mousePointerOriginalColorsChampionIcon, 32, 18, 32, 18, mousePointerIconShadowBox);
+ _vm->_displayMan->blitToBitmap(_vm->_displayMan->_bitmapScreen, _mousePointerOriginalColorsChampionIcon, championIconBox, curChampionIconBox->_x1, curChampionIconBox->_y1, k160_byteWidthScreen, k16_byteWidth, k0_ColorBlack, 200, 18);
+ _vm->_displayMan->fillScreenBox(*curChampionIconBox, k0_ColorBlack);
+ _useChampionIconOrdinalAsMousePointerBitmap = _vm->indexToOrdinal(champIconIndex);
+ } else {
+ _mousePointerBitmapUpdated = true;
+ uint16 championIconIndex = _vm->ordinalToIndex(_useChampionIconOrdinalAsMousePointerBitmap);
+ _useChampionIconOrdinalAsMousePointerBitmap = _vm->indexToOrdinal(kDMChampionNone);
+ int16 championCellIndex = _vm->_championMan->getIndexInCell(normalizeModulo4(championIconIndex + _vm->_dungeonMan->_partyDir));
+ if (championIconIndex == champIconIndex) {
+ setFlag(_vm->_championMan->_champions[championCellIndex]._attributes, kDMAttributeIcon);
+ _vm->_championMan->drawChampionState((ChampionIndex)championCellIndex);
+ } else {
+ int16 championIndex = _vm->_championMan->getIndexInCell(normalizeModulo4(champIconIndex + _vm->_dungeonMan->_partyDir));
+ if (championIndex >= 0) {
+ _vm->_championMan->_champions[championIndex]._cell = (ViewCell)normalizeModulo4(championIconIndex + _vm->_dungeonMan->_partyDir);
+ setFlag(_vm->_championMan->_champions[championIndex]._attributes, kDMAttributeIcon);
+ _vm->_championMan->drawChampionState((ChampionIndex)championIndex);
+ } else
+ _vm->_displayMan->fillScreenBox(_vm->_championMan->_boxChampionIcons[championIconIndex], k0_ColorBlack);
+
+ _vm->_championMan->_champions[championCellIndex]._cell = (ViewCell)normalizeModulo4(champIconIndex + _vm->_dungeonMan->_partyDir);
+ setFlag(_vm->_championMan->_champions[championCellIndex]._attributes, kDMAttributeIcon);
+ _vm->_championMan->drawChampionState((ChampionIndex)championCellIndex);
+ }
+ }
+ _preventBuildPointerScreenArea = false;
+ buildpointerScreenArea(_mousePos.x, _mousePos.y);
+}
+
+void EventManager::commandProcessType100_clickInSpellArea(uint16 posX, uint16 posY) {
+ ChampionIndex championIndex = kDMChampionNone;
+ if (posY <= 48) {
+ switch (_vm->_championMan->_magicCasterChampionIndex) {
+ case 0:
+ if ((posX >= 280) && (posX <= 291))
+ championIndex = kDMChampionSecond;
+ else if ((posX >= 294) && (posX <= 305))
+ championIndex = kDMChampionThird;
+ else if (posX >= 308)
+ championIndex = kDMChampionFourth;
+
+ break;
+ case 1:
+ if ((posX >= 233) && (posX <= 244))
+ championIndex = kDMChampionFirst;
+ else if ((posX >= 294) && (posX <= 305))
+ championIndex = kDMChampionThird;
+ else if (posX >= 308)
+ championIndex = kDMChampionFourth;
+
+ break;
+ case 2:
+ if ((posX >= 233) && (posX <= 244))
+ championIndex = kDMChampionFirst;
+ else if ((posX >= 247) && (posX <= 258))
+ championIndex = kDMChampionSecond;
+ else if (posX >= 308)
+ championIndex = kDMChampionFourth;
+
+ break;
+ case 3:
+ if ((posX >= 247) && (posX <= 258))
+ championIndex = kDMChampionSecond;
+ else if ((posX >= 261) && (posX <= 272))
+ championIndex = kDMChampionThird;
+ else if (posX <= 244)
+ championIndex = kDMChampionFirst;
+ break;
+ default:
+ break;
+ }
+
+ if ((championIndex != kDMChampionNone) && (championIndex < _vm->_championMan->_partyChampionCount))
+ _vm->_menuMan->setMagicCasterAndDrawSpellArea(championIndex);
+
+ return;
+ }
+
+ CommandType newCommand = getCommandTypeFromMouseInput(_mouseInputSpellArea, Common::Point(posX, posY), k1_LeftMouseButton);
+ if (newCommand != k0_CommandNone)
+ commandProcessTypes101To108_clickInSpellSymbolsArea(newCommand);
+}
+
+void EventManager::commandProcessTypes101To108_clickInSpellSymbolsArea(CommandType cmdType) {
+ static Box spellSymbolsAndDelete[7] = {
+ /* { X1, X2, Y1, Y2 } */
+ Box(235, 247, 51, 61), /* Symbol 1 */
+ Box(249, 261, 51, 61), /* Symbol 2 */
+ Box(263, 275, 51, 61), /* Symbol 3 */
+ Box(277, 289, 51, 61), /* Symbol 4 */
+ Box(291, 303, 51, 61), /* Symbol 5 */
+ Box(305, 317, 51, 61), /* Symbol 6 */
+ Box(305, 318, 63, 73) /* Delete */
+ };
+
+ if (cmdType == k108_CommandClickInSpeallAreaCastSpell) {
+ if (_vm->_championMan->_champions[_vm->_championMan->_magicCasterChampionIndex]._symbols[0] == '\0')
+ return;
+
+ commandHighlightBoxEnable(234, 303, 63, 73);
+ _vm->_stopWaitingForPlayerInput = _vm->_menuMan->getClickOnSpellCastResult();
+ return;
+ }
+
+ uint16 symbolIndex = cmdType - k101_CommandClickInSpellAreaSymbol_1;
+ Box *highlightBox = &spellSymbolsAndDelete[symbolIndex];
+ commandHighlightBoxEnable(highlightBox->_x1, highlightBox->_x2, highlightBox->_y1, highlightBox->_y2);
+ _vm->delay(1);
+ highlightBoxDisable();
+
+ if (symbolIndex < 6)
+ _vm->_menuMan->addChampionSymbol(symbolIndex);
+ else
+ _vm->_menuMan->deleteChampionSymbol();
+}
+
+void EventManager::commandProcessType111To115_ClickInActionArea(int16 posX, int16 posY) {
+ if (_vm->_championMan->_actingChampionOrdinal) {
+ uint16 mouseCommand = getCommandTypeFromMouseInput(_mouseInputActionAreaNames, Common::Point(posX, posY), k1_LeftMouseButton);
+ if (mouseCommand != k0_CommandNone) {
+ if (mouseCommand == k112_CommandClickInActionAreaPass) {
+ commandHighlightBoxEnable(285, 319, 77, 83);
+ _vm->_menuMan->didClickTriggerAction(-1);
+ } else if ((mouseCommand - k112_CommandClickInActionAreaPass) <= _vm->_menuMan->_actionCount) {
+ if (mouseCommand == k113_CommandClickInActionAreaAction_0)
+ commandHighlightBoxEnable(234, 318, 86, 96);
+ else if (mouseCommand == k114_CommandClickInActionAreaAction_1)
+ commandHighlightBoxEnable(234, 318, 98, 108);
+ else
+ commandHighlightBoxEnable(234, 318, 110, 120);
+
+ _vm->_stopWaitingForPlayerInput = _vm->_menuMan->didClickTriggerAction(mouseCommand - k113_CommandClickInActionAreaAction_0);
+ }
+ }
+ } else if (_vm->_menuMan->_actionAreaContainsIcons) {
+ uint16 mouseCommand = getCommandTypeFromMouseInput(_mouseInputActionAreaIcons, Common::Point(posX, posY), k1_LeftMouseButton);
+ if (mouseCommand != k0_CommandNone) {
+ mouseCommand -= k116_CommandClickInActionAreaChampion_0_Action;
+ if (mouseCommand < _vm->_championMan->_partyChampionCount)
+ _vm->_menuMan->processCommands116To119_setActingChampion(mouseCommand);
+ }
+ }
+}
+
+void EventManager::resetPressingEyeOrMouth() {
+ if (_vm->_pressingEye) {
+ _ignoreMouseMovements = false;
+ _vm->_stopPressingEye = true;
+ }
+ if (_vm->_pressingMouth) {
+ _ignoreMouseMovements = false;
+ _vm->_stopPressingMouth = true;
+ }
+}
+
+void EventManager::waitForMouseOrKeyActivity() {
+ discardAllInput();
+ Common::Event event;
+ while (true) {
+ if (g_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_QUIT:
+ _vm->_engineShouldQuit = true;
+ case Common::EVENT_KEYDOWN: // Intentional fall through
+ case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_RBUTTONDOWN:
+ return;
+ default:
+ break;
+ }
+ }
+ _vm->delay(1);
+ _vm->_displayMan->updateScreen();
+ }
+}
+
+void EventManager::commandHighlightBoxEnable(int16 x1, int16 x2, int16 y1, int16 y2) {
+ _highlightScreenBox = Box(x1, x2, y1, y2);
+ highlightScreenBox(x1, x2, y1, y2);
+ _highlightBoxEnabled = true;
+}
+
+void EventManager::highlightBoxDisable() {
+ if (_highlightBoxEnabled == true) {
+ highlightScreenBox(_highlightScreenBox._x1, _highlightScreenBox._x2, _highlightScreenBox._y1, _highlightScreenBox._y2);
+ _highlightBoxEnabled = false;
+ }
+}
+
+} // end of namespace DM
diff --git a/engines/dm/eventman.h b/engines/dm/eventman.h
new file mode 100644
index 0000000000..34984f1dff
--- /dev/null
+++ b/engines/dm/eventman.h
@@ -0,0 +1,326 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_EVENTMAN_H
+#define DM_EVENTMAN_H
+
+#include "common/events.h"
+#include "common/queue.h"
+#include "common/array.h"
+
+#include "dm/dm.h"
+#include "dm/gfx.h"
+#include "dm/champion.h"
+
+namespace DM {
+
+enum MouseButton {
+ k0_NoneMouseButton = 0, // present only because of typesafety
+ k1_LeftMouseButton = 1,
+ k2_RightMouseButton = 2
+};
+
+enum CommandType {
+ k0_CommandNone = 0, // @ C000_COMMAND_NONE
+ k1_CommandTurnLeft = 1, // @ C001_COMMAND_TURN_LEFT
+ k2_CommandTurnRight = 2, // @ C002_COMMAND_TURN_RIGHT
+ k3_CommandMoveForward = 3, // @ C003_COMMAND_MOVE_FORWARD
+ k4_CommandMoveRight = 4, // @ C004_COMMAND_MOVE_RIGHT
+ k5_CommandMoveBackward = 5, // @ C005_COMMAND_MOVE_BACKWARD
+ k6_CommandMoveLeft = 6, // @ C006_COMMAND_MOVE_LEFT
+ k7_CommandToggleInventoryChampion_0 = 7, // @ C007_COMMAND_TOGGLE_INVENTORY_CHAMPION_0
+ k8_CommandToggleInventoryChampion_1 = 8, // @ C008_COMMAND_TOGGLE_INVENTORY_CHAMPION_1
+ k9_CommandToggleInventoryChampion_2 = 9, // @ C009_COMMAND_TOGGLE_INVENTORY_CHAMPION_2
+ k10_CommandToggleInventoryChampion_3 = 10, // @ C010_COMMAND_TOGGLE_INVENTORY_CHAMPION_3
+ k11_CommandCloseInventory = 11, // @ C011_COMMAND_CLOSE_INVENTORY
+ k12_CommandClickInChampion_0_StatusBox = 12, // @ C012_COMMAND_CLICK_IN_CHAMPION_0_STATUS_BOX
+ k13_CommandClickInChampion_1_StatusBox = 13, // @ C013_COMMAND_CLICK_IN_CHAMPION_1_STATUS_BOX
+ k14_CommandClickInChampion_2_StatusBox = 14, // @ C014_COMMAND_CLICK_IN_CHAMPION_2_STATUS_BOX
+ k15_CommandClickInChampion_3_StatusBox = 15, // @ C015_COMMAND_CLICK_IN_CHAMPION_3_STATUS_BOX
+ k16_CommandSetLeaderChampion_0 = 16, // @ C016_COMMAND_SET_LEADER_CHAMPION_0
+ k17_CommandSetLeaderChampion_1 = 17, // @ C017_COMMAND_SET_LEADER_CHAMPION_1
+ k18_CommandSetLeaderChampion_2 = 18, // @ C018_COMMAND_SET_LEADER_CHAMPION_2
+ k19_CommandSetLeaderChampion_3 = 19, // @ C019_COMMAND_SET_LEADER_CHAMPION_3
+ k20_CommandClickOnSlotBoxChampion_0_StatusBoxReadyHand = 20, // @ C020_COMMAND_CLICK_ON_SLOT_BOX_00_CHAMPION_0_STATUS_BOX_READY_HAND
+ k21_CommandClickOnSlotBoxChampion_0_StatusBoxActionHand = 21, // @ C021_COMMAND_CLICK_ON_SLOT_BOX_01_CHAMPION_0_STATUS_BOX_ACTION_HAND
+ k22_CommandClickOnSlotBoxChampion_1_StatusBoxReadyHand = 22, // @ C022_COMMAND_CLICK_ON_SLOT_BOX_02_CHAMPION_1_STATUS_BOX_READY_HAND
+ k23_CommandClickOnSlotBoxChampion_1_StatusBoxActionHand = 23, // @ C023_COMMAND_CLICK_ON_SLOT_BOX_03_CHAMPION_1_STATUS_BOX_ACTION_HAND
+ k24_CommandClickOnSlotBoxChampion_2_StatusBoxReadyHand = 24, // @ C024_COMMAND_CLICK_ON_SLOT_BOX_04_CHAMPION_2_STATUS_BOX_READY_HAND
+ k25_CommandClickOnSlotBoxChampion_2_StatusBoxActionHand = 25, // @ C025_COMMAND_CLICK_ON_SLOT_BOX_05_CHAMPION_2_STATUS_BOX_ACTION_HAND
+ k26_CommandClickOnSlotBoxChampion_3_StatusBoxReadyHand = 26, // @ C026_COMMAND_CLICK_ON_SLOT_BOX_06_CHAMPION_3_STATUS_BOX_READY_HAND
+ k27_CommandClickOnSlotBoxChampion_3_StatusBoxActionHand = 27, // @ C027_COMMAND_CLICK_ON_SLOT_BOX_07_CHAMPION_3_STATUS_BOX_ACTION_HAND
+ k28_CommandClickOnSlotBoxInventoryReadyHand = 28, // @ C028_COMMAND_CLICK_ON_SLOT_BOX_08_INVENTORY_READY_HAND
+ k29_CommandClickOnSlotBoxInventoryActionHand = 29, // @ C029_COMMAND_CLICK_ON_SLOT_BOX_09_INVENTORY_ACTION_HAND
+ k30_CommandClickOnSlotBoxInventoryHead = 30, // @ C030_COMMAND_CLICK_ON_SLOT_BOX_10_INVENTORY_HEAD
+ k31_CommandClickOnSlotBoxInventoryTorso = 31, // @ C031_COMMAND_CLICK_ON_SLOT_BOX_11_INVENTORY_TORSO
+ k32_CommandClickOnSlotBoxInventoryLegs = 32, // @ C032_COMMAND_CLICK_ON_SLOT_BOX_12_INVENTORY_LEGS
+ k33_CommandClickOnSlotBoxInventoryFeet = 33, // @ C033_COMMAND_CLICK_ON_SLOT_BOX_13_INVENTORY_FEET
+ k34_CommandClickOnSlotBoxInventoryPouch_2 = 34, // @ C034_COMMAND_CLICK_ON_SLOT_BOX_14_INVENTORY_POUCH_2
+ k35_CommandClickOnSlotBoxInventoryQuiverLine_2_1 = 35, // @ C035_COMMAND_CLICK_ON_SLOT_BOX_15_INVENTORY_QUIVER_LINE2_1
+ k36_CommandClickOnSlotBoxInventoryQuiverLine_1_2 = 36, // @ C036_COMMAND_CLICK_ON_SLOT_BOX_16_INVENTORY_QUIVER_LINE1_2
+ k37_CommandClickOnSlotBoxInventoryQuiverLine_2_2 = 37, // @ C037_COMMAND_CLICK_ON_SLOT_BOX_17_INVENTORY_QUIVER_LINE2_2
+ k38_CommandClickOnSlotBoxInventoryNeck = 38, // @ C038_COMMAND_CLICK_ON_SLOT_BOX_18_INVENTORY_NECK
+ k39_CommandClickOnSlotBoxInventoryPouch_1 = 39, // @ C039_COMMAND_CLICK_ON_SLOT_BOX_19_INVENTORY_POUCH_1
+ k40_CommandClickOnSlotBoxInventoryQuiverLine_1_1 = 40, // @ C040_COMMAND_CLICK_ON_SLOT_BOX_20_INVENTORY_QUIVER_LINE1_1
+ k41_CommandClickOnSlotBoxInventoryBackpackLine_1_1 = 41, // @ C041_COMMAND_CLICK_ON_SLOT_BOX_21_INVENTORY_BACKPACK_LINE1_1
+ k42_CommandClickOnSlotBoxInventoryBackpackLine_2_2 = 42, // @ C042_COMMAND_CLICK_ON_SLOT_BOX_22_INVENTORY_BACKPACK_LINE2_2
+ k43_CommandClickOnSlotBoxInventoryBackpackLine_2_3 = 43, // @ C043_COMMAND_CLICK_ON_SLOT_BOX_23_INVENTORY_BACKPACK_LINE2_3
+ k44_CommandClickOnSlotBoxInventoryBackpackLine_2_4 = 44, // @ C044_COMMAND_CLICK_ON_SLOT_BOX_24_INVENTORY_BACKPACK_LINE2_4
+ k45_CommandClickOnSlotBoxInventoryBackpackLine_2_5 = 45, // @ C045_COMMAND_CLICK_ON_SLOT_BOX_25_INVENTORY_BACKPACK_LINE2_5
+ k46_CommandClickOnSlotBoxInventoryBackpackLine_2_6 = 46, // @ C046_COMMAND_CLICK_ON_SLOT_BOX_26_INVENTORY_BACKPACK_LINE2_6
+ k47_CommandClickOnSlotBoxInventoryBackpackLine_2_7 = 47, // @ C047_COMMAND_CLICK_ON_SLOT_BOX_27_INVENTORY_BACKPACK_LINE2_7
+ k48_CommandClickOnSlotBoxInventoryBackpackLine_2_8 = 48, // @ C048_COMMAND_CLICK_ON_SLOT_BOX_28_INVENTORY_BACKPACK_LINE2_8
+ k49_CommandClickOnSlotBoxInventoryBackpackLine_2_9 = 49, // @ C049_COMMAND_CLICK_ON_SLOT_BOX_29_INVENTORY_BACKPACK_LINE2_9
+ k50_CommandClickOnSlotBoxInventoryBackpackLine_1_2 = 50, // @ C050_COMMAND_CLICK_ON_SLOT_BOX_30_INVENTORY_BACKPACK_LINE1_2
+ k51_CommandClickOnSlotBoxInventoryBackpackLine_1_3 = 51, // @ C051_COMMAND_CLICK_ON_SLOT_BOX_31_INVENTORY_BACKPACK_LINE1_3
+ k52_CommandClickOnSlotBoxInventoryBackpackLine_1_4 = 52, // @ C052_COMMAND_CLICK_ON_SLOT_BOX_32_INVENTORY_BACKPACK_LINE1_4
+ k53_CommandClickOnSlotBoxInventoryBackpackLine_1_5 = 53, // @ C053_COMMAND_CLICK_ON_SLOT_BOX_33_INVENTORY_BACKPACK_LINE1_5
+ k54_CommandClickOnSlotBoxInventoryBackpackLine_1_6 = 54, // @ C054_COMMAND_CLICK_ON_SLOT_BOX_34_INVENTORY_BACKPACK_LINE1_6
+ k55_CommandClickOnSlotBoxInventoryBackpackLine_1_7 = 55, // @ C055_COMMAND_CLICK_ON_SLOT_BOX_35_INVENTORY_BACKPACK_LINE1_7
+ k56_CommandClickOnSlotBoxInventoryBackpackLine_1_8 = 56, // @ C056_COMMAND_CLICK_ON_SLOT_BOX_36_INVENTORY_BACKPACK_LINE1_8
+ k57_CommandClickOnSlotBoxInventoryBackpackLine_1_9 = 57, // @ C057_COMMAND_CLICK_ON_SLOT_BOX_37_INVENTORY_BACKPACK_LINE1_9
+ k58_CommandClickOnSlotBoxChest_1 = 58, // @ C058_COMMAND_CLICK_ON_SLOT_BOX_38_CHEST_1
+ k59_CommandClickOnSlotBoxChest_2 = 59, // @ C059_COMMAND_CLICK_ON_SLOT_BOX_39_CHEST_2
+ k60_CommandClickOnSlotBoxChest_3 = 60, // @ C060_COMMAND_CLICK_ON_SLOT_BOX_40_CHEST_3
+ k61_CommandClickOnSlotBoxChest_4 = 61, // @ C061_COMMAND_CLICK_ON_SLOT_BOX_41_CHEST_4
+ k62_CommandClickOnSlotBoxChest_5 = 62, // @ C062_COMMAND_CLICK_ON_SLOT_BOX_42_CHEST_5
+ k63_CommandClickOnSlotBoxChest_6 = 63, // @ C063_COMMAND_CLICK_ON_SLOT_BOX_43_CHEST_6
+ k64_CommandClickOnSlotBoxChest_7 = 64, // @ C064_COMMAND_CLICK_ON_SLOT_BOX_44_CHEST_7
+ k65_CommandClickOnSlotBoxChest_8 = 65, // @ C065_COMMAND_CLICK_ON_SLOT_BOX_45_CHEST_8
+ k70_CommandClickOnMouth = 70, // @ C070_COMMAND_CLICK_ON_MOUTH
+ k71_CommandClickOnEye = 71, // @ C071_COMMAND_CLICK_ON_EYE
+ k80_CommandClickInDungeonView = 80, // @ C080_COMMAND_CLICK_IN_DUNGEON_VIEW
+ k81_CommandClickInPanel = 81, // @ C081_COMMAND_CLICK_IN_PANEL
+ k83_CommandToggleInventoryLeader = 83, // @ C083_COMMAND_TOGGLE_INVENTORY_LEADER
+ k100_CommandClickInSpellArea = 100, // @ C100_COMMAND_CLICK_IN_SPELL_AREA
+ k101_CommandClickInSpellAreaSymbol_1 = 101, // @ C101_COMMAND_CLICK_IN_SPELL_AREA_SYMBOL_1
+ k102_CommandClickInSpellAreaSymbol_2 = 102, // @ C102_COMMAND_CLICK_IN_SPELL_AREA_SYMBOL_2
+ k103_CommandClickInSpellAreaSymbol_3 = 103, // @ C103_COMMAND_CLICK_IN_SPELL_AREA_SYMBOL_3
+ k104_CommandClickInSpellAreaSymbol_4 = 104, // @ C104_COMMAND_CLICK_IN_SPELL_AREA_SYMBOL_4
+ k105_CommandClickInSpellAreaSymbol_5 = 105, // @ C105_COMMAND_CLICK_IN_SPELL_AREA_SYMBOL_5
+ k106_CommandClickInSpellAreaSymbol_6 = 106, // @ C106_COMMAND_CLICK_IN_SPELL_AREA_SYMBOL_6
+ k107_CommandClickInSpellAreaRecantSymbol = 107, // @ C107_COMMAND_CLICK_IN_SPELL_AREA_RECANT_SYMBOL
+ k108_CommandClickInSpeallAreaCastSpell = 108, // @ C108_COMMAND_CLICK_IN_SPELL_AREA_CAST_SPELL
+ k111_CommandClickInActionArea = 111, // @ C111_COMMAND_CLICK_IN_ACTION_AREA
+ k112_CommandClickInActionAreaPass = 112, // @ C112_COMMAND_CLICK_IN_ACTION_AREA_PASS
+ k113_CommandClickInActionAreaAction_0 = 113, // @ C113_COMMAND_CLICK_IN_ACTION_AREA_ACTION_0
+ k114_CommandClickInActionAreaAction_1 = 114, // @ C114_COMMAND_CLICK_IN_ACTION_AREA_ACTION_1
+ k115_CommandClickInActionAreaAction_2 = 115, // @ C115_COMMAND_CLICK_IN_ACTION_AREA_ACTION_2
+ k116_CommandClickInActionAreaChampion_0_Action = 116, // @ C116_COMMAND_CLICK_IN_ACTION_AREA_CHAMPION_0_ACTION
+ k117_CommandClickInActionAreaChampion_1_Action = 117, // @ C117_COMMAND_CLICK_IN_ACTION_AREA_CHAMPION_1_ACTION
+ k118_CommandClickInActionAreaChampion_2_Action = 118, // @ C118_COMMAND_CLICK_IN_ACTION_AREA_CHAMPION_2_ACTION
+ k119_CommandClickInActionAreaChampion_3_Action = 119, // @ C119_COMMAND_CLICK_IN_ACTION_AREA_CHAMPION_3_ACTION
+ k125_CommandClickOnChamptionIcon_Top_Left = 125, // @ C125_COMMAND_CLICK_ON_CHAMPION_ICON_TOP_LEFT
+ k126_CommandClickOnChamptionIcon_Top_Right = 126, // @ C126_COMMAND_CLICK_ON_CHAMPION_ICON_TOP_RIGHT
+ k127_CommandClickOnChamptionIcon_Lower_Right = 127, // @ C127_COMMAND_CLICK_ON_CHAMPION_ICON_LOWER_RIGHT
+ k128_CommandClickOnChamptionIcon_Lower_Left = 128, // @ C128_COMMAND_CLICK_ON_CHAMPION_ICON_LOWER_LEFT
+ k140_CommandSaveGame = 140, // @ C140_COMMAND_SAVE_GAME
+ k145_CommandSleep = 145, // @ C145_COMMAND_SLEEP
+ k146_CommandWakeUp = 146, // @ C146_COMMAND_WAKE_UP
+ k147_CommandFreezeGame = 147, // @ C147_COMMAND_FREEZE_GAME
+ k148_CommandUnfreezeGame = 148, // @ C148_COMMAND_UNFREEZE_GAME
+ k160_CommandClickInPanelResurrect = 160, // @ C160_COMMAND_CLICK_IN_PANEL_RESURRECT
+ k161_CommandClickInPanelReincarnate = 161, // @ C161_COMMAND_CLICK_IN_PANEL_REINCARNATE
+ k162_CommandClickInPanelCancel = 162, // @ C162_COMMAND_CLICK_IN_PANEL_CANCEL
+ k200_CommandEntranceEnterDungeon = 200, // @ C200_COMMAND_ENTRANCE_ENTER_DUNGEON
+ k201_CommandEntranceResume = 201, // @ C201_COMMAND_ENTRANCE_RESUME /* Versions 1.x and 2.x command */
+ k202_CommandEntranceDrawCredits = 202, // @ C202_COMMAND_ENTRANCE_DRAW_CREDITS /* Versions 1.x and 2.x command */
+ k210_CommandClickOnDialogChoice_1 = 210, // @ C210_COMMAND_CLICK_ON_DIALOG_CHOICE_1
+ k211_CommandClickOnDialogChoice_2 = 211, // @ C211_COMMAND_CLICK_ON_DIALOG_CHOICE_2
+ k212_CommandClickOnDialogChoice_3 = 212, // @ C212_COMMAND_CLICK_ON_DIALOG_CHOICE_3
+ k213_CommandClickOnDialogChoice_4 = 213, // @ C213_COMMAND_CLICK_ON_DIALOG_CHOICE_4
+ k215_CommandRestartGame = 215 // @ C215_COMMAND_RESTART_GAME
+}; // @ NONE
+
+class Command {
+public:
+ Common::Point _pos;
+ CommandType _type;
+
+ Command(Common::Point position, CommandType commandType) : _pos(position), _type(commandType) {}
+}; // @ COMMAND
+
+
+class MouseInput {
+public:
+ CommandType _commandTypeToIssue;
+ Box _hitbox;
+ MouseButton _button;
+
+ MouseInput(CommandType type, uint16 x1, uint16 x2, uint16 y1, uint16 y2, MouseButton mouseButton)
+ : _commandTypeToIssue(type), _hitbox(x1, x2 + 1, y1, y2 + 1), _button(mouseButton) {}
+ MouseInput()
+ : _commandTypeToIssue(k0_CommandNone), _hitbox(0, 1, 0, 1), _button(k0_NoneMouseButton) {}
+}; // @ MOUSE_INPUT
+
+class KeyboardInput {
+public:
+ CommandType _commandToIssue;
+ Common::KeyCode _key;
+ byte _modifiers;
+
+ KeyboardInput(CommandType command, Common::KeyCode keycode, byte modifierFlags) : _commandToIssue(command), _key(keycode), _modifiers(modifierFlags) {}
+ KeyboardInput() : _commandToIssue(k0_CommandNone), _key(Common::KEYCODE_ESCAPE), _modifiers(0) {}
+}; // @ KEYBOARD_INPUT
+
+class DMEngine;
+
+#define k0_pointerArrow 0 // @ C0_POINTER_ARROW
+#define k1_pointerHand 1 // @ C1_POINTER_HAND
+
+#define k0_pointerTypeArrow 0 // @ C0_POINTER_TYPE_ARROW
+#define k1_pointerTypeObjectIcon 1 // @ C1_POINTER_TYPE_OBJECT_ICON
+#define k2_pointerTypeChampionIcon 2 // @ C2_POINTER_TYPE_CHAMPION_ICON
+#define k3_pointerTypeHand 3 // @ C3_POINTER_TYPE_HAND
+#define k4_pointerTypeAutoselect 4 // @ C4_POINTER_TYPE_AUTOSELECT
+
+class EventManager {
+ DMEngine *_vm;
+
+ Common::Point _mousePos;
+ uint16 _dummyMapIndex;
+
+ bool _pendingClickPresent; // G0436_B_PendingClickPresent
+ Common::Point _pendingClickPos; // @ G0437_i_PendingClickX, G0438_i_PendingClickY
+ MouseButton _pendingClickButton; // @ G0439_i_PendingClickButtonsStatus
+ bool _useObjectAsMousePointerBitmap; // @ G0600_B_UseObjectAsMousePointerBitmap
+ bool _useHandAsMousePointerBitmap; // @ G0601_B_UseHandAsMousePointerBitmap
+ bool _preventBuildPointerScreenArea; // @ K0100_B_PreventBuildPointerScreenArea
+ byte *_mousePointerOriginalColorsObject; // @ G0615_puc_Bitmap_MousePointerOriginalColorsObject
+ byte *_mousePointerOriginalColorsChampionIcon; // @ G0613_puc_Bitmap_MousePointerOriginalColorsChampionIcon
+ byte *_mousePointerTempBuffer; // @ K0190_puc_Bitmap_MousePointerTemporaryBuffer
+ int16 _mousePointerType; // @ K0104_i_MousePointerType
+ int16 _previousMousePointerType; // @ K0105_i_PreviousMousePointerType
+ uint16 _mouseButtonStatus;// @ G0588_i_MouseButtonsStatus
+
+// this doesn't seem to be used anywhere at all
+ bool _isCommandQueueLocked; // @ G0435_B_CommandQueueLocked
+ Common::Queue<Command> _commandQueue;
+
+ void commandTurnParty(CommandType cmdType); // @ F0365_COMMAND_ProcessTypes1To2_TurnParty
+ void commandMoveParty(CommandType cmdType); // @ F0366_COMMAND_ProcessTypes3To6_MoveParty
+ bool isLeaderHandObjThrown(int16 posX, int16 posY); // @ F0375_COMMAND_ProcessType80_ClickInDungeonView_IsLeaderHandObjectThrown
+ void setMousePointerFromSpriteData(byte *mouseSprite);
+
+ Box _highlightScreenBox; // @ G0336_i_HighlightBoxX1
+public:
+ explicit EventManager(DMEngine *vm);
+ ~EventManager();
+
+ MouseInput *_primaryMouseInput;// @ G0441_ps_PrimaryMouseInput
+ MouseInput *_secondaryMouseInput;// @ G0442_ps_SecondaryMouseInput
+ bool _mousePointerBitmapUpdated; // @ G0598_B_MousePointerBitmapUpdated
+ bool _refreshMousePointerInMainLoop; // @ G0326_B_RefreshMousePointerInMainLoop
+ bool _highlightBoxEnabled; // @ G0341_B_HighlightBoxEnabled
+ uint16 _useChampionIconOrdinalAsMousePointerBitmap; // @ G0599_ui_UseChampionIconOrdinalAsMousePointerBitmap
+ KeyboardInput *_primaryKeyboardInput; // @ G0443_ps_PrimaryKeyboardInput
+ KeyboardInput *_secondaryKeyboardInput; // @ G0444_ps_SecondaryKeyboardInput
+ bool _ignoreMouseMovements;// @ G0597_B_IgnoreMouseMovements
+ int16 _hideMousePointerRequestCount; // @ G0587_i_HideMousePointerRequestCount
+
+ void initMouse();
+ void setMousePointerToNormal(int16 mousePointer); // @ F0067_MOUSE_SetPointerToNormal
+ void setPointerToObject(byte *bitmap); // @ F0068_MOUSE_SetPointerToObject
+ void mouseDropChampionIcon(); // @ F0071_MOUSE_DropChampionIcon
+ void buildpointerScreenArea(int16 mousePosX, int16 mousePosY); // @ F0073_MOUSE_BuildPointerScreenArea
+ void setMousePointer(); // @ F0069_MOUSE_SetPointer
+ void showMouse(); // @ F0077_MOUSE_HidePointer_CPSE
+ void hideMouse(); // @ F0078_MOUSE_ShowPointer
+ bool isMouseButtonDown(MouseButton button);
+
+ void setMousePos(Common::Point pos);
+ Common::Point getMousePos() { return _mousePos; }
+ /**
+ * Upon encountering an event type for which the grab parameter is not null, the function
+ * will return with the event type, passes the event to the grab desitination and returns without
+ * processing the rest of the events into commands accoring to the current keyboard and mouse input.
+ * If there are no more events, it returns with Common::EVENT_INVALID.
+ */
+ Common::EventType processInput(Common::Event *grabKey = nullptr, Common::Event *grabMouseClick = nullptr);
+ void processPendingClick(); // @ F0360_COMMAND_ProcessPendingClick
+ void processClick(Common::Point mousePos, MouseButton button); // @ F0359_COMMAND_ProcessClick_CPSC
+ CommandType getCommandTypeFromMouseInput(MouseInput *input, Common::Point mousePos, MouseButton button); // @ F0358_COMMAND_GetCommandFromMouseInput_CPSC
+ void processCommandQueue(); // @ F0380_COMMAND_ProcessQueue_CPSC
+
+ void commandSetLeader(ChampionIndex index); // @ F0368_COMMAND_SetLeader
+ void commandProcessType80ClickInDungeonViewTouchFrontWall(); // @ F0372_COMMAND_ProcessType80_ClickInDungeonView_TouchFrontWall
+ void commandProcessType80ClickInDungeonView(int16 posX, int16 posY); // @ F0377_COMMAND_ProcessType80_ClickInDungeonView
+ void commandProcessCommands160To162ClickInResurrectReincarnatePanel(CommandType commandType); // @ F0282_CHAMPION_ProcessCommands160To162_ClickInResurrectReincarnatePanel
+ void commandProcess81ClickInPanel(int16 x, int16 y); // @ F0378_COMMAND_ProcessType81_ClickInPanel
+ void processType80_clickInDungeonView_grabLeaderHandObject(uint16 viewCell); // @ F0373_COMMAND_ProcessType80_ClickInDungeonView_GrabLeaderHandObject
+ void processType80_clickInDungeonViewDropLeaderHandObject(uint16 viewCell); // @ F0374_COMMAND_ProcessType80_ClickInDungeonView_DropLeaderHandObject
+
+ bool hasPendingClick(Common::Point &point, MouseButton button); // @ F0360_COMMAND_ProcessPendingClick
+ void drawSleepScreen(); // @ F0379_COMMAND_DrawSleepScreen
+ void discardAllInput(); // @ F0357_COMMAND_DiscardAllInput
+ void commandTakeStairs(bool stairsGoDown);// @ F0364_COMMAND_TakeStairs
+ void commandProcessTypes12to27_clickInChampionStatusBox(uint16 champIndex, int16 posX,
+ int16 posY); // @ F0367_COMMAND_ProcessTypes12To27_ClickInChampionStatusBox
+ void mouseProcessCommands125To128_clickOnChampionIcon(uint16 champIconIndex); // @ F0070_MOUSE_ProcessCommands125To128_ClickOnChampionIcon
+ void commandProcessType100_clickInSpellArea(uint16 posX, uint16 posY); // @ F0370_COMMAND_ProcessType100_ClickInSpellArea
+ void commandProcessTypes101To108_clickInSpellSymbolsArea(CommandType cmdType); // @ F0369_COMMAND_ProcessTypes101To108_ClickInSpellSymbolsArea_CPSE
+ void commandProcessType111To115_ClickInActionArea(int16 posX, int16 posY); // @ F0371_COMMAND_ProcessType111To115_ClickInActionArea_CPSE
+ void resetPressingEyeOrMouth(); // @ F0544_INPUT_ResetPressingEyeOrMouth
+ void waitForMouseOrKeyActivity(); // @ F0541_INPUT_WaitForMouseOrKeyboardActivity
+ void commandHighlightBoxEnable(int16 x1, int16 x2, int16 y1, int16 y2); // @ F0362_COMMAND_HighlightBoxEnable
+ void highlightBoxDisable(); // @ F0363_COMMAND_HighlightBoxDisable
+ void highlightScreenBox(int16 x1, int16 x2, int16 y1, int16 y2) { warning("STUB METHOD: highlightScreenBox"); } // @ F0006_MAIN_HighlightScreenBox
+
+ KeyboardInput _primaryKeyboardInputInterface[7]; // @ G0458_as_Graphic561_PrimaryKeyboardInput_Interface
+ KeyboardInput _secondaryKeyboardInputMovement[19]; // @ G0459_as_Graphic561_SecondaryKeyboardInput_Movement
+ KeyboardInput _primaryKeyboardInputPartySleeping[3]; // @ G0460_as_Graphic561_PrimaryKeyboardInput_PartySleeping
+ KeyboardInput _primaryKeyboardInputFrozenGame[2]; // @ G0461_as_Graphic561_PrimaryKeyboardInput_FrozenGame
+ MouseInput _primaryMouseInputEntrance[4]; // @ G0445_as_Graphic561_PrimaryMouseInput_Entrance[4]
+ MouseInput _primaryMouseInputRestartGame[2]; // @ G0446_as_Graphic561_PrimaryMouseInput_RestartGame[2]
+ MouseInput _primaryMouseInputInterface[20]; // @ G0447_as_Graphic561_PrimaryMouseInput_Interface[20]
+ MouseInput _secondaryMouseInputMovement[9]; // @ G0448_as_Graphic561_SecondaryMouseInput_Movement[9]
+ MouseInput _secondaryMouseInputChampionInventory[38]; // @ G0449_as_Graphic561_SecondaryMouseInput_ChampionInventory[38]
+ MouseInput _primaryMouseInputPartySleeping[3]; // @ G0450_as_Graphic561_PrimaryMouseInput_PartySleeping[3]
+ MouseInput _primaryMouseInputFrozenGame[3]; // @ G0451_as_Graphic561_PrimaryMouseInput_FrozenGame[3]
+ MouseInput _mouseInputActionAreaNames[5]; // @ G0452_as_Graphic561_MouseInput_ActionAreaNames[5]
+ MouseInput _mouseInputActionAreaIcons[5]; // @ G0453_as_Graphic561_MouseInput_ActionAreaIcons[5]
+ MouseInput _mouseInputSpellArea[9]; // @ G0454_as_Graphic561_MouseInput_SpellArea[9]
+ MouseInput _mouseInputChampionNamesHands[13]; // @ G0455_as_Graphic561_MouseInput_ChampionNamesHands[13]
+ MouseInput _mouseInputPanelChest[9]; // @ G0456_as_Graphic561_MouseInput_PanelChest[9]
+ MouseInput _mouseInputPanelResurrectReincarnateCancel[4]; // @ G0457_as_Graphic561_MouseInput_PanelResurrectReincarnateCancel[4]
+ MouseInput _primaryMouseInputViewportDialog1Choice[2]; // @ G0471_as_Graphic561_PrimaryMouseInput_ViewportDialog1Choice[2]
+ MouseInput _primaryMouseInputScreenDialog1Choice[2]; // @ G0475_as_Graphic561_PrimaryMouseInput_ScreenDialog1Choice[2]
+ MouseInput _primaryMouseInputViewportDialog2Choices[3]; // @ G0472_as_Graphic561_PrimaryMouseInput_ViewportDialog2Choices[3]
+ MouseInput _primaryMouseInputScreenDialog2Choices[3]; // @ G0476_as_Graphic561_PrimaryMouseInput_ScreenDialog2Choices[3]
+ MouseInput _primaryMouseInputViewportDialog3Choices[4]; // @ G0473_as_Graphic561_PrimaryMouseInput_ViewportDialog3Choices[4]
+ MouseInput _primaryMouseInputScreenDialog3Choices[4]; // @ G0477_as_Graphic561_PrimaryMouseInput_ScreenDialog3Choices[4]
+ MouseInput _primaryMouseInputViewportDialog4Choices[5]; // @ G0474_as_Graphic561_PrimaryMouseInput_ViewportDialog4Choices[5]
+ MouseInput _primaryMouseInputScreenDialog4Choices[5]; // @ G0478_as_Graphic561_PrimaryMouseInput_ScreenDialog4Choices[5]
+
+ MouseInput *_primaryMouseInputDialogSets[2][4]; // @ G0480_aaps_PrimaryMouseInput_DialogSets
+
+ void initArrays();
+};
+
+}
+
+#endif
diff --git a/engines/dm/gfx.cpp b/engines/dm/gfx.cpp
new file mode 100644
index 0000000000..3c992a8c65
--- /dev/null
+++ b/engines/dm/gfx.cpp
@@ -0,0 +1,3861 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "engines/util.h"
+#include "common/system.h"
+#include "common/file.h"
+#include "common/endian.h"
+#include "graphics/palette.h"
+
+#include "dm/gfx.h"
+#include "dm/dungeonman.h"
+#include "dm/group.h"
+#include "dm/timeline.h"
+#include "dm/champion.h"
+#include "dm/eventman.h"
+#include "dm/lzw.h"
+#include "dm/text.h"
+
+namespace DM {
+DisplayMan::DisplayMan(DMEngine *dmEngine) : _vm(dmEngine) {
+ _bitmapScreen = nullptr;
+ _bitmaps = nullptr;
+ _grapItemCount = 0;
+ _packedItemPos = nullptr;
+ _bitmapCompressedByteCount = nullptr;
+ _bitmapDecompressedByteCount = nullptr;
+ _packedBitmaps = nullptr;
+ _bitmaps = nullptr;
+ _tmpBitmap = nullptr;
+ _bitmapFloor = nullptr;
+ _bitmapCeiling = nullptr;
+ _currMapAllowedCreatureTypes = nullptr;
+ _derivedBitmapByteCount = nullptr;
+ _derivedBitmaps = nullptr;
+
+ _screenWidth = _screenHeight = 0;
+ _championPortraitOrdinal = 0;
+ _currMapViAltarIndex = 0;
+ _drawFloorAndCeilingRequested = true;
+
+ for (int i = 0; i < 4; i++)
+ _palChangesProjectile[i] = nullptr;
+
+ for (int i = 0; i < k3_AlcoveOrnCount; i++)
+ _currMapAlcoveOrnIndices[i] = 0;
+
+ for (int i = 0; i < k1_FountainOrnCount; i++)
+ _currMapFountainOrnIndices[i] = 0;
+
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 16; j++) {
+ _currMapWallOrnInfo[j][i] = 0;
+ _currMapFloorOrnInfo[j][i] = 0;
+ }
+
+ for (int j = 0; j < 17; j++)
+ _currMapDoorOrnInfo[j][i] = 0;
+ }
+
+ for (int i = 0; i < 16; i++) {
+ _currMapWallOrnIndices[i] = 0;
+ _currMapFloorOrnIndices[i] = 0;
+ }
+
+ for (int i = 0; i < 18; i++)
+ _currMapDoorOrnIndices[i] = 0;
+
+ _inscriptionThing = Thing::_none;
+ _useByteBoxCoordinates = false;
+
+ _bitmapCeiling = nullptr;
+ _bitmapFloor = nullptr;
+ _bitmapWallSetD3L2 = nullptr;
+ _bitmapWallSetD3R2 = nullptr;
+ _bitmapWallSetD3LCR = nullptr;
+ _bitmapWallSetD2LCR = nullptr;
+ _bitmapWallSetD1LCR = nullptr;
+ bitmapWallSetWallD0L = nullptr;
+ _bitmapWallSetWallD0R = nullptr;
+ _bitmapWallSetDoorFrameTopD2LCR = nullptr;
+ _bitmapWallSetDoorFrameTopD1LCR = nullptr;
+ _bitmapWallSetDoorFrameLeftD3L = nullptr;
+ _bitmapWallSetDoorFrameLeftD3C = nullptr;
+ _bitmapWallSetDoorFrameLeftD2C = nullptr;
+ _bitmapWallSetDoorFrameLeftD1C = nullptr;
+ _bitmapWallSetDoorFrameRightD1C = nullptr;
+ _bitmapWallSetDoorFrameFront = nullptr;
+ _bitmapViewport = nullptr;
+
+ _currentWallSet = -1;
+ _currentFloorSet = -1;
+
+ _bitmapWallD3LCRFlipped = nullptr;
+ _bitmapWallD2LCRFlipped = nullptr;
+ _bitmapWallD1LCRFlipped = nullptr;
+ _bitmapWallD0LFlipped = nullptr;
+ _bitmapWallD0RFlipped = nullptr;
+ _bitmapWallD3LCRNative = nullptr;
+ _bitmapWallD2LCRNative = nullptr;
+ _bitmapWallD1LCRNative = nullptr;
+ _bitmapWallD0LNative = nullptr;
+ _bitmapWallD0RNative = nullptr;
+
+ _paletteSwitchingEnabled = false;
+ _dungeonViewPaletteIndex = 0;
+
+ for (uint16 i = 0; i < 16; ++i) {
+ _paletteTopAndBottomScreen[i] = 0;
+ _paletteMiddleScreen[i] = 0;
+ }
+
+ for (uint16 i = 0; i < 32; i++)
+ _blankBuffer[i] = 0;
+
+ _paletteFadeFrom = nullptr;
+ for (uint16 i = 0; i < 16; ++i)
+ _paletteFadeTemporary[i] = 0;
+
+ initConstants();
+}
+
+void DisplayMan::initConstants() {
+ const byte palChangesDoorButtonAndWallOrnD3[16] = {0, 0, 120, 30, 40, 30, 0, 60, 30, 90, 100, 110, 0, 10, 0, 20}; // @ G0198_auc_Graphic558_PaletteChanges_DoorButtonAndWallOrnament_D3
+ const byte palChangesDoorButtonAndWallOrnD2[16] = {0, 120, 10, 30, 40, 30, 60, 70, 50, 90, 100, 110, 0, 20, 140, 130}; // @ G0199_auc_Graphic558_PaletteChanges_DoorButtonAndWallOrnament_D2
+ const FieldAspect fieldAspects188[12] = { // @ G0188_as_Graphic558_FieldAspects
+ /* { NativeBitmapRelativeIndex, BaseStartUnitIndex, Transparent color, Mask, ByteWidth, Height, X, BitPlaneWordCount } */
+ FieldAspect(0, 63, 0x8A, 0xFF, 0, 0, 0, 64), /* D3C */
+ FieldAspect(0, 63, 0x0A, 0x80, 48, 51, 11, 64), /* D3L */
+ FieldAspect(0, 63, 0x0A, 0x00, 48, 51, 0, 64), /* D3R */
+ FieldAspect(0, 60, 0x8A, 0xFF, 0, 0, 0, 64), /* D2C */
+ FieldAspect(0, 63, 0x0A, 0x81, 40, 71, 5, 64), /* D2L */
+ FieldAspect(0, 63, 0x0A, 0x01, 40, 71, 0, 64), /* D2R */
+ FieldAspect(0, 61, 0x8A, 0xFF, 0, 0, 0, 64), /* D1C */
+ FieldAspect(0, 63, 0x0A, 0x82, 32, 111, 0, 64), /* D1L */
+ FieldAspect(0, 63, 0x0A, 0x02, 32, 111, 0, 64), /* D1R */
+ FieldAspect(0, 59, 0x8A, 0xFF, 0, 0, 0, 64), /* D0C */
+ FieldAspect(0, 63, 0x0A, 0x83, 16, 136, 0, 64), /* D0L */
+ FieldAspect(0, 63, 0x0A, 0x03, 16, 136, 0, 64) /* D0R */
+ };
+
+ const ExplosionAspect explosionAspects[k4_ExplosionAspectCount] = { // @ G0211_as_Graphic558_ExplosionAspects
+ // ByteWidth, Height
+ ExplosionAspect(80, 111), // Fire
+ ExplosionAspect(64, 97), // Spell
+ ExplosionAspect(80, 91), // Poison
+ ExplosionAspect(80, 91) // Death
+ };
+
+ const byte palChangeSmoke[16] = {0, 10, 20, 30, 40, 50, 120, 10, 80, 90, 100, 110, 120, 130, 140, 150}; // @ G0212_auc_Graphic558_PaletteChanges_Smoke
+ const byte projectileScales[7] = {
+ 13, /* D4 Back */
+ 16, /* D4 Front */
+ 19, /* D3 Back */
+ 22, /* D3 Front */
+ 25, /* D2 Back */
+ 28, /* D2 Front */
+ 32 /* D1 Back */
+ };
+
+ const Frame frameWalls163[12] = { // @ G0163_as_Graphic558_Frame_Walls
+ /* { X1, X2, Y1, Y2, pixelWidth, Height, X, Y } */
+ Frame(74, 149, 25, 75, 64, 51, 18, 0), /* D3C */
+ Frame(0, 83, 25, 75, 64, 51, 32, 0), /* D3L */
+ Frame(139, 223, 25, 75, 64, 51, 0, 0), /* D3R */
+ Frame(60, 163, 20, 90, 72, 71, 16, 0), /* D2C */
+ Frame(0, 74, 20, 90, 72, 71, 61, 0), /* D2L */
+ Frame(149, 223, 20, 90, 72, 71, 0, 0), /* D2R */
+ Frame(32, 191, 9, 119, 128, 111, 48, 0), /* D1C */
+ Frame(0, 63, 9, 119, 128, 111, 192, 0), /* D1L */
+ Frame(160, 223, 9, 119, 128, 111, 0, 0), /* D1R */
+ Frame(0, 223, 0, 135, 0, 0, 0, 0), /* D0C */
+ Frame(0, 31, 0, 135, 16, 136, 0, 0), /* D0L */
+ Frame(192, 223, 0, 135, 16, 136, 0, 0) /* D0R */
+ };
+
+ const CreatureAspect creatureAspects219[k27_CreatureTypeCount] = { // @ G0219_as_Graphic558_CreatureAspects
+ /* { FirstNativeBitmapRelativeIndex, FirstDerivedBitmapIndex, pixelWidthFront, HeightFront,
+ pixelWidthSide, HeightSide, pixelWidthAttack, HeightAttack, CoordinateSet / TransparentColor,
+ Replacement Color Set Index for color 10 / Replacement Color Set Index for color 9 } */
+ CreatureAspect(0, 0, 56 , 84, 56 , 84, 56 , 84, 0x1D, 0x01), /* Creature #00 Giant Scorpion / Scorpion */
+ CreatureAspect(4, 0, 32 , 66, 0 , 0, 32 , 69, 0x0B, 0x20), /* Creature #01 Swamp Slime / Slime Devil */
+ CreatureAspect(6, 0, 24 , 48, 24 , 48, 0 , 0, 0x0B, 0x00), /* Creature #02 Giggler */
+ CreatureAspect(10, 0, 32 , 61, 0 , 0, 32 , 61, 0x24, 0x31), /* Creature #03 Wizard Eye / Flying Eye */
+ CreatureAspect(12, 0, 32 , 64, 56 , 64, 32 , 64, 0x14, 0x34), /* Creature #04 Pain Rat / Hellhound */
+ CreatureAspect(16, 0, 24 , 49, 40 , 49, 0 , 0, 0x18, 0x34), /* Creature #05 Ruster */
+ CreatureAspect(19, 0, 32 , 60, 0 , 0, 32 , 60, 0x0D, 0x00), /* Creature #06 Screamer */
+ CreatureAspect(21, 0, 32 , 43, 0 , 0, 32 , 64, 0x04, 0x00), /* Creature #07 Rockpile / Rock pile */
+ CreatureAspect(23, 0, 32 , 83, 0 , 0, 32 , 93, 0x04, 0x00), /* Creature #08 Ghost / Rive */
+ CreatureAspect(25, 0, 32 , 101, 32 , 101, 32 , 101, 0x14, 0x00), /* Creature #09 Stone Golem */
+ CreatureAspect(29, 0, 32 , 82, 32 , 82, 32 , 83, 0x04, 0x00), /* Creature #10 Mummy */
+ CreatureAspect(33, 0, 32 , 80, 0 , 0, 32 , 99, 0x14, 0x00), /* Creature #11 Black Flame */
+ CreatureAspect(35, 0, 32 , 80, 32 , 80, 32 , 76, 0x04, 0x00), /* Creature #12 Skeleton */
+ CreatureAspect(39, 0, 32 , 96, 56 , 93, 32 , 90, 0x1D, 0x20), /* Creature #13 Couatl */
+ CreatureAspect(43, 0, 32 , 49, 16 , 49, 32 , 56, 0x04, 0x30), /* Creature #14 Vexirk */
+ CreatureAspect(47, 0, 32 , 59, 56 , 43, 32 , 67, 0x14, 0x78), /* Creature #15 Magenta Worm / Worm */
+ CreatureAspect(51, 0, 32 , 83, 32 , 74, 32 , 74, 0x04, 0x65), /* Creature #16 Trolin / Ant Man */
+ CreatureAspect(55, 0, 24 , 49, 24 , 53, 24 , 53, 0x24, 0x00), /* Creature #17 Giant Wasp / Muncher */
+ CreatureAspect(59, 0, 32 , 89, 32 , 89, 32 , 89, 0x04, 0x00), /* Creature #18 Animated Armour / Deth Knight */
+ CreatureAspect(63, 0, 32 , 84, 32 , 84, 32 , 84, 0x0D, 0xA9), /* Creature #19 Materializer / Zytaz */
+ CreatureAspect(67, 0, 56 , 27, 0 , 0, 56 , 80, 0x04, 0x65), /* Creature #20 Water Elemental */
+ CreatureAspect(69, 0, 56 , 77, 56 , 81, 56 , 77, 0x04, 0xA9), /* Creature #21 Oitu */
+ CreatureAspect(73, 0, 32 , 87, 32 , 89, 32 , 89, 0x04, 0xCB), /* Creature #22 Demon */
+ CreatureAspect(77, 0, 32 , 96, 32 , 94, 32 , 96, 0x04, 0x00), /* Creature #23 Lord Chaos */
+ CreatureAspect(81, 0, 64 , 94, 72 , 94, 64 , 94, 0x04, 0xCB), /* Creature #24 Red Dragon / Dragon */
+ CreatureAspect(85, 0, 32 , 93, 0 , 0, 0 , 0, 0x04, 0xCB), /* Creature #25 Lord Order */
+ CreatureAspect(86, 0, 32 , 93, 0 , 0, 0 , 0, 0x04, 0xCB) /* Creature #26 Grey Lord */
+ };
+ static ObjectAspect objectAspects209[k85_ObjAspectCount] = { // @ G0209_as_Graphic558_ObjectAspects
+ /* FirstNativeBitmapRelativeIndex, FirstDerivedBitmapRelativeIndex, ByteWidth, Height, GraphicInfo, CoordinateSet */
+ ObjectAspect(0, 0, 24, 27, 0x11, 0),
+ ObjectAspect(2, 6, 24, 8, 0x00, 1),
+ ObjectAspect(3, 8, 8, 18, 0x00, 1),
+ ObjectAspect(4, 10, 8, 8, 0x00, 1),
+ ObjectAspect(5, 12, 8, 4, 0x00, 1),
+ ObjectAspect(6, 14, 16, 11, 0x00, 1),
+ ObjectAspect(7, 16, 24, 13, 0x00, 0),
+ ObjectAspect(8, 18, 32, 16, 0x00, 0),
+ ObjectAspect(9, 20, 40, 24, 0x00, 0),
+ ObjectAspect(10, 22, 16, 20, 0x00, 1),
+ ObjectAspect(11, 24, 40, 20, 0x00, 0),
+ ObjectAspect(12, 26, 32, 4, 0x00, 1),
+ ObjectAspect(13, 28, 40, 8, 0x00, 1),
+ ObjectAspect(14, 30, 32, 17, 0x00, 0),
+ ObjectAspect(15, 32, 40, 17, 0x00, 2),
+ ObjectAspect(16, 34, 16, 9, 0x00, 1),
+ ObjectAspect(17, 36, 24, 5, 0x00, 1),
+ ObjectAspect(18, 38, 16, 9, 0x00, 0),
+ ObjectAspect(19, 40, 8, 4, 0x00, 1),
+ ObjectAspect(20, 42, 32, 21, 0x00, 2),
+ ObjectAspect(21, 44, 32, 25, 0x00, 2),
+ ObjectAspect(22, 46, 32, 14, 0x00, 1),
+ ObjectAspect(23, 48, 32, 26, 0x00, 2),
+ ObjectAspect(24, 50, 32, 16, 0x00, 0),
+ ObjectAspect(25, 52, 32, 16, 0x00, 0),
+ ObjectAspect(26, 54, 16, 16, 0x00, 1),
+ ObjectAspect(27, 56, 16, 15, 0x00, 1),
+ ObjectAspect(28, 58, 16, 13, 0x00, 1),
+ ObjectAspect(29, 60, 16, 10, 0x00, 1),
+ ObjectAspect(30, 62, 40, 24, 0x00, 0),
+ ObjectAspect(31, 64, 40, 9, 0x00, 1),
+ ObjectAspect(32, 66, 16, 3, 0x00, 1),
+ ObjectAspect(33, 68, 32, 5, 0x00, 1),
+ ObjectAspect(34, 70, 40, 16, 0x00, 0),
+ ObjectAspect(35, 72, 8, 7, 0x00, 1),
+ ObjectAspect(36, 74, 32, 7, 0x00, 1),
+ ObjectAspect(37, 76, 24, 14, 0x00, 0),
+ ObjectAspect(38, 78, 16, 8, 0x00, 0),
+ ObjectAspect(39, 80, 8, 3, 0x00, 1),
+ ObjectAspect(40, 82, 40, 9, 0x00, 1),
+ ObjectAspect(41, 84, 24, 14, 0x00, 0),
+ ObjectAspect(42, 86, 40, 20, 0x00, 0),
+ ObjectAspect(43, 88, 40, 15, 0x00, 1),
+ ObjectAspect(44, 90, 32, 10, 0x00, 1),
+ ObjectAspect(45, 92, 32, 19, 0x00, 0),
+ ObjectAspect(46, 94, 40, 25, 0x00, 2),
+ ObjectAspect(47, 96, 24, 7, 0x00, 1),
+ ObjectAspect(48, 98, 8, 7, 0x00, 1),
+ ObjectAspect(49, 100, 16, 5, 0x00, 1),
+ ObjectAspect(50, 102, 8, 9, 0x00, 1),
+ ObjectAspect(51, 104, 32, 11, 0x00, 1),
+ ObjectAspect(52, 106, 32, 14, 0x00, 0),
+ ObjectAspect(53, 108, 24, 20, 0x00, 0),
+ ObjectAspect(54, 110, 16, 14, 0x00, 1),
+ ObjectAspect(55, 112, 32, 23, 0x00, 0),
+ ObjectAspect(56, 114, 24, 16, 0x00, 0),
+ ObjectAspect(57, 116, 32, 25, 0x00, 0),
+ ObjectAspect(58, 118, 24, 25, 0x00, 0),
+ ObjectAspect(59, 120, 8, 8, 0x00, 1),
+ ObjectAspect(60, 122, 8, 7, 0x00, 1),
+ ObjectAspect(61, 124, 8, 8, 0x00, 1),
+ ObjectAspect(62, 126, 8, 8, 0x00, 1),
+ ObjectAspect(63, 128, 8, 5, 0x00, 1),
+ ObjectAspect(64, 130, 8, 13, 0x01, 1),
+ ObjectAspect(65, 134, 16, 13, 0x00, 1),
+ ObjectAspect(66, 136, 16, 14, 0x00, 0),
+ ObjectAspect(67, 138, 16, 10, 0x00, 1),
+ ObjectAspect(68, 140, 8, 18, 0x00, 1),
+ ObjectAspect(69, 142, 8, 17, 0x00, 1),
+ ObjectAspect(70, 144, 32, 18, 0x00, 0),
+ ObjectAspect(71, 146, 16, 23, 0x00, 0),
+ ObjectAspect(72, 148, 16, 24, 0x00, 0),
+ ObjectAspect(73, 150, 16, 15, 0x00, 0),
+ ObjectAspect(74, 152, 8, 7, 0x00, 1),
+ ObjectAspect(75, 154, 8, 15, 0x00, 1),
+ ObjectAspect(76, 156, 8, 9, 0x00, 1),
+ ObjectAspect(77, 158, 16, 14, 0x00, 0),
+ ObjectAspect(78, 160, 8, 8, 0x00, 1),
+ ObjectAspect(79, 162, 16, 9, 0x00, 1),
+ ObjectAspect(80, 164, 8, 13, 0x01, 1),
+ ObjectAspect(81, 168, 8, 18, 0x00, 1),
+ ObjectAspect(82, 170, 24, 28, 0x00, 0),
+ ObjectAspect(83, 172, 40, 13, 0x00, 1),
+ ObjectAspect(84, 174, 8, 4, 0x00, 1),
+ ObjectAspect(85, 176, 32, 17, 0x00, 0)
+ };
+
+ static ProjectileAspect projectileAspect[k14_ProjectileAspectCount] = { // @ G0210_as_Graphic558_ProjectileAspects
+ /* ProjectileAspect( FirstNativeBitmapRelativeIndex, FirstDerivedBitmapRelativeIndex, ByteWidth, Height, GraphicInfo ) */
+ ProjectileAspect(0, 0, 32, 11, 0x0011), /* Arrow */
+ ProjectileAspect(3, 18, 16, 11, 0x0011), /* Dagger */
+ ProjectileAspect(6, 36, 24, 47, 0x0010), /* Axe - Executioner */
+ ProjectileAspect(9, 54, 32, 15, 0x0112), /* Explosion Lightning Bolt */
+ ProjectileAspect(11, 54, 32, 12, 0x0011), /* Slayer */
+ ProjectileAspect(14, 72, 24, 47, 0x0010), /* Stone Club */
+ ProjectileAspect(17, 90, 24, 47, 0x0010), /* Club */
+ ProjectileAspect(20, 108, 16, 11, 0x0011), /* Poison Dart */
+ ProjectileAspect(23, 126, 48, 18, 0x0011), /* Storm - Side Splitter - Diamond Edge - Falchion - Ra Blade - Rapier - Biter - Samurai Sword - Sword - Dragon Fang */
+ ProjectileAspect(26, 144, 8, 15, 0x0012), /* Throwing Star */
+ ProjectileAspect(28, 156, 16, 28, 0x0103), /* Explosion Fireball */
+ ProjectileAspect(29, 156, 16, 11, 0x0103), /* Explosion Default */
+ ProjectileAspect(30, 156, 16, 28, 0x0103), /* Explosion Slime */
+ ProjectileAspect(31, 156, 16, 24, 0x0103) /* Explosion Poison Bolt Poison Cloud */
+ };
+
+ /* Atari ST: { 0x003, 0x055, 0x773, 0x420, 0x774, 0x000, 0x040, 0x500, 0x642, 0x775, 0x742, 0x760, 0x750, 0x000, 0x310, 0x776 }, RGB colors are different */
+ static uint16 palCredits[16] = {0x006, 0x0AA, 0xFF6, 0x840, 0xFF8, 0x000, 0x080, 0xA00, 0xC84, 0xFFA, 0xF84, 0xFC0, 0xFA0, 0x000, 0x620, 0xFFC}; // @ G0019_aui_Graphic562_Palette_Credits
+ static uint16 palDungeonView[6][16] = { // @ G0021_aaui_Graphic562_Palette_DungeonView
+ /* Atari ST: { 0x000, 0x333, 0x444, 0x310, 0x066, 0x420, 0x040, 0x060, 0x700, 0x750, 0x643, 0x770, 0x222, 0x555, 0x007, 0x777 }, RGB colors are different */
+ { 0x000, 0x666, 0x888, 0x620, 0x0CC, 0x840, 0x080, 0x0C0, 0xF00, 0xFA0, 0xC86, 0xFF0, 0x444, 0xAAA, 0x00F, 0xFFF },
+ /* Atari ST: { 0x000, 0x222, 0x333, 0x310, 0x066, 0x410, 0x030, 0x050, 0x600, 0x640, 0x532, 0x760, 0x111, 0x444, 0x006, 0x666 }, RGB colors are different */
+ { 0x000, 0x444, 0x666, 0x620, 0x0CC, 0x820, 0x060, 0x0A0, 0xC00, 0x000, 0x000, 0xFC0, 0x222, 0x888, 0x00C, 0xCCC },
+ /* Atari ST: { 0x000, 0x111, 0x222, 0x210, 0x066, 0x310, 0x020, 0x040, 0x500, 0x530, 0x421, 0x750, 0x000, 0x333, 0x005, 0x555 }, RGB colors are different */
+ { 0x000, 0x222, 0x444, 0x420, 0x0CC, 0x620, 0x040, 0x080, 0xA00, 0x000, 0x000, 0xFA0, 0x000, 0x666, 0x00A, 0xAAA },
+ /* Atari ST: { 0x000, 0x000, 0x111, 0x100, 0x066, 0x210, 0x010, 0x030, 0x400, 0x420, 0x310, 0x640, 0x000, 0x222, 0x004, 0x444 }, RGB colors are different */
+ { 0x000, 0x000, 0x222, 0x200, 0x0CC, 0x420, 0x020, 0x060, 0x800, 0x000, 0x000, 0xC80, 0x000, 0x444, 0x008, 0x888 },
+ /* Atari ST: { 0x000, 0x000, 0x000, 0x000, 0x066, 0x100, 0x000, 0x020, 0x300, 0x310, 0x200, 0x530, 0x000, 0x111, 0x003, 0x333 }, RGB colors are different */
+ { 0x000, 0x000, 0x000, 0x000, 0x0CC, 0x200, 0x000, 0x040, 0x600, 0x000, 0x000, 0xA60, 0x000, 0x222, 0x006, 0x666 },
+ /* Atari ST: { 0x000, 0x000, 0x000, 0x000, 0x066, 0x000, 0x000, 0x010, 0x200, 0x200, 0x100, 0x320, 0x000, 0x000, 0x002, 0x222 }, RGB colors are different */
+ { 0x000, 0x000, 0x000, 0x000, 0x0CC, 0x000, 0x000, 0x020, 0x400, 0x000, 0x000, 0x640, 0x000, 0x000, 0x004, 0x444 }
+ };
+
+ static byte palChangesCreatureD3[16] = {0, 120, 10, 30, 40, 30, 0, 60, 30, 0, 0, 110, 0, 20, 0, 130}; // @ G0221_auc_Graphic558_PaletteChanges_Creature_D3
+ static byte palChangesCreatureD2[16] = {0, 10, 20, 30, 40, 30, 60, 70, 50, 0, 0, 110, 120, 130, 140, 150}; // @ G0222_auc_Graphic558_PaletteChanges_Creature_D2
+ static byte palChangesNoChanges[16] = {0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150}; // @ G0017_auc_Graphic562_PaletteChanges_NoChanges
+ static byte palChangesFloorOrnD3[16] = {0, 120, 10, 30, 40, 30, 0, 60, 30, 90, 100, 110, 0, 20, 140, 130}; // @ G0213_auc_Graphic558_PaletteChanges_FloorOrnament_D3
+ static byte palChangesFloorOrnD2[16] = {0, 10, 20, 30, 40, 30, 60, 70, 50, 90, 100, 110, 120, 130, 140, 150}; // @ G0214_auc_Graphic558_PaletteChanges_FloorOrnament_D2
+
+ _frameWallD3R2 = Frame(208, 223, 25, 73, 8, 49, 0, 0); // @ G0712_s_Graphic558_Frame_Wall_D3R2
+
+ _doorFrameLeftD1C = Frame(43, 74, 14, 107, 16, 94, 0, 0); // @ G0170_s_Graphic558_Frame_DoorFrameLeft_D1C
+ _doorFrameRightD1C = Frame(149, 180, 14, 107, 16, 94, 0, 0); // @ G0171_s_Graphic558_Frame_DoorFrameRight_D1C
+
+ for (int i = 0; i < 16; i++) {
+ _palChangesDoorButtonAndWallOrnD3[i] = palChangesDoorButtonAndWallOrnD3[i];
+ _palChangesDoorButtonAndWallOrnD2[i] = palChangesDoorButtonAndWallOrnD2[i];
+ _palChangeSmoke[i] = palChangeSmoke[i];
+ _palCredits[i] = palCredits[i];
+ _palChangesCreatureD3[i] = palChangesCreatureD3[i];
+ _palChangesCreatureD2[i] = palChangesCreatureD2[i];
+ _palChangesNoChanges[i] = palChangesNoChanges[i];
+ _palChangesFloorOrnD3[i] = palChangesFloorOrnD3[i];
+ _palChangesFloorOrnD2[i] = palChangesFloorOrnD2[i];
+ for (int j = 0; j < 6; j++)
+ _palDungeonView[j][i] = palDungeonView[j][i];
+ }
+
+ for (int i = 0; i < 12; i++) {
+ _fieldAspects188[i] = fieldAspects188[i];
+ _frameWalls163[i] = frameWalls163[i];
+ }
+
+ for (int i = 0; i < 7; i++)
+ _projectileScales[i] = projectileScales[i];
+
+ for (int i = 0; i < k4_ExplosionAspectCount; i++)
+ _explosionAspects[i] = explosionAspects[i];
+
+ for (int i = 0; i < k27_CreatureTypeCount; i++)
+ _creatureAspects219[i] = creatureAspects219[i];
+
+ for (int i = 0; i < k85_ObjAspectCount; i++)
+ _objectAspects209[i] = objectAspects209[i];
+
+ for (int i = 0; i < k14_ProjectileAspectCount; i++)
+ _projectileAspect[i] = projectileAspect[i];
+
+ _doorFrameD1C = new DoorFrames( // @ G0186_s_Graphic558_Frames_Door_D1C
+ Frame(64, 159, 17, 102, 48, 88, 0, 0), /* Closed Or Destroyed */
+ Frame(64, 159, 17, 38, 48, 88, 0, 66), /* Vertical Closed one fourth */
+ Frame(64, 159, 17, 60, 48, 88, 0, 44), /* Vertical Closed half */
+ Frame(64, 159, 17, 82, 48, 88, 0, 22), /* Vertical Closed three fourth */
+ Frame(64, 75, 17, 102, 48, 88, 36, 0), /* Left Horizontal Closed one fourth */
+ Frame(64, 87, 17, 102, 48, 88, 24, 0), /* Left Horizontal Closed half */
+ Frame(64, 99, 17, 102, 48, 88, 12, 0), /* Left Horizontal Closed three fourth */
+ Frame(148, 159, 17, 102, 48, 88, 48, 0), /* Right Horizontal Closed one fourth */
+ Frame(136, 159, 17, 102, 48, 88, 48, 0), /* Right Horizontal Closed half */
+ Frame(124, 159, 17, 102, 48, 88, 48, 0) /* Right Horizontal Closed three fourth */
+ );
+
+ _boxThievesEyeViewPortVisibleArea = Box(64, 159, 19, 113); // @ G0106_s_Graphic558_Box_ThievesEye_ViewportVisibleArea
+ _boxMovementArrows = Box(224, 319, 124, 168); // @ G0002_s_Graphic562_Box_MovementArrows
+}
+
+DisplayMan::~DisplayMan() {
+ delete[] _packedItemPos;
+ delete[] _packedBitmaps;
+ delete[] _bitmapScreen;
+ if (_bitmaps) {
+ delete[] _bitmaps[0];
+ delete[] _bitmaps;
+ }
+ delete[] _bitmapCompressedByteCount;
+ delete[] _bitmapDecompressedByteCount;
+
+ delete[] _derivedBitmapByteCount;
+ if (_derivedBitmaps) {
+ for (uint16 i = 0; i < k730_DerivedBitmapMaximumCount; ++i)
+ delete[] _derivedBitmaps[i];
+ delete[] _derivedBitmaps;
+ }
+
+ delete[] _bitmapCeiling;
+ delete[] _bitmapFloor;
+ delete[] _bitmapWallSetD3L2;
+ delete[] _bitmapWallSetD3R2;
+ delete[] _bitmapWallSetD3LCR;
+ delete[] _bitmapWallSetD2LCR;
+ delete[] _bitmapWallSetD1LCR;
+ delete[] bitmapWallSetWallD0L;
+ delete[] _bitmapWallSetWallD0R;
+ delete[] _bitmapWallSetDoorFrameTopD2LCR;
+ delete[] _bitmapWallSetDoorFrameTopD1LCR;
+ delete[] _bitmapWallSetDoorFrameLeftD3L;
+ delete[] _bitmapWallSetDoorFrameLeftD3C;
+ delete[] _bitmapWallSetDoorFrameLeftD2C;
+ delete[] _bitmapWallSetDoorFrameLeftD1C;
+ delete[] _bitmapWallSetDoorFrameRightD1C;
+ delete[] _bitmapWallSetDoorFrameFront;
+ delete[] _bitmapViewport;
+
+ delete[] _bitmapWallD3LCRFlipped;
+ delete[] _bitmapWallD2LCRFlipped;
+ delete[] _bitmapWallD1LCRFlipped;
+ delete[] _bitmapWallD0LFlipped;
+ delete[] _bitmapWallD0RFlipped;
+
+ delete _doorFrameD1C;
+}
+
+void DisplayMan::setUpScreens(uint16 width, uint16 height) {
+ _screenWidth = width;
+ _screenHeight = height;
+ delete[] _tmpBitmap;
+ delete[] _bitmapScreen;
+ _bitmapScreen = new byte[_screenWidth * _screenHeight];
+ fillScreen(k0_ColorBlack);
+
+ _tmpBitmap = new byte[_screenWidth * _screenHeight];
+}
+
+
+void DisplayMan::initializeGraphicData() {
+ _bitmapCeiling = new byte[224 * 29];
+ _bitmapFloor = new byte[224 * 70];
+ _bitmapWallSetD3L2 = new byte[16 * 49];
+ _bitmapWallSetD3R2 = new byte[16 * 49];
+ _bitmapWallSetD3LCR = new byte[128 * 51];
+ _bitmapWallSetD2LCR = new byte[144 * 71];
+ _bitmapWallSetD1LCR = new byte[256 * 111];
+ bitmapWallSetWallD0L = new byte[32 * 136];
+ _bitmapWallSetWallD0R = new byte[32 * 136];
+ _bitmapWallSetDoorFrameTopD2LCR = new byte[96 * 3];
+ _bitmapWallSetDoorFrameTopD1LCR = new byte[128 * 4];
+ _bitmapWallSetDoorFrameLeftD3L = new byte[32 * 44];
+ _bitmapWallSetDoorFrameLeftD3C = new byte[32 * 44];
+ _bitmapWallSetDoorFrameLeftD2C = new byte[48 * 65];
+ _bitmapWallSetDoorFrameLeftD1C = new byte[32 * 94];
+ _bitmapWallSetDoorFrameRightD1C = new byte[32 * 94];
+ _bitmapWallSetDoorFrameFront = new byte[32 * 123];
+ _bitmapViewport = new byte[224 * 136];
+
+ if (!_derivedBitmapByteCount)
+ _derivedBitmapByteCount = new uint16[k730_DerivedBitmapMaximumCount];
+ if (!_derivedBitmaps) {
+ _derivedBitmaps = new byte *[k730_DerivedBitmapMaximumCount];
+ for (uint16 i = 0; i < k730_DerivedBitmapMaximumCount; ++i)
+ _derivedBitmaps[i] = nullptr;
+ }
+
+ _derivedBitmapByteCount[k0_DerivedBitmapViewport] = 112 * 136;
+ _derivedBitmapByteCount[k1_DerivedBitmapThievesEyeVisibleArea] = 48 * 95;
+ _derivedBitmapByteCount[k2_DerivedBitmapDamageToCreatureMedium] = 32 * 37;
+ _derivedBitmapByteCount[k3_DerivedBitmapDamageToCreatureSmall] = 24 * 37;
+
+ for (int16 doorOrnamentIndex = k15_DoorOrnDestroyedMask; doorOrnamentIndex <= k16_DoorOrnThivesEyeMask; doorOrnamentIndex++) {
+ _currMapDoorOrnInfo[doorOrnamentIndex][k0_NativeBitmapIndex] = doorOrnamentIndex + (k301_DoorMaskDestroyedIndice - k15_DoorOrnDestroyedMask);
+ _currMapDoorOrnInfo[doorOrnamentIndex][k1_CoordinateSet] = 1;
+
+ _derivedBitmapByteCount[doorOrnamentIndex * 2 + k68_DerivedBitmapFirstDoorOrnament_D3] = 24 * 41;
+ _derivedBitmapByteCount[doorOrnamentIndex * 2 + k69_DerivedBitmapFirstDoorOrnament_D2] = 32 * 61;
+ }
+
+ _currMapFloorOrnInfo[k15_FloorOrnFootprints][k0_NativeBitmapIndex] = k241_FloorOrn_15_D3L_footprints;
+ _currMapFloorOrnInfo[k15_FloorOrnFootprints][k1_CoordinateSet] = 1;
+
+ ObjectAspect *objectAspect = _objectAspects209;
+ int16 derivedBitmapIndex;
+ for (int16 objectAspectIndex = 0; objectAspectIndex < k85_ObjAspectCount; ++objectAspectIndex, ++objectAspect) {
+ derivedBitmapIndex = k104_DerivedBitmapFirstObject + objectAspect->_firstDerivedBitmapRelativeIndex;
+
+ _derivedBitmapByteCount[derivedBitmapIndex++] = getScaledBitmapByteCount(objectAspect->_byteWidth, objectAspect->_height, k16_Scale_D3);
+ _derivedBitmapByteCount[derivedBitmapIndex++] = getScaledBitmapByteCount(objectAspect->_byteWidth, objectAspect->_height, k20_Scale_D2);
+
+ if (getFlag(objectAspect->_graphicInfo, k0x0001_ObjectFlipOnRightMask)) {
+ _derivedBitmapByteCount[derivedBitmapIndex] = _derivedBitmapByteCount[derivedBitmapIndex - 2];
+ derivedBitmapIndex++;
+ _derivedBitmapByteCount[derivedBitmapIndex] = _derivedBitmapByteCount[derivedBitmapIndex - 2];
+ derivedBitmapIndex++;
+ }
+
+ if (getFlag(objectAspect->_graphicInfo, k0x0010_ObjectAlcoveMask)) {
+ _derivedBitmapByteCount[derivedBitmapIndex] = _derivedBitmapByteCount[derivedBitmapIndex - 2];
+ derivedBitmapIndex++;
+ _derivedBitmapByteCount[derivedBitmapIndex] = _derivedBitmapByteCount[derivedBitmapIndex - 2];
+ }
+ }
+
+ ProjectileAspect *projectileAspect = _projectileAspect;
+ for (int16 projectileAspectIndex = 0; projectileAspectIndex < k14_ProjectileAspectCount; projectileAspectIndex++, projectileAspect++) {
+ if (!getFlag(projectileAspect->_graphicInfo, k0x0100_ProjectileScaleWithKineticEnergyMask)) {
+ derivedBitmapIndex = k282_DerivedBitmapFirstProjectile + projectileAspect->_firstDerivedBitmapRelativeIndex;
+
+ for (int16 projectileScaleIndex = 0; projectileScaleIndex < 6; projectileScaleIndex++) {
+ int16 bitmapByteCount = getScaledBitmapByteCount(projectileAspect->_byteWidth, projectileAspect->_height, _projectileScales[projectileScaleIndex]);
+ _derivedBitmapByteCount[derivedBitmapIndex] = bitmapByteCount;
+
+ if (getFlag(projectileAspect->_graphicInfo, k0x0003_ProjectileAspectTypeMask) != k3_ProjectileAspectHasNone) {
+ _derivedBitmapByteCount[derivedBitmapIndex + 6] = bitmapByteCount;
+
+ if (getFlag(projectileAspect->_graphicInfo, k0x0003_ProjectileAspectTypeMask) != k2_ProjectileAspectHasRotation)
+ _derivedBitmapByteCount[derivedBitmapIndex + 12] = bitmapByteCount;
+ }
+ }
+ }
+ }
+
+ _palChangesProjectile[0] = _palChangesFloorOrnD3;
+ _palChangesProjectile[1] = _palChangesFloorOrnD2;
+ _palChangesProjectile[2] = _palChangesProjectile[3] = _palChangesNoChanges;
+
+ derivedBitmapIndex = k438_DerivedBitmapFirstExplosion;
+ ExplosionAspect *expAsp = _explosionAspects;
+ for (uint16 expAspIndex = 0; expAspIndex < k4_ExplosionAspectCount; ++expAspIndex, expAsp++) {
+ for (int16 scale = 4; scale < 32; scale += 2)
+ _derivedBitmapByteCount[derivedBitmapIndex++] = getScaledBitmapByteCount(expAsp->_byteWidth, expAsp->_height, scale);
+
+ if (expAspIndex == k3_ExplosionAspectSmoke)
+ _derivedBitmapByteCount[derivedBitmapIndex++] = expAsp->_byteWidth * expAsp->_height;
+ }
+
+ derivedBitmapIndex = k495_DerivedBitmapFirstCreature;
+ CreatureAspect *creatureAsp;
+ for (int16 creatureIndex = 0; creatureIndex < k27_CreatureTypeCount; creatureIndex++) {
+ creatureAsp = &_creatureAspects219[creatureIndex];
+
+ int16 creatureGraphicInfo = _vm->_dungeonMan->_creatureInfos[creatureIndex]._graphicInfo;
+ creatureAsp->_firstDerivedBitmapIndex = derivedBitmapIndex;
+
+ int16 creatureFrontBitmapD3PixelCount = getScaledBitmapByteCount(creatureAsp->_byteWidthFront, creatureAsp->_heightFront, k16_Scale_D3);
+ _derivedBitmapByteCount[derivedBitmapIndex++] = creatureFrontBitmapD3PixelCount;
+
+ int16 creatureFrontBitmapD2PixelCount = getScaledBitmapByteCount(creatureAsp->_byteWidthFront, creatureAsp->_heightFront, k20_Scale_D2);
+ _derivedBitmapByteCount[derivedBitmapIndex++] = creatureFrontBitmapD2PixelCount;
+
+ if (getFlag(creatureGraphicInfo, k0x0008_CreatureInfoGraphicMaskSide)) {
+ _derivedBitmapByteCount[derivedBitmapIndex++] = getScaledBitmapByteCount(creatureAsp->_byteWidthSide, creatureAsp->_heightSide, k16_Scale_D3);
+ _derivedBitmapByteCount[derivedBitmapIndex++] = getScaledBitmapByteCount(creatureAsp->_byteWidthSide, creatureAsp->_heightSide, k20_Scale_D2);
+ }
+
+ if (getFlag(creatureGraphicInfo, k0x0010_CreatureInfoGraphicMaskBack)) {
+ _derivedBitmapByteCount[derivedBitmapIndex++] = creatureFrontBitmapD3PixelCount;
+ _derivedBitmapByteCount[derivedBitmapIndex++] = creatureFrontBitmapD2PixelCount;
+ }
+
+ if (getFlag(creatureGraphicInfo, k0x0020_CreatureInfoGraphicMaskAttack)) {
+ _derivedBitmapByteCount[derivedBitmapIndex++] = getScaledBitmapByteCount(creatureAsp->_byteWidthAttack, creatureAsp->_heightAttack, k16_Scale_D3);
+ _derivedBitmapByteCount[derivedBitmapIndex++] = getScaledBitmapByteCount(creatureAsp->_byteWidthAttack, creatureAsp->_heightAttack, k20_Scale_D2);
+ }
+
+ int16 additionalFronGraphicCount = getFlag(creatureGraphicInfo, k0x0003_CreatureInfoGraphicMaskAdditional);
+ if (additionalFronGraphicCount) {
+ do {
+ _derivedBitmapByteCount[derivedBitmapIndex++] = creatureAsp->_byteWidthFront * creatureAsp->_heightFront;
+ _derivedBitmapByteCount[derivedBitmapIndex++] = getScaledBitmapByteCount(creatureAsp->_byteWidthFront, creatureAsp->_heightFront, k16_Scale_D3);
+ _derivedBitmapByteCount[derivedBitmapIndex++] = getScaledBitmapByteCount(creatureAsp->_byteWidthFront, creatureAsp->_heightFront, k20_Scale_D2);
+ } while (--additionalFronGraphicCount);
+ }
+ }
+}
+
+void DisplayMan::loadGraphics() {
+ Common::File f;
+ f.open("graphics.dat");
+ _grapItemCount = f.readUint16BE();
+
+ delete[] _bitmapCompressedByteCount;
+ _bitmapCompressedByteCount = new uint32[_grapItemCount];
+ for (uint16 i = 0; i < _grapItemCount; ++i)
+ _bitmapCompressedByteCount[i] = f.readUint16BE();
+
+ delete[] _bitmapDecompressedByteCount;
+ _bitmapDecompressedByteCount = new uint32[_grapItemCount];
+ for (uint16 i = 0; i < _grapItemCount; ++i)
+ _bitmapDecompressedByteCount[i] = f.readUint16BE();
+
+ delete[] _packedItemPos;
+ _packedItemPos = new uint32[_grapItemCount + 1];
+ _packedItemPos[0] = 0;
+ for (uint16 i = 1; i < _grapItemCount + 1; ++i) {
+ _packedItemPos[i] = _packedItemPos[i - 1] + _bitmapDecompressedByteCount[i - 1];
+ }
+
+ delete[] _packedBitmaps;
+ _packedBitmaps = new uint8[_packedItemPos[_grapItemCount]];
+
+ LZWdecompressor lzw;
+ Common::Array<byte> tmpBuffer;
+ f.seek(2 + _grapItemCount * 4);
+ for (uint32 i = 0; i < _grapItemCount; ++i) {
+ byte *bitmap = _packedBitmaps + _packedItemPos[i];
+ f.read(bitmap, _bitmapCompressedByteCount[i]);
+ if (_bitmapCompressedByteCount[i] != _bitmapDecompressedByteCount[i]) {
+ tmpBuffer.reserve(_bitmapDecompressedByteCount[i]);
+ Common::MemoryReadStream stream(bitmap, _bitmapCompressedByteCount[i]);
+ lzw.decompress(stream, _bitmapCompressedByteCount[i], tmpBuffer.begin());
+ memcpy(bitmap, tmpBuffer.begin(), _bitmapDecompressedByteCount[i]);
+ }
+ }
+
+ f.close();
+ unpackGraphics();
+}
+
+void DisplayMan::unpackGraphics() {
+ uint32 unpackedBitmapsSize = 0;
+ for (uint16 i = 0; i <= 20; ++i)
+ unpackedBitmapsSize += getPixelWidth(i) * getPixelHeight(i);
+ for (uint16 i = 22; i <= 532; ++i)
+ unpackedBitmapsSize += getPixelWidth(i) * getPixelHeight(i);
+ unpackedBitmapsSize += (5 + 1) * 6 * 128; // 5 x 6 characters, 128 of them, +1 for convenience padding
+ // graphics items go from 0-20 and 22-532 inclusive, _unpackedItemPos 21 and 22 are there for indexing convenience
+ if (_bitmaps) {
+ delete[] _bitmaps[0];
+ delete[] _bitmaps;
+ }
+ _bitmaps = new byte *[575]; // largest graphic indice (i think)
+ _bitmaps[0] = new byte[unpackedBitmapsSize];
+ loadIntoBitmap(0, _bitmaps[0]);
+ for (uint16 i = 1; i <= 20; ++i) {
+ _bitmaps[i] = _bitmaps[i - 1] + getPixelWidth(i - 1) * getPixelHeight(i - 1);
+ loadIntoBitmap(i, _bitmaps[i]);
+ }
+ _bitmaps[22] = _bitmaps[20] + getPixelWidth(20) * getPixelHeight(20);
+ for (uint16 i = 23; i <= 532; ++i) {
+ _bitmaps[i] = _bitmaps[i - 1] + getPixelWidth(i - 1) * getPixelHeight(i - 1);
+ loadIntoBitmap(i, _bitmaps[i]);
+ }
+ _bitmaps[k557_FontGraphicIndice] = _bitmaps[532] + getPixelWidth(532) * getPixelHeight(532);
+ loadFNT1intoBitmap(k557_FontGraphicIndice, _bitmaps[k557_FontGraphicIndice]);
+}
+
+void DisplayMan::loadFNT1intoBitmap(uint16 index, byte * destBitmap) {
+ uint8 *data = _packedBitmaps + _packedItemPos[index];
+
+ for (uint16 i = 0; i < 6; i++) {
+ for (uint16 w = 0; w < 128; ++w) {
+ *destBitmap++ = k0_ColorBlack;
+
+ uint16 nextByte = *data++;
+ for (int16 pixel = 4; pixel >= 0; --pixel) {
+ *destBitmap++ = (nextByte >> pixel) & 0x1;
+ }
+ }
+ }
+}
+
+void DisplayMan::allocateFlippedWallBitmaps() {
+ _bitmapWallD3LCRFlipped = new byte[128 * 51];
+ _bitmapWallD2LCRFlipped = new byte[144 * 71];
+ _bitmapWallD1LCRFlipped = new byte[256 * 111];
+ _bitmapWallD0LFlipped = new byte[32 * 136];
+ _bitmapWallD0RFlipped = new byte[32 * 136];
+}
+
+void DisplayMan::drawDoorBitmap(Frame* frame) {
+ if (frame->_srcByteWidth) {
+ blitToBitmap(_tmpBitmap, _bitmapViewport, frame->_box, frame->_srcX, frame->_srcY,
+ frame->_srcByteWidth, k112_byteWidthViewport, k10_ColorFlesh, frame->_srcHeight, k136_heightViewport);
+ }
+}
+
+void DisplayMan::drawDoorFrameBitmapFlippedHorizontally(byte *bitmap, Frame *frame) {
+ if (frame->_srcByteWidth) {
+ flipBitmapHorizontal(bitmap, frame->_srcByteWidth, frame->_srcHeight);
+ blitToBitmap(bitmap, _bitmapViewport, frame->_box, frame->_srcX, frame->_srcY,
+ frame->_srcByteWidth, k112_byteWidthViewport, k10_ColorFlesh, frame->_srcHeight, k136_heightViewport);
+ }
+}
+
+void DisplayMan::drawDoorButton(int16 doorButtonOrdinal, int16 viewDoorButtonIndex) {
+ static byte doorButtonCoordSet[1] = {0}; // @ G0197_auc_Graphic558_DoorButtonCoordinateSet
+ static uint16 doorButtonCoordSets[1][4][6] = { // @ G0208_aaauc_Graphic558_DoorButtonCoordinateSets
+ // X1, X2, Y1, Y2, ByteWidth, Height
+ { {199, 204, 41, 44, 8, 4}, /* D3R */
+ {136, 141, 41, 44, 8, 4}, /* D3C */
+ {144, 155, 42, 47, 8, 6}, /* D2C */
+ {160, 175, 44, 52, 8, 9} /* D1C */
+ }
+ };
+
+ if (doorButtonOrdinal) {
+ doorButtonOrdinal--;
+
+ assert(doorButtonOrdinal == 0);
+
+ int16 nativeBitmapIndex = doorButtonOrdinal + k315_firstDoorButton_GraphicIndice;
+ int coordSet = doorButtonCoordSet[doorButtonOrdinal];
+ uint16 *coordSetRedEagle = doorButtonCoordSets[coordSet][viewDoorButtonIndex];
+
+ byte *bitmap = nullptr;
+ if (viewDoorButtonIndex == k3_viewDoorButton_D1C) {
+ bitmap = getNativeBitmapOrGraphic(nativeBitmapIndex);
+
+ _vm->_dungeonMan->_dungeonViewClickableBoxes[k5_ViewCellDoorButtonOrWallOrn]._x1 = coordSetRedEagle[0];
+ _vm->_dungeonMan->_dungeonViewClickableBoxes[k5_ViewCellDoorButtonOrWallOrn]._x2 = coordSetRedEagle[1];
+ _vm->_dungeonMan->_dungeonViewClickableBoxes[k5_ViewCellDoorButtonOrWallOrn]._y1 = coordSetRedEagle[2];
+ _vm->_dungeonMan->_dungeonViewClickableBoxes[k5_ViewCellDoorButtonOrWallOrn]._y2 = coordSetRedEagle[3];
+ } else {
+ doorButtonOrdinal = k102_DerivedBitmapFirstDoorButton + (doorButtonOrdinal * 2) + ((!viewDoorButtonIndex) ? 0 : viewDoorButtonIndex - 1);
+ if (!isDerivedBitmapInCache(doorButtonOrdinal)) {
+ uint16 *coordSetBlueGoat = doorButtonCoordSets[coordSet][k3_viewDoorButton_D1C];
+ byte *bitmapNative = getNativeBitmapOrGraphic(nativeBitmapIndex);
+ blitToBitmapShrinkWithPalChange(bitmapNative, getDerivedBitmap(doorButtonOrdinal),
+ coordSetBlueGoat[4] << 1, coordSetBlueGoat[5],
+ // modified code line
+ coordSetRedEagle[4] << 1,
+ coordSetRedEagle[5],
+ (viewDoorButtonIndex == k2_viewDoorButton_D2C) ? _palChangesDoorButtonAndWallOrnD2 : _palChangesDoorButtonAndWallOrnD3);
+
+ addDerivedBitmap(doorButtonOrdinal);
+ }
+ bitmap = getDerivedBitmap(doorButtonOrdinal);
+ }
+ blitToBitmap(bitmap, _bitmapViewport, *(Box *)coordSetRedEagle, 0, 0,
+ coordSetRedEagle[4], k112_byteWidthViewport, k10_ColorFlesh, coordSetRedEagle[5], k136_heightViewport);
+ }
+}
+
+void DisplayMan::viewportSetPalette(uint16* middleScreenPalette, uint16* topAndBottomScreen) {
+ if (middleScreenPalette && topAndBottomScreen)
+ buildPaletteChangeCopperList(middleScreenPalette, topAndBottomScreen);
+
+ viewportBlitToScreen();
+}
+
+void DisplayMan::viewportBlitToScreen() {
+ Box box(0, 223, 33, 168);
+
+ blitToBitmap(_bitmapViewport, _bitmapScreen, box, 0, 0, k112_byteWidthViewport, k160_byteWidthScreen, kM1_ColorNoTransparency,
+ k136_heightViewport, k200_heightScreen);
+}
+
+void DisplayMan::loadIntoBitmap(uint16 index, byte *destBitmap) {
+ uint8 *data = _packedBitmaps + _packedItemPos[index];
+
+ uint16 width = READ_BE_UINT16(data);
+ uint16 height = READ_BE_UINT16(data + 2);
+ uint16 nextByteIndex = 4;
+
+ for (int32 k = 0; k < width * height;) {
+ uint8 nextByte = data[nextByteIndex++];
+ uint8 nibble1 = (nextByte & 0xF0) >> 4;
+ uint8 nibble2 = (nextByte & 0x0F);
+ if (nibble1 <= 7) {
+ for (int j = 0; j < nibble1 + 1; ++j)
+ destBitmap[k++] = nibble2;
+ } else if (nibble1 == 0x8) {
+ uint8 byte1 = data[nextByteIndex++];
+ for (int j = 0; j < byte1 + 1; ++j)
+ destBitmap[k++] = nibble2;
+ } else if (nibble1 == 0xC) {
+ uint16 word1 = READ_BE_UINT16(data + nextByteIndex);
+ nextByteIndex += 2;
+ for (int j = 0; j < word1 + 1; ++j)
+ destBitmap[k++] = nibble2;
+ } else if (nibble1 == 0xB) {
+ uint8 byte1 = data[nextByteIndex++];
+ for (int j = 0; j < byte1 + 1; ++j, ++k)
+ destBitmap[k] = destBitmap[k - width];
+ destBitmap[k++] = nibble2;
+ } else if (nibble1 == 0xF) {
+ uint16 word1 = READ_BE_UINT16(data + nextByteIndex);
+ nextByteIndex += 2;
+ for (int j = 0; j < word1 + 1; ++j, ++k)
+ destBitmap[k] = destBitmap[k - width];
+ destBitmap[k++] = nibble2;
+ } else if (nibble1 == 9) {
+ uint8 byte1 = data[nextByteIndex++];
+ if (byte1 % 2)
+ byte1++;
+ else
+ destBitmap[k++] = nibble2;
+
+ for (int j = 0; j < byte1 / 2; ++j) {
+ uint8 byte2 = data[nextByteIndex++];
+ destBitmap[k++] = (byte2 & 0xF0) >> 4;
+ destBitmap[k++] = byte2 & 0x0F;
+ }
+ }
+ }
+}
+
+void DisplayMan::blitToBitmap(byte *srcBitmap, byte *destBitmap, const Box &box, uint16 srcX, uint16 srcY, uint16 srcByteWidth,
+ uint16 destByteWidth, Color transparent, int16 srcHeight, int16 destHight) {
+ uint16 srcWidth = srcByteWidth * 2;
+ uint16 destWidth = destByteWidth * 2;
+ for (uint16 y = 0; y < box._y2 + 1 - box._y1; ++y) { // + 1 for inclusive boundaries
+ for (uint16 x = 0; x < box._x2 + 1 - box._x1; ++x) { // + 1 for inclusive boundaries
+ if (srcX + x < srcWidth && y + srcY < srcHeight
+ && box._x1 + x < destWidth && y + box._y1 < destHight) {
+ byte srcPixel = srcBitmap[srcWidth * (y + srcY) + srcX + x];
+ if (srcPixel != transparent)
+ destBitmap[destWidth * (y + box._y1) + box._x1 + x] = srcPixel;
+ }
+ }
+ }
+}
+
+void DisplayMan::fillScreenBox(Box &box, Color color) {
+ uint16 width = box._x2 + 1 - box._x1; // + 1 for inclusive boundaries
+ for (int16 y = box._y1; y < box._y2 + 1; ++y) // + 1 for inclusive boundaries
+ memset(_bitmapScreen + y * _screenWidth + box._x1, color, sizeof(byte) * width);
+}
+
+void DisplayMan::fillBoxBitmap(byte *destBitmap, Box &box, Color color, int16 byteWidth, int16 height) {
+ for (int16 y = box._y1; y < box._y2 + 1; ++y) // + 1 for inclusive boundaries
+ memset(destBitmap + y * byteWidth * 2 + box._x1, color, sizeof(byte) * (box._x2 - box._x1 + 1)); // + 1 for inclusive boundaries
+}
+
+void DisplayMan::blitBoxFilledWithMaskedBitmap(byte *src, byte *dest, byte *mask, byte *tmp, Box& box,
+ int16 lastUnitIndex, int16 firstUnitIndex, int16 destByteWidth, Color transparent,
+ int16 xPos, int16 yPos, int16 destHeight, int16 height2) {
+ // make sure to take care of inclusive boundaries, color can have 0x8000 flag to not use mask
+ warning("STUB: blitBoxFilledWithMaskedBitmap");
+}
+
+void DisplayMan::flipBitmapHorizontal(byte *bitmap, uint16 byteWidth, uint16 height) {
+ uint16 width = byteWidth * 2;
+ for (uint16 y = 0; y < height; ++y) {
+ for (uint16 x = 0; x < width / 2; ++x)
+ SWAP<byte>(bitmap[y * width + x], bitmap[y * width + width - 1 - x]);
+ }
+}
+
+void DisplayMan::flipBitmapVertical(byte *bitmap, uint16 byteWidth, uint16 height) {
+ uint16 width = byteWidth * 2;
+ byte *tmp = new byte[width];
+
+ for (uint16 y = 0; y < height / 2; ++y) {
+ memmove(tmp, bitmap + y * width, width);
+ memmove(bitmap + y * width, bitmap + (height - 1 - y) * width, width);
+ memmove(bitmap + (height - 1 - y) * width, tmp, width);
+ }
+
+ delete[] tmp;
+}
+
+byte *DisplayMan::getExplosionBitmap(uint16 explosionAspIndex, uint16 scale, int16& returnByteWidth, int16& returnHeight) {
+ ExplosionAspect *explAsp = &_explosionAspects[explosionAspIndex];
+ if (scale > 32)
+ scale = 32;
+ int16 pixelWidth = getScaledDimension(explAsp->_byteWidth, scale);
+ int16 height = getScaledDimension(explAsp->_height, scale);
+ byte *bitmap;
+ int16 derBitmapIndex = (explosionAspIndex * 14) + scale / 2 + k438_DerivedBitmapFirstExplosion - 2;
+ if ((scale == 32) && (explosionAspIndex != k3_ExplosionAspectSmoke))
+ bitmap = getNativeBitmapOrGraphic(explosionAspIndex + k348_FirstExplosionGraphicIndice);
+ else if (isDerivedBitmapInCache(derBitmapIndex))
+ bitmap = getDerivedBitmap(derBitmapIndex);
+ else {
+ byte *nativeBitmap = getNativeBitmapOrGraphic(MIN(explosionAspIndex, (uint16)k2_ExplosionAspectPoison) + k348_FirstExplosionGraphicIndice);
+ bitmap = getDerivedBitmap(derBitmapIndex);
+ blitToBitmapShrinkWithPalChange(nativeBitmap, bitmap, explAsp->_byteWidth, explAsp->_height, pixelWidth * 2, height,
+ (explosionAspIndex == k3_ExplosionAspectSmoke) ? _palChangeSmoke : _palChangesNoChanges);
+ addDerivedBitmap(derBitmapIndex);
+ }
+
+ returnByteWidth = pixelWidth;
+ returnHeight = height;
+ return bitmap;
+}
+
+void DisplayMan::updateScreen() {
+ _vm->_textMan->updateMessageArea();
+ // apply copper
+ for (uint32 i = 320 * 30; i < 320 * 170; ++i)
+ _bitmapScreen[i] += 16;
+ g_system->copyRectToScreen(_bitmapScreen, _screenWidth, 0, 0, _screenWidth, _screenHeight);
+ _vm->_console->onFrame();
+ g_system->updateScreen();
+ for (uint32 i = 320 * 30; i < 320 * 170; ++i)
+ _bitmapScreen[i] -= 16;
+}
+
+void DisplayMan::drawViewport(int16 palSwitchingRequestedState) {
+ static uint16 *dungeonViewCurrentPalette; // @ K0010_pui_DungeonViewCurrentPalette
+
+ // ignored code F0510_AMIGA_WaitBottomOfViewPort
+ if (palSwitchingRequestedState == k2_viewportAsBeforeSleepOrFreezeGame)
+ palSwitchingRequestedState = _paletteSwitchingEnabled ? 1 : 0;
+
+ if (_refreshDungeonViewPaleteRequested) {
+ dungeonViewCurrentPalette = _palDungeonView[_dungeonViewPaletteIndex];
+ _refreshDungeonViewPaleteRequested = false;
+ if (palSwitchingRequestedState == k0_viewportNotDungeonView)
+ _paletteSwitchingEnabled = true;
+ else
+ _paletteSwitchingEnabled = false;
+ }
+
+ if (palSwitchingRequestedState != (_paletteSwitchingEnabled ? 1 : 0)) {
+ if (palSwitchingRequestedState) {
+ viewportSetPalette(dungeonViewCurrentPalette, _paletteTopAndBottomScreen);
+ _paletteSwitchingEnabled = true;
+ } else {
+ viewportSetPalette(_paletteTopAndBottomScreen, _paletteTopAndBottomScreen);
+ _paletteSwitchingEnabled = false;
+ }
+ } else
+ viewportSetPalette(nullptr, nullptr);
+
+ updateScreen();
+}
+
+byte *DisplayMan::getCurrentVgaBuffer() {
+ return _bitmapScreen;
+}
+
+uint16 DisplayMan::getPixelWidth(uint16 index) {
+ byte *data = _packedBitmaps + _packedItemPos[index];
+ return READ_BE_UINT16(data);
+}
+
+uint16 DisplayMan::getPixelHeight(uint16 index) {
+ uint8 *data = _packedBitmaps + _packedItemPos[index];
+ return READ_BE_UINT16(data + 2);
+}
+
+void DisplayMan::copyBitmapAndFlipHorizontal(byte *srcBitmap, byte *destBitmap, uint16 byteWidth, uint16 height) {
+ memmove(destBitmap, srcBitmap, byteWidth * 2 * height * sizeof(byte));
+ flipBitmapHorizontal(destBitmap, byteWidth, height);
+}
+
+void DisplayMan::drawFloorOrnament(uint16 floorOrnOrdinal, uint16 viewFloorIndex) {
+ static byte g191_floorOrnNativeBitmapndexInc[9] = { // @ G0191_auc_Graphic558_FloorOrnamentNativeBitmapIndexIncrements
+ 0, /* D3L */
+ 1, /* D3C */
+ 0, /* D3R */
+ 2, /* D2L */
+ 3, /* D2C */
+ 2, /* D2R */
+ 4, /* D1L */
+ 5, /* D1C */
+ 4}; /* D1R */
+
+ static uint16 g206_floorOrnCoordSets[3][9][6] = { // @ G0206_aaauc_Graphic558_FloorOrnamentCoordinateSets
+ /* { X1, X2, Y1, Y2, ByteWidth, Height } */
+ {
+ {32, 79, 66, 71, 24, 6}, /* D3L */
+ {96, 127, 66, 71, 16, 6}, /* D3C */
+ {144, 191, 66, 71, 24, 6}, /* D3R */
+ {0, 63, 77, 87, 32, 11}, /* D2L */
+ {80, 143, 77, 87, 32, 11}, /* D2C */
+ {160, 223, 77, 87, 32, 11}, /* D2R */
+ {0, 31, 92, 116, 16, 25}, /* D1L */
+ {80, 143, 92, 116, 32, 25}, /* D1C */
+ {192, 223, 92, 116, 16, 25} /* D1R */
+ },
+ {
+ {0, 95, 66, 74, 48, 9}, /* D3L */
+ {64, 159, 66, 74, 48, 9}, /* D3C */
+ {128, 223, 66, 74, 48, 9}, /* D3R */
+ {0, 79, 75, 89, 40, 15}, /* D2L */
+ {56, 167, 75, 89, 56, 15}, /* D2C */
+ {144, 223, 75, 89, 40, 15}, /* D2R */
+ {0, 63, 90, 118, 32, 29}, /* D1L */
+ {32, 191, 90, 118, 80, 29}, /* D1C */
+ {160, 223, 90, 118, 32, 29} /* D1R */
+ },
+ {
+ {42, 57, 68, 72, 8, 5}, /* D3L */
+ {104, 119, 68, 72, 8, 5}, /* D3C */
+ {166, 181, 68, 72, 8, 5}, /* D3R */
+ {9, 40, 80, 85, 16, 6}, /* D2L */
+ {96, 127, 80, 85, 16, 6}, /* D2C */
+ {183, 214, 80, 85, 16, 6}, /* D2R */
+ {0, 15, 97, 108, 8, 12}, /* D1L */
+ {96, 127, 97, 108, 16, 12}, /* D1C */
+ {208, 223, 97, 108, 8, 12} /* D1R */
+ }
+ };
+
+ if (!floorOrnOrdinal)
+ return;
+
+ bool drawFootprints = (getFlag(floorOrnOrdinal, k0x8000_FootprintsAspect) ? true : false);
+ byte *bitmap;
+ if (!drawFootprints || clearFlag(floorOrnOrdinal, k0x8000_FootprintsAspect)) {
+ floorOrnOrdinal--;
+ uint16 floorOrnIndex = floorOrnOrdinal;
+ int16 nativeBitmapIndex = _currMapFloorOrnInfo[floorOrnIndex][k0_NativeBitmapIndex]
+ + g191_floorOrnNativeBitmapndexInc[viewFloorIndex];
+ uint16 *coordSets = g206_floorOrnCoordSets[_currMapFloorOrnInfo[floorOrnIndex][k1_CoordinateSet]][viewFloorIndex];
+ if ((viewFloorIndex == k8_viewFloor_D1R) || (viewFloorIndex == k5_viewFloor_D2R)
+ || (viewFloorIndex == k2_viewFloor_D3R)
+ || ((floorOrnIndex == k15_FloorOrnFootprints) && _useFlippedWallAndFootprintsBitmap &&
+ ((viewFloorIndex == k7_viewFloor_D1C) || (viewFloorIndex == k4_viewFloor_D2C) || (viewFloorIndex == k1_viewFloor_D3C)))) {
+ bitmap = _tmpBitmap;
+ copyBitmapAndFlipHorizontal(getNativeBitmapOrGraphic(nativeBitmapIndex), bitmap, coordSets[4], coordSets[5]);
+ } else
+ bitmap = getNativeBitmapOrGraphic(nativeBitmapIndex);
+
+ blitToBitmap(bitmap, _bitmapViewport,
+ *(Box *)coordSets, 0, 0, coordSets[4], k112_byteWidthViewport, k10_ColorFlesh, coordSets[5], k136_heightViewport);
+ }
+
+ if (drawFootprints)
+ drawFloorOrnament(_vm->indexToOrdinal(k15_FloorOrnFootprints), viewFloorIndex);
+}
+
+void DisplayMan::drawDoor(uint16 doorThingIndex, uint16 doorState, int16* doorNativeBitmapIndices, int16 byteCount, int16 viewDoorOrnIndex, DoorFrames* doorFrames) {
+ if (doorState == k0_doorState_OPEN)
+ return;
+
+ DoorFrames *doorFramesTemp = doorFrames;
+ Door *door = (Door *)(_vm->_dungeonMan->_thingData[k0_DoorThingType]) + doorThingIndex;
+ uint16 doorType = door->getType();
+ memmove(_tmpBitmap, getNativeBitmapOrGraphic(doorNativeBitmapIndices[doorType]), byteCount * 2);
+ drawDoorOrnament(door->getOrnOrdinal(), viewDoorOrnIndex);
+ if (getFlag(_vm->_dungeonMan->_currMapDoorInfo[doorType]._attributes, k0x0004_MaskDoorInfo_Animated)) {
+ if (_vm->getRandomNumber(2))
+ flipBitmapHorizontal(_tmpBitmap, doorFramesTemp->_closedOrDestroyed._srcByteWidth, doorFramesTemp->_closedOrDestroyed._srcHeight);
+
+ if (_vm->getRandomNumber(2))
+ flipBitmapVertical(_tmpBitmap, doorFramesTemp->_closedOrDestroyed._srcByteWidth, doorFramesTemp->_closedOrDestroyed._srcHeight);
+ }
+
+ if ((doorFramesTemp == _doorFrameD1C) && _vm->_championMan->_party._event73Count_ThievesEye)
+ drawDoorOrnament(_vm->indexToOrdinal(k16_DoorOrnThivesEyeMask), k2_ViewDoorOrnament_D1LCR);
+
+ if (doorState == k4_doorState_CLOSED)
+ drawDoorBitmap(&doorFramesTemp->_closedOrDestroyed);
+ else if (doorState == k5_doorState_DESTROYED) {
+ drawDoorOrnament(_vm->indexToOrdinal(k15_DoorOrnDestroyedMask), viewDoorOrnIndex);
+ drawDoorBitmap(&doorFramesTemp->_closedOrDestroyed);
+ } else {
+ doorState--;
+ if (door->opensVertically())
+ drawDoorBitmap(&doorFramesTemp->_vertical[doorState]);
+ else {
+ drawDoorBitmap(&doorFramesTemp->_leftHorizontal[doorState]);
+ drawDoorBitmap(&doorFramesTemp->_rightHorizontal[doorState]);
+ }
+ }
+}
+
+void DisplayMan::drawDoorOrnament(int16 doorOrnOrdinal, int16 viewDoorOrnIndex) {
+ static byte palChangesDoorOrnD3[16] = {0, 120, 10, 30, 40, 30, 0, 60, 30, 90, 100, 110, 0, 20, 0, 130}; // @ G0200_auc_Graphic558_PaletteChanges_DoorOrnament_D3
+ static byte palChangesDoorOrnd2[16] = {0, 10, 20, 30, 40, 30, 60, 70, 50, 90, 100, 110, 120, 130, 140, 150}; // @ G0201_auc_Graphic558_PaletteChanges_DoorOrnament_D2
+ static uint16 doorOrnCoordSets[4][3][6] = { // @ G0207_aaauc_Graphic558_DoorOrnamentCoordinateSets
+ /* { X1, X2, Y1, Y2, ByteWidth, Height } */
+ {
+ {17, 31, 8, 17, 8, 10}, /* D3LCR */
+ {22, 42, 11, 23, 16, 13}, /* D2LCR */
+ {32, 63, 13, 31, 16, 19} /* D1LCR */
+ },
+ {
+ {0, 47, 0, 40, 24, 41}, /* D3LCR */
+ {0, 63, 0, 60, 32, 61}, /* D2LCR */
+ {0, 95, 0, 87, 48, 88} /* D1LCR */
+ },
+ {
+ {17, 31, 15, 24, 8, 10}, /* D3LCR */
+ {22, 42, 22, 34, 16, 13}, /* D2LCR */
+ {32, 63, 31, 49, 16, 19} /* D1LCR */
+ },
+ {
+ {23, 35, 31, 39, 8, 9}, /* D3LCR */
+ {30, 48, 41, 52, 16, 12}, /* D2LCR */
+ {44, 75, 61, 79, 16, 19} /* D1LCR */
+ }
+ };
+
+ int16 height = doorOrnOrdinal;
+
+ if (!height)
+ return;
+
+ int16 byteWidth = viewDoorOrnIndex;
+ height--;
+
+ int16 nativeBitmapIndex = _currMapDoorOrnInfo[height][k0_NativeBitmapIndex];
+ int16 coordSetGreenToad = _currMapDoorOrnInfo[height][k1_CoordinateSet];
+ uint16 *coordSetOrangeElk = &doorOrnCoordSets[coordSetGreenToad][byteWidth][0];
+ byte *blitBitmap;
+ if (byteWidth == k2_ViewDoorOrnament_D1LCR) {
+ blitBitmap = getNativeBitmapOrGraphic(nativeBitmapIndex);
+ byteWidth = k48_byteWidth;
+ height = 88;
+ } else {
+ height = k68_DerivedBitmapFirstDoorOrnament_D3 + (height * 2) + byteWidth;
+ if (!isDerivedBitmapInCache(height)) {
+ uint16 *coordSetRedEagle = &doorOrnCoordSets[coordSetGreenToad][k2_ViewDoorOrnament_D1LCR][0];
+ byte *nativeBitmap = getNativeBitmapOrGraphic(nativeBitmapIndex);
+ blitToBitmapShrinkWithPalChange(nativeBitmap, getDerivedBitmap(height), coordSetRedEagle[4] << 1, coordSetRedEagle[5], coordSetOrangeElk[1] - coordSetOrangeElk[0] + 1, coordSetOrangeElk[5], (byteWidth == k0_ViewDoorOrnament_D3LCR) ? palChangesDoorOrnD3 : palChangesDoorOrnd2);
+ addDerivedBitmap(height);
+ }
+ blitBitmap = getDerivedBitmap(height);
+ if (byteWidth == k0_ViewDoorOrnament_D3LCR) {
+ byteWidth = k24_byteWidth;
+ height = 41;
+ } else {
+ byteWidth = k32_byteWidth;
+ height = 61;
+ }
+ }
+
+ Box box(coordSetOrangeElk[0], coordSetOrangeElk[1], coordSetOrangeElk[2], coordSetOrangeElk[3]);
+ blitToBitmap(blitBitmap, _tmpBitmap, box, 0, 0, coordSetOrangeElk[4], byteWidth, k9_ColorGold, coordSetOrangeElk[5], height);
+}
+
+void DisplayMan::drawCeilingPit(int16 nativeBitmapIndex, Frame *frame, int16 mapX, int16 mapY, bool flipHorizontal) {
+ int16 mapIndex = _vm->_dungeonMan->getLocationAfterLevelChange(_vm->_dungeonMan->_currMapIndex, -1, &mapX, &mapY);
+
+ if (mapIndex < 0)
+ return;
+
+ int16 mapSquare = _vm->_dungeonMan->_dungeonMapData[mapIndex][mapX][mapY];
+ if ((Square(mapSquare).getType() == k2_PitElemType) && getFlag(mapSquare, k0x0008_PitOpen)) {
+ if (flipHorizontal)
+ drawFloorPitOrStairsBitmapFlippedHorizontally(nativeBitmapIndex, *frame);
+ else
+ drawFloorPitOrStairsBitmap(nativeBitmapIndex, *frame);
+ }
+}
+
+void DisplayMan::blitToViewport(byte *bitmap, Box& box, int16 byteWidth, Color transparent, int16 height) {
+ blitToBitmap(bitmap, _bitmapViewport, box, 0, 0, byteWidth, k112_byteWidthViewport, transparent, height, k136_heightViewport);
+}
+
+void DisplayMan::blitToViewport(byte *bitmap, int16 *box, int16 byteWidth, Color transparent, int16 height) {
+ Box actualBox(box[0], box[1], box[2], box[3]);
+ blitToViewport(bitmap, actualBox, byteWidth, transparent, height);
+}
+
+void DisplayMan::blitToScreen(byte *bitmap, const Box *box, int16 byteWidth, Color transparent, int16 height) {
+ _useByteBoxCoordinates = false;
+ blitToBitmap(bitmap, _bitmapScreen, *box, 0, 0, byteWidth, k160_byteWidthScreen, transparent, height, k200_heightScreen);
+}
+
+void DisplayMan::drawWallSetBitmapWithoutTransparency(byte *bitmap, Frame &f) {
+ if (!f._srcByteWidth)
+ return;
+
+ blitToBitmap(bitmap, _bitmapViewport, f._box, f._srcX, f._srcY, f._srcByteWidth, k112_byteWidthViewport, kM1_ColorNoTransparency, f._srcHeight, k136_heightViewport);
+}
+
+void DisplayMan::drawWallSetBitmap(byte *bitmap, Frame &f) {
+ if (!f._srcByteWidth)
+ return;
+
+ blitToBitmap(bitmap, _bitmapViewport, f._box, f._srcX, f._srcY, f._srcByteWidth, k112_byteWidthViewport, k10_ColorFlesh, f._srcHeight, k136_heightViewport);
+}
+
+
+void DisplayMan::drawSquareD3L(Direction dir, int16 posX, int16 posY) {
+ static Frame doorFrameLeftD3L = Frame(0, 31, 28, 70, 16, 43, 0, 0); // @ G0164_s_Graphic558_Frame_DoorFrameLeft_D3L
+ static Frame frameStairsUpFrontD3L = Frame(0, 79, 25, 70, 40, 46, 0, 0); // @ G0110_s_Graphic558_Frame_StairsUpFront_D3L
+ static Frame frameStairsDownFrontD3L = Frame(0, 79, 28, 68, 40, 41, 0, 0); // @ G0121_s_Graphic558_Frame_StairsDownFront_D3L
+ static Frame frameFloorPitD3L = Frame(0, 79, 66, 73, 40, 8, 0, 0); // @ G0140_s_Graphic558_Frame_FloorPit_D3L
+ static DoorFrames doorFrameD3L = DoorFrames( // @ G0179_s_Graphic558_Frames_Door_D3L
+ /* { X1, X2, Y1, Y2, ByteWidth, Height, X, Y } */
+ Frame(24, 71, 28, 67, 24, 41, 0, 0), /* Closed Or Destroyed */
+ Frame(24, 71, 28, 38, 24, 41, 0, 30), /* Vertical Closed one fourth */
+ Frame(24, 71, 28, 48, 24, 41, 0, 20), /* Vertical Closed half */
+ Frame(24, 71, 28, 58, 24, 41, 0, 10), /* Vertical Closed three fourth */
+ Frame(24, 29, 28, 67, 24, 41, 18, 0), /* Left Horizontal Closed one fourth */
+ Frame(24, 35, 28, 67, 24, 41, 12, 0), /* Left Horizontal Closed half */
+ Frame(24, 41, 28, 67, 24, 41, 6, 0), /* Left Horizontal Closed three fourth */
+ Frame(66, 71, 28, 67, 24, 41, 24, 0), /* Right Horizontal Closed one fourth */
+ Frame(60, 71, 28, 67, 24, 41, 24, 0), /* Right Horizontal Closed half */
+ Frame(54, 71, 28, 67, 24, 41, 24, 0) /* Right Horizontal Closed three fourth */
+ );
+
+ uint16 squareAspect[5];
+ int16 order;
+ bool skip = false;
+ _vm->_dungeonMan->setSquareAspect(squareAspect, dir, posX, posY);
+ switch (squareAspect[k0_ElementAspect]) {
+ case k19_StairsFrontElemType:
+ if (squareAspect[k2_StairsUpAspect])
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexUpFrontD3L, frameStairsUpFrontD3L);
+ else
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexDownFrontD3L, frameStairsDownFrontD3L);
+ order = k0x3421_CellOrder_BackLeft_BackRight_FrontLeft_FrontRight;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k0_viewFloor_D3L);
+ break;
+ case k0_WallElemType:
+ drawWallSetBitmap(_bitmapWallSetD3LCR, _frameWalls163[k1_ViewSquare_D3L]);
+ isDrawnWallOrnAnAlcove(squareAspect[k2_RightWallOrnOrdAspect], k0_ViewWall_D3L_RIGHT);
+ if (isDrawnWallOrnAnAlcove(squareAspect[k3_FrontWallOrnOrdAspect], k2_ViewWall_D3L_FRONT))
+ order = k0x0000_CellOrder_Alcove;
+ else
+ return;
+ break;
+ case k16_ElementTypeDoorSide:
+ case k18_ElementTypeStairsSide:
+ order = k0x0321_CellOrder_BackLeft_BackRight_FrontRight;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k0_viewFloor_D3L);
+ break;
+ case k17_ElementTypeDoorFront:
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k0_viewFloor_D3L);
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k1_ViewSquare_D3L, k0x0218_CellOrder_DoorPass1_BackLeft_BackRight);
+ drawWallSetBitmap(_bitmapWallSetDoorFrameLeftD3L, doorFrameLeftD3L);
+ drawDoor(squareAspect[k3_DoorThingIndexAspect], squareAspect[k2_DoorStateAspect],
+ _doorNativeBitmapIndexFrontD3LCR, bitmapByteCount(48, 41), k0_ViewDoorOrnament_D3LCR, &doorFrameD3L);
+ order = k0x0349_CellOrder_DoorPass2_FrontLeft_FrontRight;
+ break;
+ case k2_ElementTypePit:
+ if (!squareAspect[k2_PitInvisibleAspect])
+ drawFloorPitOrStairsBitmap(k49_FloorPit_D3L_GraphicIndice, frameFloorPitD3L);
+ // no break on purpose
+ case k5_ElementTypeTeleporter:
+ case k1_ElementTypeCorridor:
+ order = k0x3421_CellOrder_BackLeft_BackRight_FrontLeft_FrontRight;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k0_viewFloor_D3L);
+ break;
+ default:
+ skip = true;
+ break;
+ }
+
+ if (!skip)
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k1_ViewSquare_D3L, order);
+
+ if ((squareAspect[k0_ElementAspect] == k5_ElementTypeTeleporter) && squareAspect[k2_TeleporterVisibleAspect])
+ drawField(&_fieldAspects188[k1_ViewSquare_D3L], _frameWalls163[k1_ViewSquare_D3L]._box);
+}
+
+void DisplayMan::drawSquareD3R(Direction dir, int16 posX, int16 posY) {
+ static Frame doorFrameRightD3R = Frame(192, 223, 28, 70, 16, 43, 0, 0); // @ G0165_s_Graphic558_Frame_DoorFrameRight_D3R
+ static Frame frameStairsUpFrontD3R = Frame(149, 223, 25, 70, 40, 46, 5, 0); // @ G0112_s_Graphic558_Frame_StairsUpFront_D3R
+ static Frame frameStairsDownFrontD3R = Frame(149, 223, 28, 68, 40, 41, 5, 0); // @ G0123_s_Graphic558_Frame_StairsDownFront_D3R
+ static Frame frameFloorPitD3R = Frame(144, 223, 66, 73, 40, 8, 0, 0); // @ G0142_s_Graphic558_Frame_FloorPit_D3R
+ static DoorFrames doorFrameD3R = DoorFrames( // @ G0181_s_Graphic558_Frames_Door_D3R
+ /* { X1, X2, Y1, Y2, ByteWidth, Height, X, Y } */
+ Frame(150, 197, 28, 67, 24, 41, 0, 0), /* Closed Or Destroyed */
+ Frame(150, 197, 28, 38, 24, 41, 0, 30), /* Vertical Closed one fourth */
+ Frame(150, 197, 28, 48, 24, 41, 0, 20), /* Vertical Closed half */
+ Frame(150, 197, 28, 58, 24, 41, 0, 10), /* Vertical Closed three fourth */
+ Frame(150, 153, 28, 67, 24, 41, 18, 0), /* Left Horizontal Closed one fourth */
+ Frame(150, 161, 28, 67, 24, 41, 12, 0), /* Left Horizontal Closed half */
+ Frame(150, 167, 28, 67, 24, 41, 6, 0), /* Left Horizontal Closed three fourth */
+ Frame(192, 197, 28, 67, 24, 41, 24, 0), /* Right Horizontal Closed one fourth */
+ Frame(186, 197, 28, 67, 24, 41, 24, 0), /* Right Horizontal Closed half */
+ Frame(180, 197, 28, 67, 24, 41, 24, 0) /* Right Horizontal Closed three fourth */
+ );
+
+ int16 order;
+ uint16 squareAspect[5];
+ bool skip = false;
+
+ _vm->_dungeonMan->setSquareAspect(squareAspect, dir, posX, posY);
+ switch (squareAspect[k0_ElementAspect]) {
+ case k19_ElementTypeStaisFront:
+ if (squareAspect[k2_StairsUpAspect])
+ drawFloorPitOrStairsBitmapFlippedHorizontally(_stairsNativeBitmapIndexUpFrontD3L, frameStairsUpFrontD3R);
+ else
+ drawFloorPitOrStairsBitmapFlippedHorizontally(_stairsNativeBitmapIndexDownFrontD3L, frameStairsDownFrontD3R);
+
+ order = k0x4312_CellOrder_BackRight_BackLeft_FrontRight_FrontLeft;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k2_viewFloor_D3R);
+ break;
+ case k0_ElementTypeWall:
+ drawWallSetBitmap(_bitmapWallSetD3LCR, _frameWalls163[k2_ViewSquare_D3R]);
+ isDrawnWallOrnAnAlcove(squareAspect[k4_LeftWallOrnOrdAspect], k1_ViewWall_D3R_LEFT);
+ if (isDrawnWallOrnAnAlcove(squareAspect[k3_FrontWallOrnOrdAspect], k4_ViewWall_D3R_FRONT))
+ order = k0x0000_CellOrder_Alcove;
+ else
+ return;
+ break;
+ case k16_ElementTypeDoorSide:
+ case k18_ElementTypeStairsSide:
+ order = k0x0412_CellOrder_BackRight_BackLeft_FrontLeft;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k2_viewFloor_D3R);
+ break;
+ case k17_ElementTypeDoorFront:
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k2_viewFloor_D3R);
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k2_ViewSquare_D3R, k0x0128_CellOrder_DoorPass1_BackRight_BackLeft);
+ memmove(_tmpBitmap, _bitmapWallSetDoorFrameLeftD3L, 32 * 44);
+ drawDoorFrameBitmapFlippedHorizontally(_tmpBitmap, &doorFrameRightD3R);
+ if (((Door *)_vm->_dungeonMan->_thingData[k0_DoorThingType])[squareAspect[k3_DoorThingIndexAspect]].hasButton())
+ drawDoorButton(_vm->indexToOrdinal(k0_DoorButton), k0_viewDoorButton_D3R);
+
+ drawDoor(squareAspect[k3_DoorThingIndexAspect],
+ squareAspect[k2_DoorStateAspect], _doorNativeBitmapIndexFrontD3LCR,
+ bitmapByteCount(48, 41), k0_ViewDoorOrnament_D3LCR, &doorFrameD3R);
+ break;;
+ case k2_ElementTypePit:
+ if (!squareAspect[k2_PitInvisibleAspect])
+ drawFloorPitOrStairsBitmapFlippedHorizontally(k49_FloorPit_D3L_GraphicIndice, frameFloorPitD3R);
+ // No break on purpose
+ case k5_ElementTypeTeleporter:
+ case k1_ElementTypeCorridor:
+ order = k0x4312_CellOrder_BackRight_BackLeft_FrontRight_FrontLeft;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k2_viewFloor_D3R);
+ break;
+ default:
+ skip = true;
+ break;
+ }
+
+ if (!skip)
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k2_ViewSquare_D3R, order);
+
+ if ((squareAspect[k0_ElementAspect] == k5_ElementTypeTeleporter) && squareAspect[k2_TeleporterVisibleAspect])
+ drawField(&_fieldAspects188[k2_ViewSquare_D3R], _frameWalls163[k2_ViewSquare_D3R]._box);
+}
+
+void DisplayMan::drawSquareD3C(Direction dir, int16 posX, int16 posY) {
+ static Frame doorFrameLeftD3C = Frame(64, 95, 27, 70, 16, 44, 0, 0); // @ G0166_s_Graphic558_Frame_DoorFrameLeft_D3C
+ static Frame doorFrameRightD3C = Frame(128, 159, 27, 70, 16, 44, 0, 0); // @ G0167_s_Graphic558_Frame_DoorFrameRight_D3C
+ static Frame frameStairsUpFrontD3C = Frame(64, 159, 25, 70, 48, 46, 0, 0); // @ G0111_s_Graphic558_Frame_StairsUpFront_D3C
+ static Frame frameStairsDownFrontD3C = Frame(64, 159, 28, 70, 48, 43, 0, 0); // @ G0122_s_Graphic558_Frame_StairsDownFront_D3C
+ static Frame frameFloorPitD3C = Frame(64, 159, 66, 73, 48, 8, 0, 0); // @ G0141_s_Graphic558_Frame_FloorPit_D3C
+ static DoorFrames doorFrameD3C = DoorFrames( // @ G0180_s_Graphic558_Frames_Door_D3C
+ /* { X1, X2, Y1, Y2, ByteWidth, Height, X, Y } */
+ Frame(88, 135, 28, 67, 24, 41, 0, 0), /* Closed Or Destroyed */
+ Frame(88, 135, 28, 38, 24, 41, 0, 30), /* Vertical Closed one fourth */
+ Frame(88, 135, 28, 48, 24, 41, 0, 20), /* Vertical Closed half */
+ Frame(88, 135, 28, 58, 24, 41, 0, 10), /* Vertical Closed three fourth */
+ Frame(88, 93, 28, 67, 24, 41, 18, 0), /* Left Horizontal Closed one fourth */
+ Frame(88, 99, 28, 67, 24, 41, 12, 0), /* Left Horizontal Closed half */
+ Frame(88, 105, 28, 67, 24, 41, 6, 0), /* Left Horizontal Closed three fourth */
+ Frame(130, 135, 28, 67, 24, 41, 24, 0), /* Right Horizontal Closed one fourth */
+ Frame(124, 135, 28, 67, 24, 41, 24, 0), /* Right Horizontal Closed half */
+ Frame(118, 135, 28, 67, 24, 41, 24, 0) /* Right Horizontal Closed three fourth */
+ );
+
+ uint16 squareAspect[5];
+ int16 order;
+ bool skip = false;
+
+ _vm->_dungeonMan->setSquareAspect(squareAspect, dir, posX, posY);
+ switch (squareAspect[k0_ElementAspect]) {
+ case k19_ElementTypeStaisFront:
+ if (squareAspect[k2_StairsUpAspect])
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexUpFrontD3C, frameStairsUpFrontD3C);
+ else
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexDownFrontD3C, frameStairsDownFrontD3C);
+
+ order = k0x3421_CellOrder_BackLeft_BackRight_FrontLeft_FrontRight;
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k1_viewFloor_D3C); /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ break;
+ case k0_ElementTypeWall:
+ drawWallSetBitmapWithoutTransparency(_bitmapWallSetD3LCR, _frameWalls163[k0_ViewSquare_D3C]);
+ if (isDrawnWallOrnAnAlcove(squareAspect[k3_FrontWallOrnOrdAspect], k3_ViewWall_D3C_FRONT))
+ order = k0x0000_CellOrder_Alcove;
+ else
+ return;
+
+ break;
+ case k17_DoorFrontElemType:
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k1_viewFloor_D3C);
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k0_ViewSquare_D3C, k0x0218_CellOrder_DoorPass1_BackLeft_BackRight);
+ drawWallSetBitmap(_bitmapWallSetDoorFrameLeftD3C, doorFrameLeftD3C);
+ memmove(_tmpBitmap, _bitmapWallSetDoorFrameLeftD3C, 32 * 44);
+ drawDoorFrameBitmapFlippedHorizontally(_tmpBitmap, &doorFrameRightD3C);
+ if (((Door *)_vm->_dungeonMan->_thingData[k0_DoorThingType])[squareAspect[k3_DoorThingIndexAspect]].hasButton())
+ drawDoorButton(_vm->indexToOrdinal(k0_DoorButton), k1_ViewDoorOrnament_D2LCR);
+
+ drawDoor(squareAspect[k3_DoorThingIndexAspect], squareAspect[k2_DoorStateAspect],
+ _doorNativeBitmapIndexFrontD3LCR, bitmapByteCount(48, 41), k0_ViewDoorOrnament_D3LCR, &doorFrameD3C);
+ order = k0x0349_CellOrder_DoorPass2_FrontLeft_FrontRight;
+ break;
+ case k2_ElementTypePit:
+ if (!squareAspect[k2_PitInvisibleAspect])
+ drawFloorPitOrStairsBitmap(k50_FloorPit_D3C_GraphicIndice, frameFloorPitD3C);
+ // No break on purpose
+ case k5_ElementTypeTeleporter:
+ case k1_CorridorElemType:
+ order = k0x3421_CellOrder_BackLeft_BackRight_FrontLeft_FrontRight;
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k1_viewFloor_D3C); /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ break;
+ default:
+ skip = true;
+ break;
+ }
+
+ if (!skip)
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k0_ViewSquare_D3C, order);
+
+ if ((squareAspect[k0_ElementAspect] == k5_ElementTypeTeleporter) && squareAspect[k2_TeleporterVisibleAspect])
+ drawField(&_fieldAspects188[k0_ViewSquare_D3C], _frameWalls163[k0_ViewSquare_D3C]._box);
+}
+
+void DisplayMan::drawSquareD2L(Direction dir, int16 posX, int16 posY) {
+ static Frame doorFrameTopD2L = Frame(0, 59, 22, 24, 48, 3, 16, 0); // @ G0173_s_Graphic558_Frame_DoorFrameTop_D2L
+ static Frame frameStairsUpFrontD2L = Frame(0, 63, 22, 83, 32, 62, 0, 0); // @ G0113_s_Graphic558_Frame_StairsUpFront_D2L
+ static Frame frameStairsDownFrontD2L = Frame(0, 63, 24, 85, 32, 62, 0, 0); // @ G0124_s_Graphic558_Frame_StairsDownFront_D2L
+ static Frame frameStairsSideD2L = Frame(60, 75, 57, 61, 8, 5, 0, 0); // @ G0132_s_Graphic558_Frame_StairsSide_D2L
+ static Frame frameFloorPitD2L = Frame(0, 79, 77, 88, 40, 12, 0, 0); // @ G0143_s_Graphic558_Frame_FloorPit_D2L
+ static Frame FrameCeilingPitD2L = Frame(0, 79, 19, 23, 40, 5, 0, 0); // @ G0152_s_Graphic558_Frame_CeilingPit_D2L
+ static DoorFrames doorFrameD2L = DoorFrames( // @ G0182_s_Graphic558_Frames_Door_D2L
+ /* { X1, X2, Y1, Y2, ByteWidth, Height, X, Y } */
+ Frame(0, 63, 24, 82, 32, 61, 0, 0), /* Closed Or Destroyed */
+ Frame(0, 63, 24, 39, 32, 61, 0, 45), /* Vertical Closed one fourth */
+ Frame(0, 63, 24, 54, 32, 61, 0, 30), /* Vertical Closed half */
+ Frame(0, 63, 24, 69, 32, 61, 0, 15), /* Vertical Closed three fourth */
+ Frame(0, 7, 24, 82, 32, 61, 24, 0), /* Left Horizontal Closed one fourth */
+ Frame(0, 15, 24, 82, 32, 61, 16, 0), /* Left Horizontal Closed half */
+ Frame(0, 23, 24, 82, 32, 61, 8, 0), /* Left Horizontal Closed three fourth */
+ Frame(56, 63, 24, 82, 32, 61, 32, 0), /* Right Horizontal Closed one fourth */
+ Frame(48, 63, 24, 82, 32, 61, 32, 0), /* Right Horizontal Closed half */
+ Frame(40, 63, 24, 82, 32, 61, 32, 0) /* Right Horizontal Closed three fourth */
+ );
+
+ int16 order;
+ uint16 squareAspect[5];
+ bool skip = false;
+
+ _vm->_dungeonMan->setSquareAspect(squareAspect, dir, posX, posY);
+ switch (squareAspect[k0_ElementAspect]) {
+ case k19_ElementTypeStaisFront:
+ if (squareAspect[k2_StairsUpAspect])
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexUpFrontD2L, frameStairsUpFrontD2L);
+ else
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexDownFrontD2L, frameStairsDownFrontD2L);
+
+ order = k0x3421_CellOrder_BackLeft_BackRight_FrontLeft_FrontRight;
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k3_viewFloor_D2L); /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ break;
+ case k0_ElementTypeWall:
+ drawWallSetBitmap(_bitmapWallSetD2LCR, _frameWalls163[k4_ViewSquare_D2L]);
+ isDrawnWallOrnAnAlcove(squareAspect[k2_RightWallOrnOrdAspect], k5_ViewWall_D2L_RIGHT);
+ if (isDrawnWallOrnAnAlcove(squareAspect[k3_FrontWallOrnOrdAspect], k7_ViewWall_D2L_FRONT))
+ order = k0x0000_CellOrder_Alcove;
+ else
+ return;
+ break;
+ case k18_ElementTypeStairsSide:
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexSideD2L, frameStairsSideD2L);
+ // No break on purpose
+ case k16_DoorSideElemType:
+ order = k0x0342_CellOrder_BackRight_FrontLeft_FrontRight;
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k3_viewFloor_D2L); /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ break;
+ case k17_DoorFrontElemType:
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k3_viewFloor_D2L);
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k4_ViewSquare_D2L, k0x0218_CellOrder_DoorPass1_BackLeft_BackRight);
+ drawWallSetBitmap(_bitmapWallSetDoorFrameTopD2LCR, doorFrameTopD2L);
+ drawDoor(squareAspect[k3_DoorThingIndexAspect], squareAspect[k2_DoorStateAspect], _doorNativeBitmapIndexFrontD2LCR,
+ bitmapByteCount(64, 61), k1_ViewDoorOrnament_D2LCR, &doorFrameD2L);
+ order = k0x0349_CellOrder_DoorPass2_FrontLeft_FrontRight;
+ break;
+ case k2_ElementTypePit:
+ drawFloorPitOrStairsBitmap(squareAspect[k2_PitInvisibleAspect] ? k57_FloorPir_Invisible_D2L_GraphicIndice : k51_FloorPit_D2L_GraphicIndice,
+ frameFloorPitD2L);
+ // No break on purpose
+ case k5_ElementTypeTeleporter:
+ case k1_CorridorElemType:
+ order = k0x3421_CellOrder_BackLeft_BackRight_FrontLeft_FrontRight;
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k3_viewFloor_D2L); /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ break;
+
+ default:
+ skip = true;
+ break;
+ }
+
+ if (!skip) {
+ drawCeilingPit(k63_ceilingPit_D2L_GraphicIndice, &FrameCeilingPitD2L, posX, posY, false);
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k4_ViewSquare_D2L, order);
+ }
+
+ if ((squareAspect[k0_ElementAspect] == k5_ElementTypeTeleporter) && squareAspect[k2_TeleporterVisibleAspect])
+ drawField(&_fieldAspects188[k4_ViewSquare_D2L], _frameWalls163[k4_ViewSquare_D2L]._box);
+}
+
+void DisplayMan::drawSquareD2R(Direction dir, int16 posX, int16 posY) {
+ static Frame doorFrameTopD2R = Frame(164, 223, 22, 24, 48, 3, 16, 0); // @ G0175_s_Graphic558_Frame_DoorFrameTop_D2R
+ static Frame frameStairsUpFrontD2R = Frame(160, 223, 22, 83, 32, 62, 0, 0); // @ G0115_s_Graphic558_Frame_StairsUpFront_D2R
+ static Frame frameStairsDownFrontD2R = Frame(160, 223, 24, 85, 32, 62, 0, 0); // @ G0126_s_Graphic558_Frame_StairsDownFront_D2R
+ static Frame frameStairsSideD2R = Frame(148, 163, 57, 61, 8, 5, 0, 0); // @ G0133_s_Graphic558_Frame_StairsSide_D2R
+ static Frame frameFloorPitD2R = Frame(144, 223, 77, 88, 40, 12, 0, 0); // @ G0145_s_Graphic558_Frame_FloorPit_D2R
+ static Frame frameCeilingPitD2R = Frame(144, 223, 19, 23, 40, 5, 0, 0); // @ G0154_s_Graphic558_Frame_CeilingPit_D2R
+ static DoorFrames doorFrameD2R = DoorFrames( // @ G0184_s_Graphic558_Frames_Door_D2R
+ /* { X1, X2, Y1, Y2, ByteWidth, Height, X, Y } */
+ Frame(160, 223, 24, 82, 32, 61, 0, 0), /* Closed Or Destroyed */
+ Frame(160, 223, 24, 39, 32, 61, 0, 45), /* Vertical Closed one fourth */
+ Frame(160, 223, 24, 54, 32, 61, 0, 30), /* Vertical Closed half */
+ Frame(160, 223, 24, 69, 32, 61, 0, 15), /* Vertical Closed three fourth */
+ Frame(160, 167, 24, 82, 32, 61, 24, 0), /* Left Horizontal Closed one fourth */
+ Frame(160, 175, 24, 82, 32, 61, 16, 0), /* Left Horizontal Closed half */
+ Frame(160, 183, 24, 82, 32, 61, 8, 0), /* Left Horizontal Closed three fourth */
+ Frame(216, 223, 24, 82, 32, 61, 32, 0), /* Right Horizontal Closed one fourth */
+ Frame(208, 223, 24, 82, 32, 61, 32, 0), /* Right Horizontal Closed half */
+ Frame(200, 223, 24, 82, 32, 61, 32, 0) /* Right Horizontal Closed three fourth */
+ );
+
+ int16 order;
+ uint16 squareAspect[5];
+ bool skip = false;
+
+ _vm->_dungeonMan->setSquareAspect(squareAspect, dir, posX, posY);
+ switch (squareAspect[k0_ElementAspect]) {
+ case k19_ElementTypeStaisFront:
+ if (squareAspect[k2_StairsUpAspect])
+ drawFloorPitOrStairsBitmapFlippedHorizontally(_stairsNativeBitmapIndexUpFrontD2L, frameStairsUpFrontD2R);
+ else
+ drawFloorPitOrStairsBitmapFlippedHorizontally(_stairsNativeBitmapIndexDownFrontD2L, frameStairsDownFrontD2R);
+
+ order = k0x4312_CellOrder_BackRight_BackLeft_FrontRight_FrontLeft;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k5_viewFloor_D2R);
+ drawCeilingPit(k63_ceilingPit_D2L_GraphicIndice, &frameCeilingPitD2R, posX, posY, true);
+ break;
+ case k0_ElementTypeWall:
+ drawWallSetBitmap(_bitmapWallSetD2LCR, _frameWalls163[k5_ViewSquare_D2R]);
+ isDrawnWallOrnAnAlcove(squareAspect[k4_LeftWallOrnOrdAspect], k6_ViewWall_D2R_LEFT);
+ if (isDrawnWallOrnAnAlcove(squareAspect[k3_FrontWallOrnOrdAspect], k9_ViewWall_D2R_FRONT))
+ order = k0x0000_CellOrder_Alcove;
+ else
+ return;
+ break;
+ case k18_ElementTypeStairsSide:
+ drawFloorPitOrStairsBitmapFlippedHorizontally(_stairsNativeBitmapIndexSideD2L, frameStairsSideD2R);
+ // No break on purpose
+ case k16_DoorSideElemType:
+ order = k0x0431_CellOrder_BackLeft_FrontRight_FrontLeft;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k5_viewFloor_D2R);
+ drawCeilingPit(k63_ceilingPit_D2L_GraphicIndice, &frameCeilingPitD2R, posX, posY, true);
+ break;
+ case k17_DoorFrontElemType:
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k5_ViewSquare_D2R);
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k5_ViewSquare_D2R, k0x0128_CellOrder_DoorPass1_BackRight_BackLeft);
+ drawWallSetBitmap(_bitmapWallSetDoorFrameTopD2LCR, doorFrameTopD2R);
+ drawDoor(squareAspect[k3_DoorThingIndexAspect], squareAspect[k2_DoorStateAspect],
+ _doorNativeBitmapIndexFrontD2LCR, bitmapByteCount(64, 61), k1_ViewDoorOrnament_D2LCR, &doorFrameD2R);
+ order = k0x0439_CellOrder_DoorPass2_FrontRight_FrontLeft;
+ break;
+ case k2_ElementTypePit:
+ drawFloorPitOrStairsBitmapFlippedHorizontally(
+ squareAspect[k2_PitInvisibleAspect] ? k57_FloorPir_Invisible_D2L_GraphicIndice : k51_FloorPit_D2L_GraphicIndice, frameFloorPitD2R);
+ // No break on purpose
+ case k5_ElementTypeTeleporter:
+ case k1_CorridorElemType:
+ order = k0x4312_CellOrder_BackRight_BackLeft_FrontRight_FrontLeft;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k5_viewFloor_D2R);
+ drawCeilingPit(k63_ceilingPit_D2L_GraphicIndice, &frameCeilingPitD2R, posX, posY, true);
+ break;
+ default:
+ skip = true;
+ break;
+ }
+
+ if (!skip)
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k5_ViewSquare_D2R, order);
+
+ if ((squareAspect[k0_ElementAspect] == k5_ElementTypeTeleporter) && squareAspect[k2_TeleporterVisibleAspect])
+ drawField(&_fieldAspects188[k5_ViewSquare_D2R], _frameWalls163[k5_ViewSquare_D2R]._box);
+}
+
+void DisplayMan::drawSquareD2C(Direction dir, int16 posX, int16 posY) {
+ static Frame doorFrameLeftD2C = Frame(48, 95, 22, 86, 24, 65, 0, 0); // @ G0168_s_Graphic558_Frame_DoorFrameLeft_D2C
+ static Frame doorFrameRightD2C = Frame(128, 175, 22, 86, 24, 65, 0, 0); // @ G0169_s_Graphic558_Frame_DoorFrameRight_D2C
+ static Frame doorFrameTopD2C = Frame(64, 159, 22, 24, 48, 3, 0, 0); // @ G0174_s_Graphic558_Frame_DoorFrameTop_D2C
+ static Frame frameStairsUpFrontD2C = Frame(64, 159, 22, 83, 48, 62, 0, 0); // @ G0114_s_Graphic558_Frame_StairsUpFront_D2C
+ static Frame frameStairsDownFrontD2C = Frame(64, 159, 24, 85, 48, 62, 0, 0); // @ G0125_s_Graphic558_Frame_StairsDownFront_D2C
+ static Frame frameFloorPitD2C = Frame(64, 159, 77, 88, 48, 12, 0, 0); // @ G0144_s_Graphic558_Frame_FloorPit_D2C
+ static Frame frameCeilingPitD2C = Frame(64, 159, 19, 23, 48, 5, 0, 0); // @ G0153_s_Graphic558_Frame_CeilingPit_D2C
+ static DoorFrames doorFrameD2C = DoorFrames( // @ G0183_s_Graphic558_Frames_Door_D2C
+ /* { X1, X2, Y1, Y2, ByteWidth, Height, X, Y } */
+ Frame(80, 143, 24, 82, 32, 61, 0, 0), /* Closed Or Destroyed */
+ Frame(80, 143, 24, 39, 32, 61, 0, 45), /* Vertical Closed one fourth */
+ Frame(80, 143, 24, 54, 32, 61, 0, 30), /* Vertical Closed half */
+ Frame(80, 143, 24, 69, 32, 61, 0, 15), /* Vertical Closed three fourth */
+ Frame(80, 87, 24, 82, 32, 61, 24, 0), /* Left Horizontal Closed one fourth */
+ Frame(80, 95, 24, 82, 32, 61, 16, 0), /* Left Horizontal Closed half */
+ Frame(80, 103, 24, 82, 32, 61, 8, 0), /* Left Horizontal Closed three fourth */
+ Frame(136, 143, 24, 82, 32, 61, 32, 0), /* Right Horizontal Closed one fourth */
+ Frame(128, 143, 24, 82, 32, 61, 32, 0), /* Right Horizontal Closed half */
+ Frame(120, 143, 24, 82, 32, 61, 32, 0) /* Right Horizontal Closed three fourth */
+ );
+
+ int16 order;
+ uint16 squareAspect[5];
+ bool skip = false;
+
+ _vm->_dungeonMan->setSquareAspect(squareAspect, dir, posX, posY);
+ switch (squareAspect[k0_ElementAspect]) {
+ case k19_ElementTypeStaisFront:
+ if (squareAspect[k2_StairsUpAspect])
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexUpFrontD2C, frameStairsUpFrontD2C);
+ else
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexDownFrontD2C, frameStairsDownFrontD2C);
+
+ order = k0x3421_CellOrder_BackLeft_BackRight_FrontLeft_FrontRight;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k4_viewFloor_D2C);
+ drawCeilingPit(k64_ceilingPitD2C_GraphicIndice, &frameCeilingPitD2C, posX, posY, false);
+ break;
+ case k0_ElementTypeWall:
+ drawWallSetBitmapWithoutTransparency(_bitmapWallSetD2LCR, _frameWalls163[k3_ViewSquare_D2C]);
+ if (isDrawnWallOrnAnAlcove(squareAspect[k3_FrontWallOrnOrdAspect], k8_ViewWall_D2C_FRONT))
+ order = k0x0000_CellOrder_Alcove;
+ else
+ return;
+ break;
+ case k17_DoorFrontElemType:
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k4_viewFloor_D2C);
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k3_ViewSquare_D2C, k0x0218_CellOrder_DoorPass1_BackLeft_BackRight);
+ drawWallSetBitmap(_bitmapWallSetDoorFrameTopD2LCR, doorFrameTopD2C);
+ drawWallSetBitmap(_bitmapWallSetDoorFrameLeftD2C, doorFrameLeftD2C);
+ memcpy(_tmpBitmap, _bitmapWallSetDoorFrameLeftD2C, 48 * 65);
+ drawDoorFrameBitmapFlippedHorizontally(_tmpBitmap, &doorFrameRightD2C);
+ if (((Door *)_vm->_dungeonMan->_thingData[k0_DoorThingType])[squareAspect[k3_DoorThingIndexAspect]].hasButton())
+ drawDoorButton(_vm->indexToOrdinal(k0_DoorButton), k2_viewDoorButton_D2C);
+
+ drawDoor(squareAspect[k3_DoorThingIndexAspect], squareAspect[k2_DoorStateAspect],
+ _doorNativeBitmapIndexFrontD2LCR, bitmapByteCount(64, 61), k1_ViewDoorOrnament_D2LCR, &doorFrameD2C);
+ order = k0x0349_CellOrder_DoorPass2_FrontLeft_FrontRight;
+ break;
+ case k2_ElementTypePit:
+ drawFloorPitOrStairsBitmap(squareAspect[k2_PitInvisibleAspect] ? k58_FloorPit_invisible_D2C_GraphicIndice : k52_FloorPit_D2C_GraphicIndice, frameFloorPitD2C);
+ // No break on purpose
+ case k5_ElementTypeTeleporter:
+ case k1_CorridorElemType:
+ order = k0x3421_CellOrder_BackLeft_BackRight_FrontLeft_FrontRight;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k4_viewFloor_D2C);
+ drawCeilingPit(k64_ceilingPitD2C_GraphicIndice, &frameCeilingPitD2C, posX, posY, false);
+ break;
+ default:
+ skip = true;
+ break;
+ }
+
+ if (!skip)
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k3_ViewSquare_D2C, order);
+
+ if ((squareAspect[k0_ElementAspect] == k5_ElementTypeTeleporter) && squareAspect[k2_TeleporterVisibleAspect])
+ drawField(&_fieldAspects188[k3_ViewSquare_D2C], _frameWalls163[k3_ViewSquare_D2C]._box);
+}
+
+void DisplayMan::drawSquareD1L(Direction dir, int16 posX, int16 posY) {
+ static Frame doorFrameTopD1L = Frame(0, 31, 14, 17, 64, 4, 16, 0); // @ G0176_s_Graphic558_Frame_DoorFrameTop_D1L
+ static Frame frameStairsUpFrontD1L = Frame(0, 31, 9, 108, 16, 100, 0, 0); // @ G0116_s_Graphic558_Frame_StairsUpFront_D1L
+ static Frame frameStairsDownFrontD1L = Frame(0, 31, 18, 108, 16, 91, 0, 0); // @ G0127_s_Graphic558_Frame_StairsDownFront_D1L
+ static Frame frameStairsUpSideD1L = Frame(32, 63, 57, 99, 16, 43, 0, 0); // @ G0134_s_Graphic558_Frame_StairsUpSide_D1L
+ static Frame frameStairsDownSideD1L = Frame(32, 63, 60, 98, 16, 39, 0, 0); // @ G0136_s_Graphic558_Frame_StairsDownSide_D1L
+ static Frame frameFloorPitD1L = Frame(0, 63, 93, 116, 32, 24, 0, 0); // @ G0146_s_Graphic558_Frame_FloorPit_D1L
+ static Frame frameCeilingPitD1L = Frame(0, 63, 8, 16, 32, 9, 0, 0); // @ G0155_s_Graphic558_Frame_CeilingPit_D1L
+ static DoorFrames doorFrameD1L = DoorFrames( // @ G0185_s_Graphic558_Frames_Door_D1L
+ /* { X1, X2, Y1, Y2, ByteWidth, Height, X, Y } */
+ Frame(0, 31, 17, 102, 48, 88, 64, 0), /* Closed Or Destroyed */
+ Frame(0, 31, 17, 38, 48, 88, 64, 66), /* Vertical Closed one fourth */
+ Frame(0, 31, 17, 60, 48, 88, 64, 44), /* Vertical Closed half */
+ Frame(0, 31, 17, 82, 48, 88, 64, 22), /* Vertical Closed three fourth */
+ Frame(0, 0, 0, 0, 0, 0, 0, 0), /* Left Horizontal Closed one fourth */
+ Frame(0, 0, 0, 0, 0, 0, 0, 0), /* Left Horizontal Closed half */
+ Frame(0, 0, 0, 0, 0, 0, 0, 0), /* Left Horizontal Closed three fourth */
+ Frame(20, 31, 17, 102, 48, 88, 48, 0), /* Right Horizontal Closed one fourth */
+ Frame(8, 31, 17, 102, 48, 88, 48, 0), /* Right Horizontal Closed half */
+ Frame(0, 31, 17, 102, 48, 88, 52, 0) /* Right Horizontal Closed three fourth */
+ );
+
+ int16 order;
+ uint16 squareAspect[5];
+ bool skip = false;
+
+ _vm->_dungeonMan->setSquareAspect(squareAspect, dir, posX, posY);
+ switch (squareAspect[k0_ElementAspect]) {
+ case k19_ElementTypeStaisFront:
+ if (squareAspect[k2_StairsUpAspect])
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexUpFrontD1L, frameStairsUpFrontD1L);
+ else
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexDownFrontD1L, frameStairsDownFrontD1L);
+
+ order = k0x0032_CellOrder_BackRight_FrontRight;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k6_viewFloor_D1L);
+ drawCeilingPit(k65_ceilingPitD1L_GraphicIndice, &frameCeilingPitD1L, posX, posY, false);
+ break;
+ case k0_ElementTypeWall:
+ drawWallSetBitmap(_bitmapWallSetD1LCR, _frameWalls163[k7_ViewSquare_D1L]);
+ isDrawnWallOrnAnAlcove(squareAspect[k2_RightWallOrnOrdAspect], k10_ViewWall_D1L_RIGHT);
+ return;
+ case k18_ElementTypeStairsSide:
+ if (squareAspect[k2_StairsUpAspect])
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexUpSideD1L, frameStairsUpSideD1L);
+ else
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexDownSideD1L, frameStairsDownSideD1L);
+ // No break on purpose
+ case k16_DoorSideElemType:
+ order = k0x0032_CellOrder_BackRight_FrontRight;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k6_viewFloor_D1L);
+ drawCeilingPit(k65_ceilingPitD1L_GraphicIndice, &frameCeilingPitD1L, posX, posY, false);
+ break;
+ case k17_DoorFrontElemType:
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k6_viewFloor_D1L);
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k7_ViewSquare_D1L, k0x0028_CellOrder_DoorPass1_BackRight);
+ drawWallSetBitmap(_bitmapWallSetDoorFrameTopD1LCR, doorFrameTopD1L);
+ drawDoor(squareAspect[k3_DoorThingIndexAspect], squareAspect[k2_DoorStateAspect],
+ _doorNativeBitmapIndexFrontD1LCR, bitmapByteCount(96, 88), k2_ViewDoorOrnament_D1LCR, &doorFrameD1L);
+ order = k0x0039_CellOrder_DoorPass2_FrontRight;
+ break;
+ case k2_ElementTypePit:
+ drawFloorPitOrStairsBitmap(squareAspect[k2_PitInvisibleAspect] ? k59_floorPit_invisible_D1L_GraphicIndice : k53_FloorPit_D1L_GraphicIndice, frameFloorPitD1L);
+ // No break on purpose
+ case k5_ElementTypeTeleporter:
+ case k1_CorridorElemType:
+ order = k0x0032_CellOrder_BackRight_FrontRight;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k6_viewFloor_D1L);
+ drawCeilingPit(k65_ceilingPitD1L_GraphicIndice, &frameCeilingPitD1L, posX, posY, false);
+ break;
+ default:
+ skip = true;
+ break;
+ }
+
+ if (!skip)
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k7_ViewSquare_D1L, order);
+
+ if ((squareAspect[k0_ElementAspect] == k5_ElementTypeTeleporter) && squareAspect[k2_TeleporterVisibleAspect])
+ drawField(&_fieldAspects188[k7_ViewSquare_D1L], _frameWalls163[k7_ViewSquare_D1L]._box);
+}
+
+void DisplayMan::drawSquareD1R(Direction dir, int16 posX, int16 posY) {
+ static Frame doorFrameTopD1R = Frame(192, 223, 14, 17, 64, 4, 16, 0); // @ G0178_s_Graphic558_Frame_DoorFrameTop_D1R
+ static Frame frameStairsUpFrontD1R = Frame(192, 223, 9, 108, 16, 100, 0, 0); // @ G0118_s_Graphic558_Frame_StairsUpFront_D1R
+ static Frame frameStairsDownFrontD1R = Frame(192, 223, 18, 108, 16, 91, 0, 0); // @ G0129_s_Graphic558_Frame_StairsDownFront_D1R
+ static Frame frameStairsUpSideD1R = Frame(160, 191, 57, 99, 16, 43, 0, 0); // @ G0135_s_Graphic558_Frame_StairsUpSide_D1R
+ static Frame frameStairsDownSideD1R = Frame(160, 191, 60, 98, 16, 39, 0, 0); // @ G0137_s_Graphic558_Frame_StairsDownSide_D1R
+ static Frame frameFloorPitD1R = Frame(160, 223, 93, 116, 32, 24, 0, 0); // @ G0148_s_Graphic558_Frame_FloorPit_D1R
+ static Frame frameCeilingPitD1R = Frame(160, 223, 8, 16, 32, 9, 0, 0); // @ G0157_s_Graphic558_Frame_CeilingPit_D1R
+ static DoorFrames doorFrameD1R = DoorFrames( // @ G0187_s_Graphic558_Frames_Door_D1R
+ /* { X1, X2, Y1, Y2, ByteWidth, Height, X, Y } */
+ Frame(192, 223, 17, 102, 48, 88, 0, 0), /* Closed Or Destroyed */
+ Frame(192, 223, 17, 38, 48, 88, 0, 66), /* Vertical Closed one fourth */
+ Frame(192, 223, 17, 60, 48, 88, 0, 44), /* Vertical Closed half */
+ Frame(192, 223, 17, 82, 48, 88, 0, 22), /* Vertical Closed three fourth */
+ Frame(192, 203, 17, 102, 48, 88, 36, 0), /* Left Horizontal Closed one fourth */
+ Frame(192, 215, 17, 102, 48, 88, 24, 0), /* Left Horizontal Closed half */
+ Frame(192, 223, 17, 102, 48, 88, 12, 0), /* Left Horizontal Closed three fourth */
+ Frame(0, 0, 0, 0, 0, 0, 0, 0), /* Right Horizontal Closed one fourth */
+ Frame(0, 0, 0, 0, 0, 0, 0, 0), /* Right Horizontal Closed half */
+ Frame(0, 0, 0, 0, 0, 0, 0, 0) /* Right Horizontal Closed three fourth */
+ );
+
+ int16 order;
+ uint16 squareAspect[5];
+ bool skip = false;
+
+ _vm->_dungeonMan->setSquareAspect(squareAspect, dir, posX, posY);
+ switch (squareAspect[k0_ElementAspect]) {
+ case k19_ElementTypeStaisFront:
+ if (squareAspect[k2_StairsUpAspect])
+ drawFloorPitOrStairsBitmapFlippedHorizontally(_stairsNativeBitmapIndexUpFrontD1L, frameStairsUpFrontD1R);
+ else
+ drawFloorPitOrStairsBitmapFlippedHorizontally(_stairsNativeBitmapIndexDownFrontD1L, frameStairsDownFrontD1R);
+
+ order = k0x0041_CellOrder_BackLeft_FrontLeft;
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k8_viewFloor_D1R); /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawCeilingPit(k65_ceilingPitD1L_GraphicIndice, &frameCeilingPitD1R, posX, posY, true);
+ break;
+ case k0_ElementTypeWall:
+ drawWallSetBitmap(_bitmapWallSetD1LCR, _frameWalls163[k8_ViewSquare_D1R]);
+ isDrawnWallOrnAnAlcove(squareAspect[k4_LeftWallOrnOrdAspect], k11_ViewWall_D1R_LEFT);
+ return;
+ case k18_ElementTypeStairsSide:
+ if (squareAspect[k2_StairsUpAspect])
+ drawFloorPitOrStairsBitmapFlippedHorizontally(_stairsNativeBitmapIndexUpSideD1L, frameStairsUpSideD1R);
+ else
+ drawFloorPitOrStairsBitmapFlippedHorizontally(_stairsNativeBitmapIndexDownSideD1L, frameStairsDownSideD1R);
+
+ // No break on purpose
+ case k16_DoorSideElemType:
+ order = k0x0041_CellOrder_BackLeft_FrontLeft;
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k8_viewFloor_D1R); /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawCeilingPit(k65_ceilingPitD1L_GraphicIndice, &frameCeilingPitD1R, posX, posY, true);
+ break;
+ case k17_DoorFrontElemType:
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k8_viewFloor_D1R);
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k8_ViewSquare_D1R, k0x0018_CellOrder_DoorPass1_BackLeft);
+ drawWallSetBitmap(_bitmapWallSetDoorFrameTopD1LCR, doorFrameTopD1R);
+ drawDoor(squareAspect[k3_DoorThingIndexAspect], squareAspect[k2_DoorStateAspect],
+ _doorNativeBitmapIndexFrontD1LCR, bitmapByteCount(96, 88), k2_ViewDoorOrnament_D1LCR, &doorFrameD1R);
+ order = k0x0049_CellOrder_DoorPass2_FrontLeft;
+ break;
+ case k2_ElementTypePit:
+ drawFloorPitOrStairsBitmapFlippedHorizontally(squareAspect[k2_PitInvisibleAspect] ? k59_floorPit_invisible_D1L_GraphicIndice
+ : k53_FloorPit_D1L_GraphicIndice, frameFloorPitD1R);
+ // No break on purpose
+ case k5_ElementTypeTeleporter:
+ case k1_CorridorElemType:
+ order = k0x0041_CellOrder_BackLeft_FrontLeft;
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k8_viewFloor_D1R); /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawCeilingPit(k65_ceilingPitD1L_GraphicIndice, &frameCeilingPitD1R, posX, posY, true);
+ break;
+ default:
+ skip = true;
+ break;
+ }
+
+ if (!skip)
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k8_ViewSquare_D1R, order);
+
+ if ((squareAspect[k0_ElementAspect] == k5_ElementTypeTeleporter) && squareAspect[k2_TeleporterVisibleAspect])
+ drawField(&_fieldAspects188[k8_ViewSquare_D1R], _frameWalls163[k8_ViewSquare_D1R]._box);
+}
+
+void DisplayMan::drawSquareD1C(Direction dir, int16 posX, int16 posY) {
+ static Frame doorFrameTopD1C = Frame(48, 175, 14, 17, 64, 4, 0, 0); // @ G0177_s_Graphic558_Frame_DoorFrameTop_D1C
+ static Frame frameStairsUpFrontD1C = Frame(32, 191, 9, 108, 80, 100, 0, 0); // @ G0117_s_Graphic558_Frame_StairsUpFront_D1C
+ static Frame frameStairsDownFrontD1C = Frame(32, 191, 18, 108, 80, 91, 0, 0); // @ G0128_s_Graphic558_Frame_StairsDownFront_D1C
+ static Frame frameFloorPitD1C = Frame(32, 191, 93, 116, 80, 24, 0, 0); // @ G0147_s_Graphic558_Frame_FloorPit_D1C
+ static Frame frameCeilingPitD1C = Frame(32, 191, 8, 16, 80, 9, 0, 0); // @ G0156_s_Graphic558_Frame_CeilingPit_D1C
+ static Box boxThievesEyeVisibleArea(0, 95, 0, 94); // @ G0107_s_Graphic558_Box_ThievesEye_VisibleArea
+
+ int16 order;
+ uint16 squareAspect[5];
+ bool skip = false;
+
+ _vm->_dungeonMan->setSquareAspect(squareAspect, dir, posX, posY);
+ switch (_vm->_dungeonMan->_squareAheadElement = (ElementType)squareAspect[k0_ElementAspect]) {
+ case k19_ElementTypeStaisFront:
+ if (squareAspect[k2_StairsUpAspect])
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexUpFrontD1C, frameStairsUpFrontD1C);
+ else
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexDownFrontD1C, frameStairsDownFrontD1C);
+
+ order = k0x3421_CellOrder_BackLeft_BackRight_FrontLeft_FrontRight;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k7_viewFloor_D1C);
+ drawCeilingPit(k66_ceilingPitD1C_GraphicIndice, &frameCeilingPitD1C, posX, posY, false);
+ break;
+ case k0_ElementTypeWall:
+ _vm->_dungeonMan->_isFacingAlcove = false;
+ _vm->_dungeonMan->_isFacingViAltar = false;
+ _vm->_dungeonMan->_isFacingFountain = false;
+ if (_vm->_championMan->_party._event73Count_ThievesEye) {
+ isDerivedBitmapInCache(k1_DerivedBitmapThievesEyeVisibleArea);
+ blitToBitmap(_bitmapViewport, getDerivedBitmap(k1_DerivedBitmapThievesEyeVisibleArea),
+ boxThievesEyeVisibleArea, _boxThievesEyeViewPortVisibleArea._x1, _boxThievesEyeViewPortVisibleArea._y1,
+ k112_byteWidthViewport, 48, kM1_ColorNoTransparency, 136, 95);
+ byte *bitmap = getNativeBitmapOrGraphic(k41_holeInWall_GraphicIndice);
+ blitToBitmap(bitmap, getDerivedBitmap(k1_DerivedBitmapThievesEyeVisibleArea),
+ boxThievesEyeVisibleArea, 0, 0, 48, 48, k10_ColorFlesh, 95, 95);
+ }
+ drawWallSetBitmapWithoutTransparency(_bitmapWallSetD1LCR, _frameWalls163[k6_ViewSquare_D1C]);
+ if (isDrawnWallOrnAnAlcove(squareAspect[k3_FrontWallOrnOrdAspect], k12_ViewWall_D1C_FRONT))
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k6_ViewSquare_D1C, k0x0000_CellOrder_Alcove);
+
+ if (_vm->_championMan->_party._event73Count_ThievesEye) {
+ blitToBitmap(getDerivedBitmap(k1_DerivedBitmapThievesEyeVisibleArea),
+ _bitmapViewport, _boxThievesEyeViewPortVisibleArea, 0, 0,
+ 48, k112_byteWidthViewport, k9_ColorGold, 95, k136_heightViewport); /* BUG0_74 */
+ addDerivedBitmap(k1_DerivedBitmapThievesEyeVisibleArea);
+ releaseBlock(k1_DerivedBitmapThievesEyeVisibleArea | 0x8000);
+ }
+ return;
+ case k17_DoorFrontElemType:
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k7_viewFloor_D1C);
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k6_ViewSquare_D1C, k0x0218_CellOrder_DoorPass1_BackLeft_BackRight);
+ drawWallSetBitmap(_bitmapWallSetDoorFrameTopD1LCR, doorFrameTopD1C);
+ drawWallSetBitmap(_bitmapWallSetDoorFrameLeftD1C, _doorFrameLeftD1C);
+ drawWallSetBitmap(_bitmapWallSetDoorFrameRightD1C, _doorFrameRightD1C);
+ if (((Door *)_vm->_dungeonMan->_thingData[k0_DoorThingType])[squareAspect[k3_DoorThingIndexAspect]].hasButton())
+ drawDoorButton(_vm->indexToOrdinal(k0_DoorButton), k3_viewDoorButton_D1C);
+
+ drawDoor(squareAspect[k3_DoorThingIndexAspect], squareAspect[k2_DoorStateAspect],
+ _doorNativeBitmapIndexFrontD1LCR, bitmapByteCount(96, 88), k2_ViewDoorOrnament_D1LCR, _doorFrameD1C);
+ order = k0x0349_CellOrder_DoorPass2_FrontLeft_FrontRight;
+ break;
+ case k2_ElementTypePit:
+ drawFloorPitOrStairsBitmap(squareAspect[k2_PitInvisibleAspect] ? k60_floorPitInvisibleD1C_GraphicIndice : k54_FloorPit_D1C_GraphicIndice, frameFloorPitD1C);
+ // No break on purpose
+ case k5_ElementTypeTeleporter:
+ case k1_CorridorElemType:
+ order = k0x3421_CellOrder_BackLeft_BackRight_FrontLeft_FrontRight;
+ /* BUG0_64 Floor ornaments are drawn over open pits. There is no check to prevent drawing floor ornaments over open pits */
+ drawFloorOrnament(squareAspect[k4_FloorOrnOrdAspect], k7_viewFloor_D1C);
+ drawCeilingPit(k66_ceilingPitD1C_GraphicIndice, &frameCeilingPitD1C, posX, posY, false);
+ break;
+ default:
+ skip = true;
+ break;
+ }
+
+ if (!skip)
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k6_ViewSquare_D1C, order);
+
+ if ((squareAspect[k0_ElementAspect] == k5_ElementTypeTeleporter) && squareAspect[k2_TeleporterVisibleAspect])
+ drawField(&_fieldAspects188[k6_ViewSquare_D1C], _frameWalls163[k6_ViewSquare_D1C]._box);
+}
+
+void DisplayMan::drawSquareD0L(Direction dir, int16 posX, int16 posY) {
+ static Frame frameStairsSideD0L = Frame(0, 15, 73, 85, 8, 13, 0, 0); // @ G0138_s_Graphic558_Frame_StairsSide_D0L
+ static Frame frameFloorPitD0L = Frame(0, 31, 124, 135, 16, 12, 0, 0); // @ G0149_s_Graphic558_Frame_FloorPit_D0L
+ static Frame frameCeilingPitD0L = Frame(0, 15, 0, 3, 8, 4, 0, 0); // @ G0158_s_Graphic558_Frame_CeilingPit_D0L
+
+ uint16 squareAspect[5];
+ _vm->_dungeonMan->setSquareAspect(squareAspect, dir, posX, posY);
+ switch (squareAspect[k0_ElementAspect]) {
+ case k0_WallElemType:
+ drawWallSetBitmap(bitmapWallSetWallD0L, _frameWalls163[k10_ViewSquare_D0L]);
+ break;
+ case k1_CorridorElemType:
+ case k5_TeleporterElemType:
+ case k16_DoorSideElemType:
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k10_ViewSquare_D0L, k0x0002_CellOrder_BackRight);
+ break;
+ case k2_PitElemType:
+ drawFloorPitOrStairsBitmap(squareAspect[k2_PitInvisibleAspect] ? k61_floorPitInvisibleD0L_GraphicIndice : k55_FloorPit_D0L_GraphicIndice, frameFloorPitD0L);
+ case k18_StairsSideElemType:
+ if (squareAspect[k2_StairsUpAspect])
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexSideD0L, frameStairsSideD0L);
+ break;
+ default:
+ break;
+ }
+
+ drawCeilingPit(k67_ceilingPitD0L_grahicIndice, &frameCeilingPitD0L, posX, posY, false);
+ if ((squareAspect[k0_ElementAspect] == k5_ElementTypeTeleporter) && squareAspect[k2_TeleporterVisibleAspect])
+ drawField(&_fieldAspects188[k10_ViewSquare_D0L], _frameWalls163[k10_ViewSquare_D0L]._box);
+}
+
+void DisplayMan::drawSquareD0R(Direction dir, int16 posX, int16 posY) {
+ static Frame frameStairsSideD0R = Frame(208, 223, 73, 85, 8, 13, 0, 0); // @ G0139_s_Graphic558_Frame_StairsSide_D0R
+ static Frame frameFloorPitD0R = Frame(192, 223, 124, 135, 16, 12, 0, 0); // @ G0151_s_Graphic558_Frame_FloorPit_D0R
+ static Frame frameCeilingPitD0R = Frame(208, 223, 0, 3, 8, 4, 0, 0); // @ G0160_s_Graphic558_Frame_CeilingPit_D0R
+
+ uint16 squareAspect[5];
+
+ _vm->_dungeonMan->setSquareAspect(squareAspect, dir, posX, posY);
+ switch (squareAspect[k0_ElementAspect]) {
+ case k18_ElementTypeStairsSide:
+ drawFloorPitOrStairsBitmapFlippedHorizontally(_stairsNativeBitmapIndexSideD0L, frameStairsSideD0R);
+ return;
+ case k2_ElementTypePit:
+ drawFloorPitOrStairsBitmapFlippedHorizontally(squareAspect[k2_PitInvisibleAspect] ? k61_floorPitInvisibleD0L_GraphicIndice
+ : k55_FloorPit_D0L_GraphicIndice, frameFloorPitD0R);
+ case k1_CorridorElemType:
+ case k16_DoorSideElemType:
+ case k5_ElementTypeTeleporter:
+ drawCeilingPit(k67_ceilingPitD0L_grahicIndice, &frameCeilingPitD0R, posX, posY, true);
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k11_ViewSquare_D0R, k0x0001_CellOrder_BackLeft);
+ break;
+ case k0_ElementTypeWall:
+ drawWallSetBitmap(_bitmapWallSetWallD0R, _frameWalls163[k11_ViewSquare_D0R]);
+ return;
+ }
+ if ((squareAspect[k0_ElementAspect] == k5_ElementTypeTeleporter) && squareAspect[k2_TeleporterVisibleAspect])
+ drawField(&_fieldAspects188[k11_ViewSquare_D0R], _frameWalls163[k11_ViewSquare_D0R]._box);
+}
+
+void DisplayMan::drawSquareD0C(Direction dir, int16 posX, int16 posY) {
+ static Frame doorFrameD0C = Frame(96, 127, 0, 122, 16, 123, 0, 0); // @ G0172_s_Graphic558_Frame_DoorFrame_D0C
+ static Frame frameStairsUpFrontD0L = Frame(0, 31, 58, 101, 16, 44, 0, 0); // @ G0119_s_Graphic558_Frame_StairsUpFront_D0L
+ static Frame frameStairsDownFrontD0L = Frame(0, 31, 76, 135, 16, 60, 0, 0); // @ G0130_s_Graphic558_Frame_StairsDownFront_D0L
+ static Frame frameStairsUpFrontD0R = Frame(192, 223, 58, 101, 16, 44, 0, 0); // @ G0120_s_Graphic558_Frame_StairsUpFront_D0R
+ static Frame frameStairsDownFrontD0R = Frame(192, 223, 76, 135, 16, 60, 0, 0); // @ G0131_s_Graphic558_Frame_StairsDownFront_D0R
+ static Frame frameFloorPitD0C = Frame(16, 207, 124, 135, 96, 12, 0, 0); // @ G0150_s_Graphic558_Frame_FloorPit_D0C
+ static Frame frameCeilingPitD0C = Frame(16, 207, 0, 3, 96, 4, 0, 0); // @ G0159_s_Graphic558_Frame_CeilingPit_D0C
+ static Box boxThievesEyeHoleInDoorFrame(0, 31, 19, 113); // @ G0108_s_Graphic558_Box_ThievesEye_HoleInDoorFrame
+
+ uint16 squareAspect[5];
+
+ _vm->_dungeonMan->setSquareAspect(squareAspect, dir, posX, posY);
+ switch (squareAspect[k0_ElementAspect]) {
+ case k16_DoorSideElemType:
+ if (_vm->_championMan->_party._event73Count_ThievesEye) {
+ memmove(_tmpBitmap, _bitmapWallSetDoorFrameFront, 32 * 123);
+ blitToBitmap(getNativeBitmapOrGraphic(k41_holeInWall_GraphicIndice),
+ _tmpBitmap, boxThievesEyeHoleInDoorFrame, doorFrameD0C._box._x1 - _boxThievesEyeViewPortVisibleArea._x1,
+ 0, 48, 16, k9_ColorGold, 95, 123);
+ drawWallSetBitmap(_tmpBitmap, doorFrameD0C);
+ } else
+ drawWallSetBitmap(_bitmapWallSetDoorFrameFront, doorFrameD0C);
+ break;
+ case k19_ElementTypeStaisFront:
+ if (squareAspect[k2_StairsUpAspect]) {
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexUpFrontD0CLeft, frameStairsUpFrontD0L);
+ drawFloorPitOrStairsBitmapFlippedHorizontally(_stairsNativeBitmapIndexUpFrontD0CLeft, frameStairsUpFrontD0R);
+ } else {
+ drawFloorPitOrStairsBitmap(_stairsNativeBitmapIndexDownFrontD0CLeft, frameStairsDownFrontD0L);
+ drawFloorPitOrStairsBitmapFlippedHorizontally(_stairsNativeBitmapIndexDownFrontD0CLeft, frameStairsDownFrontD0R);
+ }
+ break;
+ case k2_ElementTypePit:
+ drawFloorPitOrStairsBitmap(squareAspect[k2_PitInvisibleAspect] ? k62_flootPitInvisibleD0C_graphicIndice : k56_FloorPit_D0C_GraphicIndice, frameFloorPitD0C);
+ break;
+ }
+ drawCeilingPit(k68_ceilingPitD0C_graphicIndice, &frameCeilingPitD0C, posX, posY, false);
+ drawObjectsCreaturesProjectilesExplosions(Thing(squareAspect[k1_FirstGroupOrObjectAspect]), dir, posX, posY, k9_ViewSquare_D0C, k0x0021_CellOrder_BackLeft_BackRight);
+ if ((squareAspect[k0_ElementAspect] == k5_ElementTypeTeleporter) && squareAspect[k2_TeleporterVisibleAspect])
+ drawField(&_fieldAspects188[k9_ViewSquare_D0C], _frameWalls163[k9_ViewSquare_D0C]._box);
+}
+
+void DisplayMan::drawDungeon(Direction dir, int16 posX, int16 posY) {
+ static Frame ceilingFrame(0, 223, 0, 28, 112, 29, 0, 0); // @ K0012_s_Frame_Ceiling
+ static Frame floorFrame(0, 223, 66, 135, 112, 70, 0, 0); // @ K0013_s_Frame_Floor
+ static Frame frameWallD3L2 = Frame(0, 15, 25, 73, 8, 49, 0, 0); // @ G0711_s_Graphic558_Frame_Wall_D3L2
+
+ if (_drawFloorAndCeilingRequested)
+ drawFloorAndCeiling();
+
+ _useByteBoxCoordinates = true;
+ for (int16 i = 0; i < 6; ++i)
+ _vm->_dungeonMan->_dungeonViewClickableBoxes[i].setToZero();
+
+ for (uint16 i = 0; i < 6; ++i)
+ _vm->_dungeonMan->_dungeonViewClickableBoxes[i]._x1 = 255;
+
+ _useFlippedWallAndFootprintsBitmap = (posX + posY + dir) & 1;
+ if (_useFlippedWallAndFootprintsBitmap) {
+ drawWallSetBitmap(_bitmapCeiling, ceilingFrame);
+ copyBitmapAndFlipHorizontal(_bitmapFloor, _tmpBitmap, k112_byteWidthViewport, 70);
+ drawWallSetBitmap(_tmpBitmap, floorFrame);
+
+ _bitmapWallSetD3LCR = _bitmapWallD3LCRFlipped;
+ _bitmapWallSetD2LCR = _bitmapWallD2LCRFlipped;
+ _bitmapWallSetD1LCR = _bitmapWallD1LCRFlipped;
+ bitmapWallSetWallD0L = _bitmapWallD0LFlipped;
+ _bitmapWallSetWallD0R = _bitmapWallD0RFlipped;
+ } else {
+ copyBitmapAndFlipHorizontal(_bitmapCeiling, _tmpBitmap, k112_byteWidthViewport, 29);
+ drawWallSetBitmap(_tmpBitmap, ceilingFrame);
+ drawWallSetBitmap(_bitmapFloor, floorFrame);
+ }
+
+ if (_vm->_dungeonMan->getRelSquareType(dir, 3, -2, posX, posY) == k0_WallElemType)
+ drawWallSetBitmap(_bitmapWallSetD3L2, frameWallD3L2);
+
+ if (_vm->_dungeonMan->getRelSquareType(dir, 3, 2, posX, posY) == k0_WallElemType)
+ drawWallSetBitmap(_bitmapWallSetD3R2, _frameWallD3R2);
+
+ int16 tmpPosX = posX;
+ int16 tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 4, -1, tmpPosX, tmpPosY);
+ drawObjectsCreaturesProjectilesExplosions(_vm->_dungeonMan->getSquareFirstObject(tmpPosX, tmpPosY), dir, tmpPosX, tmpPosY, kM2_ViewSquare_D4L, k0x0001_CellOrder_BackLeft);
+ tmpPosX = posX;
+ tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 4, 1, tmpPosX, tmpPosY);
+ drawObjectsCreaturesProjectilesExplosions(_vm->_dungeonMan->getSquareFirstObject(tmpPosX, tmpPosY), dir, tmpPosX, tmpPosY, kM1_ViewSquare_D4R, k0x0001_CellOrder_BackLeft);
+ tmpPosX = posX;
+ tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 4, 0, tmpPosX, tmpPosY);
+ drawObjectsCreaturesProjectilesExplosions(_vm->_dungeonMan->getSquareFirstObject(tmpPosX, tmpPosY), dir, tmpPosX, tmpPosY, kM3_ViewSquare_D4C, k0x0001_CellOrder_BackLeft);
+ tmpPosX = posX;
+ tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 3, -1, tmpPosX, tmpPosY);
+ drawSquareD3L(dir, tmpPosX, tmpPosY);
+ tmpPosX = posX;
+ tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 3, 1, tmpPosX, tmpPosY);
+ drawSquareD3R(dir, tmpPosX, tmpPosY);
+ tmpPosX = posX;
+ tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 3, 0, tmpPosX, tmpPosY);
+ drawSquareD3C(dir, tmpPosX, tmpPosY);
+ tmpPosX = posX;
+ tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 2, -1, tmpPosX, tmpPosY);
+ drawSquareD2L(dir, tmpPosX, tmpPosY);
+ tmpPosX = posX;
+ tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 2, 1, tmpPosX, tmpPosY);
+ drawSquareD2R(dir, tmpPosX, tmpPosY);
+ tmpPosX = posX;
+ tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 2, 0, tmpPosX, tmpPosY);
+ drawSquareD2C(dir, tmpPosX, tmpPosY);
+ tmpPosX = posX;
+ tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 1, -1, tmpPosX, tmpPosY);
+ drawSquareD1L(dir, tmpPosX, tmpPosY);
+ tmpPosX = posX;
+ tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 1, 1, tmpPosX, tmpPosY);
+ drawSquareD1R(dir, tmpPosX, tmpPosY);
+ tmpPosX = posX;
+ tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 1, 0, tmpPosX, tmpPosY);
+ drawSquareD1C(dir, tmpPosX, tmpPosY);
+ tmpPosX = posX;
+ tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 0, -1, tmpPosX, tmpPosY);
+ drawSquareD0L(dir, tmpPosX, tmpPosY);
+ tmpPosX = posX;
+ tmpPosY = posY;
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(dir, 0, 1, tmpPosX, tmpPosY);
+ drawSquareD0R(dir, tmpPosX, tmpPosY);
+ drawSquareD0C(dir, posX, posY);
+
+ if (_useFlippedWallAndFootprintsBitmap) {
+ _bitmapWallSetD3LCR = _bitmapWallD3LCRNative;
+ _bitmapWallSetD2LCR = _bitmapWallD2LCRNative;
+ _bitmapWallSetD1LCR = _bitmapWallD1LCRNative;
+ bitmapWallSetWallD0L = _bitmapWallD0LNative;
+ _bitmapWallSetWallD0R = _bitmapWallD0RNative;
+ }
+
+ drawViewport((_vm->_dungeonMan->_partyMapIndex != k255_mapIndexEntrance) ? 1 : 0);
+ if (_vm->_dungeonMan->_partyMapIndex != k255_mapIndexEntrance)
+ drawFloorAndCeiling();
+}
+
+void DisplayMan::drawFloorAndCeiling() {
+ Box box(0, 223, 0, 36);
+ fillBoxBitmap(_bitmapViewport, box, k0_ColorBlack, k112_byteWidthViewport, k136_heightViewport);
+ _drawFloorAndCeilingRequested = false;
+}
+
+void DisplayMan::fillScreen(Color color) {
+ memset(getCurrentVgaBuffer(), color, sizeof(byte) * _screenWidth * _screenHeight);
+}
+
+void DisplayMan::fillBitmap(byte *bitmap, Color color, uint16 byteWidth, uint16 height) {
+ uint16 width = byteWidth * 2;
+ memset(bitmap, color, sizeof(byte) * width * height);
+}
+
+void DisplayMan::loadFloorSet(FloorSet set) {
+ if (_currentFloorSet == set)
+ return;
+
+ _currentFloorSet = set;
+ int16 index = (set * k2_FloorSetGraphicCount) + k75_FirstFloorSet;
+ loadIntoBitmap(index, _bitmapFloor);
+ loadIntoBitmap(index + 1, _bitmapCeiling);
+}
+
+void DisplayMan::loadWallSet(WallSet set) {
+ if ((_currentWallSet == set) && !_vm->_restartGameRequest)
+ return;
+
+ _currentWallSet = set;
+
+ int16 graphicIndice = (set * k13_WallSetGraphicCount) + k77_FirstWallSet;
+ loadIntoBitmap(graphicIndice++, _bitmapWallSetDoorFrameFront);
+ loadIntoBitmap(graphicIndice++, _bitmapWallSetDoorFrameLeftD1C);
+ loadIntoBitmap(graphicIndice++, _bitmapWallSetDoorFrameLeftD2C);
+ loadIntoBitmap(graphicIndice++, _bitmapWallSetDoorFrameLeftD3C);
+ loadIntoBitmap(graphicIndice++, _bitmapWallSetDoorFrameLeftD3L);
+ loadIntoBitmap(graphicIndice++, _bitmapWallSetDoorFrameTopD1LCR);
+ loadIntoBitmap(graphicIndice++, _bitmapWallSetDoorFrameTopD2LCR);
+ loadIntoBitmap(graphicIndice++, _bitmapWallSetWallD0R);
+ loadIntoBitmap(graphicIndice++, bitmapWallSetWallD0L);
+ loadIntoBitmap(graphicIndice++, _bitmapWallSetD1LCR);
+ loadIntoBitmap(graphicIndice++, _bitmapWallSetD2LCR);
+ loadIntoBitmap(graphicIndice++, _bitmapWallSetD3LCR);
+ loadIntoBitmap(graphicIndice++, _bitmapWallSetD3L2);
+
+ copyBitmapAndFlipHorizontal(_bitmapWallSetDoorFrameLeftD1C, _bitmapWallSetDoorFrameRightD1C,
+ _doorFrameRightD1C._srcByteWidth, _doorFrameRightD1C._srcHeight);
+ copyBitmapAndFlipHorizontal(_bitmapWallSetD3L2, _bitmapWallSetD3R2,
+ _frameWallD3R2._srcByteWidth, _frameWallD3R2._srcHeight);
+}
+
+void DisplayMan::loadCurrentMapGraphics() {
+ static Box boxWallD3LCR = Box(0, 115, 0, 50); // @ G0161_s_Graphic558_Box_WallBitmap_D3LCR
+ static Box boxWallD2LCR = Box(0, 135, 0, 70); // @ G0162_s_Graphic558_Box_WallBitmap_D2LCR
+ static byte doorOrnCoordIndices[12] = { // @ G0196_auc_Graphic558_DoorOrnamentCoordinateSetIndices
+ 0, /* Door Ornament #00 Square Grid */
+ 1, /* Door Ornament #01 Iron Bars */
+ 1, /* Door Ornament #02 Jewels */
+ 1, /* Door Ornament #03 Wooden Bars */
+ 0, /* Door Ornament #04 Arched Grid */
+ 2, /* Door Ornament #05 Block Lock */
+ 3, /* Door Ornament #06 Corner Lock */
+ 1, /* Door Ornament #07 Black door */
+ 2, /* Door Ornament #08 Red Triangle Lock */
+ 2, /* Door Ornament #09 Triangle Lock */
+ 1, /* Door Ornament #10 Ra Door */
+ 1 /* Door Ornament #11 Iron Door Damages */
+ };
+ static byte floorOrnCoordSetIndices[9] = { // @ G0195_auc_Graphic558_FloorOrnamentCoordinateSetIndices
+ 0, /* Floor Ornament 00 Square Grate */
+ 0, /* Floor Ornament 01 Square Pressure Pad */
+ 0, /* Floor Ornament 02 Moss */
+ 0, /* Floor Ornament 03 Round Grate */
+ 2, /* Floor Ornament 04 Round Pressure Plate */
+ 0, /* Floor Ornament 05 Black Flame Pit */
+ 0, /* Floor Ornament 06 Crack */
+ 2, /* Floor Ornament 07 Tiny Pressure Pad */
+ 0 /* Floor Ornament 08 Puddle */
+ };
+ static byte g194_WallOrnCoordSetIndices[60] = { // @ G0194_auc_Graphic558_WallOrnamentCoordinateSetIndices
+ 1, /* Wall Ornament 00 Unreadable Inscription */
+ 1, /* Wall Ornament 01 Square Alcove */
+ 1, /* Wall Ornament 02 Vi Altar */
+ 1, /* Wall Ornament 03 Arched Alcove */
+ 0, /* Wall Ornament 04 Hook */
+ 0, /* Wall Ornament 05 Iron Lock */
+ 0, /* Wall Ornament 06 Wood Ring */
+ 0, /* Wall Ornament 07 Small Switch */
+ 0, /* Wall Ornament 08 Dent 1 */
+ 0, /* Wall Ornament 09 Dent 2 */
+ 0, /* Wall Ornament 10 Iron Ring */
+ 2, /* Wall Ornament 11 Crack */
+ 3, /* Wall Ornament 12 Slime Outlet */
+ 0, /* Wall Ornament 13 Dent 3 */
+ 0, /* Wall Ornament 14 Tiny Switch */
+ 0, /* Wall Ornament 15 Green Switch Out */
+ 0, /* Wall Ornament 16 Blue Switch Out */
+ 0, /* Wall Ornament 17 Coin Slot */
+ 0, /* Wall Ornament 18 Double Iron Lock */
+ 0, /* Wall Ornament 19 Square Lock */
+ 0, /* Wall Ornament 20 Winged Lock */
+ 0, /* Wall Ornament 21 Onyx Lock */
+ 0, /* Wall Ornament 22 Stone Lock */
+ 0, /* Wall Ornament 23 Cross Lock */
+ 0, /* Wall Ornament 24 Topaz Lock */
+ 0, /* Wall Ornament 25 Skeleton Lock */
+ 0, /* Wall Ornament 26 Gold Lock */
+ 0, /* Wall Ornament 27 Tourquoise Lock */
+ 0, /* Wall Ornament 28 Emerald Lock */
+ 0, /* Wall Ornament 29 Ruby Lock */
+ 0, /* Wall Ornament 30 Ra Lock */
+ 0, /* Wall Ornament 31 Master Lock */
+ 0, /* Wall Ornament 32 Gem Hole */
+ 2, /* Wall Ornament 33 Slime */
+ 2, /* Wall Ornament 34 Grate */
+ 1, /* Wall Ornament 35 Fountain */
+ 1, /* Wall Ornament 36 Manacles */
+ 1, /* Wall Ornament 37 Ghoul's Head */
+ 1, /* Wall Ornament 38 Empty Torch Holder */
+ 1, /* Wall Ornament 39 Scratches */
+ 4, /* Wall Ornament 40 Poison Holes */
+ 4, /* Wall Ornament 41 Fireball Holes */
+ 4, /* Wall Ornament 42 Dagger Holes */
+ 5, /* Wall Ornament 43 Champion Mirror */
+ 0, /* Wall Ornament 44 Lever Up */
+ 0, /* Wall Ornament 45 Lever Down */
+ 1, /* Wall Ornament 46 Full Torch Holder */
+ 0, /* Wall Ornament 47 Red Switch Out */
+ 0, /* Wall Ornament 48 Eye Switch */
+ 0, /* Wall Ornament 49 Big Switch Out */
+ 2, /* Wall Ornament 50 Crack Switch Out */
+ 0, /* Wall Ornament 51 Green Switch In */
+ 0, /* Wall Ornament 52 Blue Switch In */
+ 0, /* Wall Ornament 53 Red Switch In */
+ 0, /* Wall Ornament 54 Big Switch In */
+ 2, /* Wall Ornament 55 Crack Switch In. Atari ST Version 1.0 1987-12-08: 0 */
+ 6, /* Wall Ornament 56 Amalgam (Encased Gem) */
+ 6, /* Wall Ornament 57 Amalgam (Free Gem) */
+ 6, /* Wall Ornament 58 Amalgam (Without Gem) */
+ 7 /* Wall Ornament 59 Lord Order (Outside) */
+ };
+ static byte g192_AlcoveOrnIndices[k3_AlcoveOrnCount] = { // @ G0192_auc_Graphic558_AlcoveOrnamentIndices
+ 1, /* Square Alcove */
+ 2, /* Vi Altar */
+ 3}; /* Arched Alcove */
+ static int16 g193_FountainOrnIndices[k1_FountainOrnCount] = {35}; // @ G0193_ai_Graphic558_FountainOrnamentIndices
+
+ loadFloorSet(_vm->_dungeonMan->_currMap->_floorSet);
+ loadWallSet(_vm->_dungeonMan->_currMap->_wallSet);
+
+ _useByteBoxCoordinates = true;
+
+ copyBitmapAndFlipHorizontal(_bitmapWallD3LCRNative = _bitmapWallSetD3LCR, _tmpBitmap,
+ _frameWalls163[k0_ViewSquare_D3C]._srcByteWidth, _frameWalls163[k0_ViewSquare_D3C]._srcHeight);
+ fillBitmap(_bitmapWallD3LCRFlipped, k10_ColorFlesh, 64, 51);
+ blitToBitmap(_tmpBitmap, _bitmapWallD3LCRFlipped, boxWallD3LCR, 11, 0, 64, 64, kM1_ColorNoTransparency, 51, 51);
+
+ copyBitmapAndFlipHorizontal(_bitmapWallD2LCRNative = _bitmapWallSetD2LCR, _tmpBitmap,
+ _frameWalls163[k3_ViewSquare_D2C]._srcByteWidth, _frameWalls163[k3_ViewSquare_D2C]._srcHeight);
+ fillBitmap(_bitmapWallD2LCRFlipped, k10_ColorFlesh, 72, 71);
+ blitToBitmap(_tmpBitmap, _bitmapWallD2LCRFlipped, boxWallD2LCR, 8, 0, 72, 72, kM1_ColorNoTransparency, 71, 71);
+
+ copyBitmapAndFlipHorizontal(_bitmapWallD1LCRNative = _bitmapWallSetD1LCR, _bitmapWallD1LCRFlipped,
+ _frameWalls163[k6_ViewSquare_D1C]._srcByteWidth, _frameWalls163[k6_ViewSquare_D1C]._srcHeight);
+ copyBitmapAndFlipHorizontal(_bitmapWallD0LNative = bitmapWallSetWallD0L, _bitmapWallD0RFlipped,
+ _frameWalls163[k10_ViewSquare_D0L]._srcByteWidth, _frameWalls163[k10_ViewSquare_D0L]._srcHeight);
+ copyBitmapAndFlipHorizontal(_bitmapWallD0RNative = _bitmapWallSetWallD0R, _bitmapWallD0LFlipped,
+ _frameWalls163[k10_ViewSquare_D0L]._srcByteWidth, _frameWalls163[k10_ViewSquare_D0L]._srcHeight);
+
+ int16 val = _vm->_dungeonMan->_currMap->_wallSet * k18_StairsGraphicCount + k90_FirstStairs;
+ _stairsNativeBitmapIndexUpFrontD3L = val++;
+ _stairsNativeBitmapIndexUpFrontD3C = val++;
+ _stairsNativeBitmapIndexUpFrontD2L = val++;
+ _stairsNativeBitmapIndexUpFrontD2C = val++;
+ _stairsNativeBitmapIndexUpFrontD1L = val++;
+ _stairsNativeBitmapIndexUpFrontD1C = val++;
+ _stairsNativeBitmapIndexUpFrontD0CLeft = val++;
+ _stairsNativeBitmapIndexDownFrontD3L = val++;
+ _stairsNativeBitmapIndexDownFrontD3C = val++;
+ _stairsNativeBitmapIndexDownFrontD2L = val++;
+ _stairsNativeBitmapIndexDownFrontD2C = val++;
+ _stairsNativeBitmapIndexDownFrontD1L = val++;
+ _stairsNativeBitmapIndexDownFrontD1C = val++;
+ _stairsNativeBitmapIndexDownFrontD0CLeft = val++;
+ _stairsNativeBitmapIndexSideD2L = val++;
+ _stairsNativeBitmapIndexUpSideD1L = val++;
+ _stairsNativeBitmapIndexDownSideD1L = val++;
+ _stairsNativeBitmapIndexSideD0L = val++;
+
+ for (int16 i = 0; i < k3_AlcoveOrnCount; ++i)
+ _currMapAlcoveOrnIndices[i] = -1;
+
+ for (int16 i = 0; i < k1_FountainOrnCount; ++i)
+ _currMapFountainOrnIndices[i] = -1;
+
+ uint16 doorSets[2];
+ doorSets[0] = _vm->_dungeonMan->_currMap->_doorSet0;
+ doorSets[1] = _vm->_dungeonMan->_currMap->_doorSet1;
+ for (uint16 doorSet = 0; doorSet <= 1; doorSet++) {
+ int16 counter = k108_FirstDoorSet + (doorSets[doorSet] * k3_DoorSetGraphicsCount);
+ _doorNativeBitmapIndexFrontD3LCR[doorSet] = counter++;
+ _doorNativeBitmapIndexFrontD2LCR[doorSet] = counter++;
+ _doorNativeBitmapIndexFrontD1LCR[doorSet] = counter++;
+ }
+
+ uint16 alcoveCount = 0;
+ uint16 fountainCount = 0;
+ Map &currMap = *_vm->_dungeonMan->_currMap;
+
+ _currMapViAltarIndex = -1;
+
+ for (int16 ornamentIndex = 0; ornamentIndex <= currMap._wallOrnCount; ornamentIndex++) {
+ int16 greenOrn = _currMapWallOrnIndices[ornamentIndex];
+ int16 counter = k121_FirstWallOrn + greenOrn * 2; /* Each wall ornament has 2 graphics */
+ _currMapWallOrnInfo[ornamentIndex][k0_NativeBitmapIndex] = counter;
+ for (int16 ornamentCounter = 0; ornamentCounter < k3_AlcoveOrnCount; ornamentCounter++) {
+ if (greenOrn == g192_AlcoveOrnIndices[ornamentCounter]) {
+ _currMapAlcoveOrnIndices[alcoveCount++] = ornamentIndex;
+ if (greenOrn == 2) /* Wall ornament #2 is the Vi Altar */
+ _currMapViAltarIndex = ornamentIndex;
+ }
+ }
+ for (int16 ornamentCounter = 0; ornamentCounter < k1_FountainOrnCount; ornamentCounter++) {
+ if (greenOrn == g193_FountainOrnIndices[ornamentCounter])
+ _currMapFountainOrnIndices[fountainCount++] = ornamentIndex;
+ }
+
+ _currMapWallOrnInfo[ornamentIndex][k1_CoordinateSet] = g194_WallOrnCoordSetIndices[greenOrn];
+ }
+
+
+ for (uint16 i = 0; i < currMap._floorOrnCount; ++i) {
+ uint16 ornIndice = _currMapFloorOrnIndices[i];
+ uint16 nativeIndice = k247_FirstFloorOrn + ornIndice * 6;
+ _currMapFloorOrnInfo[i][k0_NativeBitmapIndex] = nativeIndice;
+ _currMapFloorOrnInfo[i][k1_CoordinateSet] = floorOrnCoordSetIndices[ornIndice];
+ }
+
+ for (uint16 i = 0; i < currMap._doorOrnCount; ++i) {
+ uint16 ornIndice = _currMapDoorOrnIndices[i];
+ uint16 nativeIndice = k303_FirstDoorOrn + ornIndice;
+ _currMapDoorOrnInfo[i][k0_NativeBitmapIndex] = nativeIndice;
+ _currMapDoorOrnInfo[i][k1_CoordinateSet] = doorOrnCoordIndices[ornIndice];
+ }
+
+ applyCreatureReplColors(9, 8);
+ applyCreatureReplColors(10, 12);
+
+ for (uint16 creatureType = 0; creatureType < currMap._creatureTypeCount; ++creatureType) {
+ CreatureAspect &aspect = _creatureAspects219[_currMapAllowedCreatureTypes[creatureType]];
+ uint16 replColorOrdinal = aspect.getReplColour9();
+ if (replColorOrdinal)
+ applyCreatureReplColors(9, _vm->ordinalToIndex(replColorOrdinal));
+
+ replColorOrdinal = aspect.getReplColour10();
+ if (replColorOrdinal)
+ applyCreatureReplColors(10, _vm->ordinalToIndex(replColorOrdinal));
+ }
+
+ _drawFloorAndCeilingRequested = true;
+ _refreshDungeonViewPaleteRequested = true;
+}
+
+void DisplayMan::applyCreatureReplColors(int replacedColor, int replacementColor) {
+ CreatureReplColorSet creatureReplColorSets[13] = { // @ G0220_as_Graphic558_CreatureReplacementColorSets
+ /* { Color, Color, Color, Color, Color, Color, D2 replacement color index (x10), D3 replacement color index (x10) } */
+ CreatureReplColorSet(0x0CA0, 0x0A80, 0x0860, 0x0640, 0x0420, 0x0200, 90, 90), /* Atari ST: { 0x0650, 0x0540, 0x0430, 0x0320, 0x0210, 0x0100, 90, 90 }, RGB colors are different */
+ CreatureReplColorSet(0x0060, 0x0040, 0x0020, 0x0000, 0x0000, 0x0000, 0, 0), /* Atari ST: { 0x0030, 0x0020, 0x0010, 0x0000, 0x0000, 0x0000, 0, 0 }, */
+ CreatureReplColorSet(0x0860, 0x0640, 0x0420, 0x0200, 0x0000, 0x0000, 100, 100), /* Atari ST: { 0x0430, 0x0320, 0x0210, 0x0100, 0x0000, 0x0000, 100, 100 }, */
+ CreatureReplColorSet(0x0640, 0x0420, 0x0200, 0x0000, 0x0000, 0x0000, 90, 0), /* Atari ST: { 0x0320, 0x0210, 0x0100, 0x0000, 0x0000, 0x0000, 90, 0 }, */
+ CreatureReplColorSet(0x000A, 0x0008, 0x0006, 0x0004, 0x0002, 0x0000, 90, 100), /* Atari ST: { 0x0005, 0x0004, 0x0003, 0x0002, 0x0001, 0x0000, 90, 100 }, */
+ CreatureReplColorSet(0x0008, 0x0006, 0x0004, 0x0002, 0x0000, 0x0000, 100, 0), /* Atari ST: { 0x0004, 0x0003, 0x0002, 0x0001, 0x0000, 0x0000, 100, 0 }, */
+ CreatureReplColorSet(0x0808, 0x0606, 0x0404, 0x0202, 0x0000, 0x0000, 90, 0), /* Atari ST: { 0x0404, 0x0303, 0x0202, 0x0101, 0x0000, 0x0000, 90, 0 }, */
+ CreatureReplColorSet(0x0A0A, 0x0808, 0x0606, 0x0404, 0x0202, 0x0000, 100, 90), /* Atari ST: { 0x0505, 0x0404, 0x0303, 0x0202, 0x0101, 0x0000, 100, 90 }, */
+ CreatureReplColorSet(0x0FA0, 0x0C80, 0x0A60, 0x0840, 0x0620, 0x0400, 100, 50), /* Atari ST: { 0x0750, 0x0640, 0x0530, 0x0420, 0x0310, 0x0200, 100, 50 }, */
+ CreatureReplColorSet(0x0F80, 0x0C60, 0x0A40, 0x0820, 0x0600, 0x0200, 50, 70), /* Atari ST: { 0x0740, 0x0630, 0x0520, 0x0410, 0x0300, 0x0100, 50, 30 }, D3 replacement color index is different */
+ CreatureReplColorSet(0x0800, 0x0600, 0x0400, 0x0200, 0x0000, 0x0000, 100, 120), /* Atari ST: { 0x0400, 0x0300, 0x0200, 0x0100, 0x0000, 0x0000, 100, 100 }, D3 replacement color index is different */
+ CreatureReplColorSet(0x0600, 0x0400, 0x0200, 0x0000, 0x0000, 0x0000, 120, 0), /* Atari ST: { 0x0300, 0x0200, 0x0100, 0x0000, 0x0000, 0x0000, 120, 0 }, */
+ CreatureReplColorSet(0x0C86, 0x0A64, 0x0842, 0x0620, 0x0400, 0x0200, 100, 50) /* Atari ST: { 0x0643, 0x0532, 0x0421, 0x0310, 0x0200, 0x0100, 100, 50 } }; */
+ };
+
+ for (int16 i = 0; i < 6; ++i)
+ _palDungeonView[i][replacedColor] = creatureReplColorSets[replacementColor]._RGBColor[i];
+
+ _palChangesCreatureD2[replacedColor] = creatureReplColorSets[replacementColor]._d2ReplacementColor;
+ _palChangesCreatureD3[replacedColor] = creatureReplColorSets[replacementColor]._d3ReplacementColor;
+}
+
+void DisplayMan::drawFloorPitOrStairsBitmap(uint16 nativeIndex, Frame &f) {
+ if (f._srcByteWidth)
+ blitToBitmap(getNativeBitmapOrGraphic(nativeIndex), _bitmapViewport, f._box, f._srcX, f._srcY,
+ f._srcByteWidth, k112_byteWidthViewport, k10_ColorFlesh, f._srcHeight, k136_heightViewport);
+}
+
+void DisplayMan::drawFloorPitOrStairsBitmapFlippedHorizontally(uint16 nativeIndex, Frame &f) {
+ if (f._srcByteWidth) {
+ copyBitmapAndFlipHorizontal(getNativeBitmapOrGraphic(nativeIndex), _tmpBitmap, f._srcByteWidth, f._srcHeight);
+ blitToBitmap(_tmpBitmap, _bitmapViewport, f._box, f._srcX, f._srcY, f._srcByteWidth,
+ k112_byteWidthViewport, k10_ColorFlesh, f._srcHeight, k136_heightViewport);
+ }
+}
+
+bool DisplayMan::isDrawnWallOrnAnAlcove(int16 wallOrnOrd, ViewWall viewWallIndex) {
+ static Box boxWallPatchBehindInscription = Box(110, 113, 37, 63); // @ G0202_ac_Graphic558_Box_WallPatchBehindInscription
+ static const byte inscriptionLineY[4] = { // @ G0203_auc_Graphic558_InscriptionLineY
+ 48, /* 1 Line */
+ 59, /* 2 lines */
+ 75, /* 3 lines */
+ 86 /* 4 lines */
+ };
+ static const byte wallOrnDerivedBitmapIndexIncrement[12] = { // @ G0190_auc_Graphic558_WallOrnamentDerivedBitmapIndexIncrement
+ 0, /* D3L Right */
+ 0, /* D3R Left */
+ 1, /* D3L Front */
+ 1, /* D3C Front */
+ 1, /* D3R Front */
+ 2, /* D2L Right */
+ 2, /* D2R Left */
+ 3, /* D2L Front */
+ 3, /* D2C Front */
+ 3, /* D2R Front */
+ 4, /* D1L Right */
+ 4 /* D1R Left */
+ };
+
+ static byte unreadableInscriptionBoxY2[15] = { // @ G0204_auc_Graphic558_UnreadableInscriptionBoxY2
+ /* { Y for 1 line, Y for 2 lines, Y for 3 lines } */
+ 45, 48, 53, /* D3L Right, D3R Left */
+ 43, 49, 56, /* D3L Front, D3C Front, D3R Front */
+ 42, 49, 56, /* D2L Right, D2R Left */
+ 46, 53, 63, /* D2L Front, D2C Front, D2R Front */
+ 46, 57, 68 /* D1L Right, D1R Left */
+ };
+
+ static byte g205_WallOrnCoordSets[8][13][6] = { // @ G0205_aaauc_Graphic558_WallOrnamentCoordinateSets
+ /* { X1, X2, Y1, Y2, ByteWidth, Height } */
+ {
+ {80, 83, 41, 45, 8, 5}, /* D3L */
+ {140, 143, 41, 45, 8, 5}, /* D3R */
+ {16, 29, 39, 50, 8, 12}, /* D3L */
+ {107, 120, 39, 50, 8, 12}, /* D3C */
+ {187, 200, 39, 50, 8, 12}, /* D3R */
+ {67, 77, 40, 49, 8, 10}, /* D2L */
+ {146, 156, 40, 49, 8, 10}, /* D2R */
+ {0, 17, 38, 55, 16, 18}, /* D2L */
+ {102, 123, 38, 55, 16, 18}, /* D2C */
+ {206, 223, 38, 55, 16, 18}, /* D2R */
+ {48, 63, 38, 56, 8, 19}, /* D1L */
+ {160, 175, 38, 56, 8, 19}, /* D1R */
+ {96, 127, 36, 63, 16, 28} /* D1C */
+ },
+ {
+ {74, 82, 41, 60, 8, 20}, /* D3L */
+ {141, 149, 41, 60, 8, 20}, /* D3R */
+ {1, 47, 37, 63, 24, 27}, /* D3L */
+ {88, 134, 37, 63, 24, 27}, /* D3C */
+ {171, 217, 37, 63, 24, 27}, /* D3R */
+ {61, 76, 38, 67, 8, 30}, /* D2L */
+ {147, 162, 38, 67, 8, 30}, /* D2R */
+ {0, 43, 37, 73, 32, 37}, /* D2L */
+ {80, 143, 37, 73, 32, 37}, /* D2C */
+ {180, 223, 37, 73, 32, 37}, /* D2R */
+ {32, 63, 36, 83, 16, 48}, /* D1L */
+ {160, 191, 36, 83, 16, 48}, /* D1R */
+ {64, 159, 36, 91, 48, 56} /* D1C */
+ },
+ {
+ {80, 83, 66, 70, 8, 5}, /* D3L */
+ {140, 143, 66, 70, 8, 5}, /* D3R */
+ {16, 29, 64, 75, 8, 12}, /* D3L */
+ {106, 119, 64, 75, 8, 12}, /* D3C */
+ {187, 200, 64, 75, 8, 12}, /* D3R */
+ {67, 77, 74, 83, 8, 10}, /* D2L */
+ {146, 156, 74, 83, 8, 10}, /* D2R */
+ {0, 17, 73, 90, 16, 18}, /* D2L */
+ {100, 121, 73, 90, 16, 18}, /* D2C */
+ {206, 223, 73, 90, 16, 18}, /* D2R */
+ {48, 63, 84, 102, 8, 19}, /* D1L */
+ {160, 175, 84, 102, 8, 19}, /* D1R */
+ {96, 127, 92, 119, 16, 28} /* D1C */
+ },
+ {
+ {80, 83, 49, 53, 8, 5}, /* D3L */
+ {140, 143, 49, 53, 8, 5}, /* D3R */
+ {16, 29, 50, 61, 8, 12}, /* D3L */
+ {106, 119, 50, 61, 8, 12}, /* D3C */
+ {187, 200, 50, 61, 8, 12}, /* D3R */
+ {67, 77, 53, 62, 8, 10}, /* D2L */
+ {146, 156, 53, 62, 8, 10}, /* D2R */
+ {0, 17, 55, 72, 16, 18}, /* D2L */
+ {100, 121, 55, 72, 16, 18}, /* D2C */
+ {206, 223, 55, 72, 16, 18}, /* D2R */
+ {48, 63, 57, 75, 8, 19}, /* D1L */
+ {160, 175, 57, 75, 8, 19}, /* D1R */
+ {96, 127, 64, 91, 16, 28} /* D1C */
+ },
+ {
+ {75, 90, 40, 44, 8, 5}, /* D3L */
+ {133, 148, 40, 44, 8, 5}, /* D3R */
+ {1, 48, 44, 49, 24, 6}, /* D3L */
+ {88, 135, 44, 49, 24, 6}, /* D3C */
+ {171, 218, 44, 49, 24, 6}, /* D3R */
+ {60, 77, 40, 46, 16, 7}, /* D2L */
+ {146, 163, 40, 46, 16, 7}, /* D2R */
+ {0, 35, 43, 50, 32, 8}, /* D2L */
+ {80, 143, 43, 50, 32, 8}, /* D2C */
+ {184, 223, 43, 50, 32, 8}, /* D2R */
+ {32, 63, 41, 52, 16, 12}, /* D1L */
+ {160, 191, 41, 52, 16, 12}, /* D1R */
+ {64, 159, 41, 52, 48, 12} /* D1C */
+ },
+ {
+ {78, 85, 36, 51, 8, 16}, /* D3L */
+ {138, 145, 36, 51, 8, 16}, /* D3R */
+ {10, 41, 34, 53, 16, 20}, /* D3L */
+ {98, 129, 34, 53, 16, 20}, /* D3C */
+ {179, 210, 34, 53, 16, 20}, /* D3R */
+ {66, 75, 34, 56, 8, 23}, /* D2L */
+ {148, 157, 34, 56, 8, 23}, /* D2R */
+ {0, 26, 33, 61, 24, 29}, /* D2L */
+ {91, 133, 33, 61, 24, 29}, /* D2C */
+ {194, 223, 33, 61, 24, 29}, /* D2R */
+ {41, 56, 31, 65, 8, 35}, /* D1L */
+ {167, 182, 31, 65, 8, 35}, /* D1R */
+ {80, 143, 29, 71, 32, 43} /* D1C */
+ },
+ {
+ {75, 82, 25, 75, 8, 51}, /* D3L */
+ {142, 149, 25, 75, 8, 51}, /* D3R */
+ {12, 60, 25, 75, 32, 51}, /* D3L */
+ {88, 136, 25, 75, 32, 51}, /* D3C */
+ {163, 211, 25, 75, 32, 51}, /* D3R */
+ {64, 73, 20, 90, 8, 71}, /* D2L */
+ {150, 159, 20, 90, 8, 71}, /* D2R */
+ {0, 38, 20, 90, 32, 71}, /* D2L */
+ {82, 142, 20, 90, 32, 71}, /* D2C */
+ {184, 223, 20, 90, 32, 71}, /* D2R */
+ {41, 56, 9, 119, 8, 111}, /* D1L */
+ {169, 184, 9, 119, 8, 111}, /* D1R */
+ {64, 159, 9, 119, 48, 111} /* D1C */
+ },
+ {
+ {74, 85, 25, 75, 8, 51}, /* D3L */
+ {137, 149, 25, 75, 8, 51}, /* D3R */
+ {0, 75, 25, 75, 40, 51}, /* D3L Atari ST: { 0, 83, 25, 75, 48, 51 } */
+ {74, 149, 25, 75, 40, 51}, /* D3C Atari ST: { 74, 149, 25, 75, 48, 51 } */
+ {148, 223, 25, 75, 40, 51}, /* D3R Atari ST: { 139, 223, 25, 75, 48, 51 } */
+ {60, 77, 20, 90, 16, 71}, /* D2L */
+ {146, 163, 20, 90, 16, 71}, /* D2R */
+ {0, 74, 20, 90, 56, 71}, /* D2L */
+ {60, 163, 20, 90, 56, 71}, /* D2C */
+ {149, 223, 20, 90, 56, 71}, /* D2R */
+ {32, 63, 9, 119, 16, 111}, /* D1L */
+ {160, 191, 9, 119, 16, 111}, /* D1R */
+ {32, 191, 9, 119, 80, 111} /* D1C */
+ }
+ };
+
+ static Box boxChampionPortraitOnWall = Box(96, 127, 35, 63); // G0109_s_Graphic558_Box_ChampionPortraitOnWall
+
+ if (!wallOrnOrd)
+ return false;
+ wallOrnOrd--;
+ int16 wallOrnamentIndex = wallOrnOrd;
+ int16 ornNativeBitmapIndex = _currMapWallOrnInfo[wallOrnamentIndex][k0_NativeBitmapIndex];
+ int16 wallOrnamentCoordinateSetIndex = _currMapWallOrnInfo[wallOrnamentIndex][k1_CoordinateSet];
+ byte *ornCoordSet = g205_WallOrnCoordSets[wallOrnamentCoordinateSetIndex][viewWallIndex];
+ bool isAlcove = _vm->_dungeonMan->isWallOrnAnAlcove(wallOrnamentIndex);
+ unsigned char inscriptionString[70];
+ bool isInscription = (wallOrnamentIndex == _vm->_dungeonMan->_currMapInscriptionWallOrnIndex);
+ if (isInscription)
+ _vm->_dungeonMan->decodeText((char *)inscriptionString, _inscriptionThing, k0_TextTypeInscription);
+
+ int16 blitPosX;
+ byte *ornBlitBitmap;
+
+ if (viewWallIndex >= k10_ViewWall_D1L_RIGHT) {
+ if (viewWallIndex == k12_ViewWall_D1C_FRONT) {
+ if (isInscription) {
+ blitToBitmap(_bitmapWallSetD1LCR, _bitmapViewport, boxWallPatchBehindInscription, 94, 28, _frameWalls163[k6_ViewSquare_D1C]._srcByteWidth, k112_byteWidthViewport, kM1_ColorNoTransparency, _frameWalls163[k6_ViewSquare_D1C]._srcHeight, k136_heightViewport);
+ byte *inscrString = inscriptionString;
+ byte *L0092_puc_Bitmap = getNativeBitmapOrGraphic(k120_InscriptionFont);
+ int16 textLineIndex = 0;
+ do {
+ int16 characterCount = 0;
+ byte *AL0091_puc_Character = inscrString;
+ while (*AL0091_puc_Character++ < 128) { /* Hexadecimal: 0x80 (Megamax C does not support hexadecimal character constants) */
+ characterCount++;
+ }
+ Frame blitFrame;
+ blitFrame._box._x2 = (blitFrame._box._x1 = 112 - (characterCount << 2)) + 7;
+ blitFrame._box._y1 = (blitFrame._box._y2 = inscriptionLineY[textLineIndex++]) - 7;
+ while (characterCount--) {
+ blitToBitmap(L0092_puc_Bitmap, _bitmapViewport, blitFrame._box, *inscrString++ << 3, 0, k144_byteWidth, k112_byteWidthViewport, k10_ColorFlesh, 8, k136_heightViewport);
+ blitFrame._box._x1 += 8;
+ blitFrame._box._x2 += 8;
+ }
+ } while (*inscrString++ != 129); /* Hexadecimal: 0x81 (Megamax C does not support hexadecimal character constants) */
+ return isAlcove;
+ }
+ ornNativeBitmapIndex++;
+ {
+ Box tmpBox(ornCoordSet);
+ _vm->_dungeonMan->_dungeonViewClickableBoxes[k5_ViewCellDoorButtonOrWallOrn] = tmpBox;
+ }
+ _vm->_dungeonMan->_isFacingAlcove = isAlcove;
+ _vm->_dungeonMan->_isFacingViAltar =
+ (wallOrnamentIndex == _currMapViAltarIndex);
+ _vm->_dungeonMan->_isFacingFountain = false;
+ for (int16 idx = 0; idx < k1_FountainOrnCount; idx++) {
+ if (_currMapFountainOrnIndices[idx] == wallOrnamentIndex) {
+ _vm->_dungeonMan->_isFacingFountain = true;
+ break;
+ }
+ }
+ }
+ ornBlitBitmap = getNativeBitmapOrGraphic(ornNativeBitmapIndex);
+ if (viewWallIndex == k11_ViewWall_D1R_LEFT) {
+ copyBitmapAndFlipHorizontal(ornBlitBitmap, _tmpBitmap, ornCoordSet[4], ornCoordSet[5]);
+ ornBlitBitmap = _tmpBitmap;
+ }
+ blitPosX = 0;
+ } else {
+ int16 coordinateSetOffset = 0;
+ bool flipHorizontal = (viewWallIndex == k6_ViewWall_D2R_LEFT) || (viewWallIndex == k1_ViewWall_D3R_LEFT);
+ if (flipHorizontal)
+ ornBlitBitmap = g205_WallOrnCoordSets[wallOrnamentCoordinateSetIndex][k11_ViewWall_D1R_LEFT];
+ else if ((viewWallIndex == k5_ViewWall_D2L_RIGHT) || (viewWallIndex == k0_ViewWall_D3L_RIGHT))
+ ornBlitBitmap = g205_WallOrnCoordSets[wallOrnamentCoordinateSetIndex][k10_ViewWall_D1L_RIGHT];
+ else {
+ ornNativeBitmapIndex++;
+ ornBlitBitmap = g205_WallOrnCoordSets[wallOrnamentCoordinateSetIndex][k12_ViewWall_D1C_FRONT];
+ if (viewWallIndex == k7_ViewWall_D2L_FRONT)
+ coordinateSetOffset = 6;
+ else if (viewWallIndex == k9_ViewWall_D2R_FRONT)
+ coordinateSetOffset = -6;
+ }
+ blitPosX = (ornCoordSet + coordinateSetOffset)[1] - (ornCoordSet + coordinateSetOffset)[0];
+ if (!isDerivedBitmapInCache(wallOrnamentIndex = k4_DerivedBitmapFirstWallOrnament + (wallOrnamentIndex << 2) + wallOrnDerivedBitmapIndexIncrement[viewWallIndex])) {
+ byte *blitBitmap = getNativeBitmapOrGraphic(ornNativeBitmapIndex);
+ blitToBitmapShrinkWithPalChange(blitBitmap, getDerivedBitmap(wallOrnamentIndex), ornBlitBitmap[4] << 1, ornBlitBitmap[5], ornCoordSet[4] << 1, ornCoordSet[5], (viewWallIndex <= k4_ViewWall_D3R_FRONT) ? _palChangesDoorButtonAndWallOrnD3 : _palChangesDoorButtonAndWallOrnD2);
+ addDerivedBitmap(wallOrnamentIndex);
+ }
+ ornBlitBitmap = getDerivedBitmap(wallOrnamentIndex);
+ if (flipHorizontal) {
+ copyBitmapAndFlipHorizontal(ornBlitBitmap, _tmpBitmap, ornCoordSet[4], ornCoordSet[5]);
+ ornBlitBitmap = _tmpBitmap;
+ blitPosX = 15 - (blitPosX & 0x000F);
+ } else if (viewWallIndex == k7_ViewWall_D2L_FRONT)
+ blitPosX -= ornCoordSet[1] - ornCoordSet[0];
+ else
+ blitPosX = 0;
+ }
+ byte byteFrame[6];
+ if (isInscription) {
+ byte *blitBitmap = ornCoordSet;
+ byte *inscrString2 = inscriptionString;
+ int16 unreadableTextLineCount = 0;
+ do {
+ while (*inscrString2 < 128) { /* Hexadecimal: 0x80 (Megamax C does not support hexadecimal character constants) */
+ inscrString2++;
+ }
+ unreadableTextLineCount++;
+ } while (*inscrString2++ != 129); /* Hexadecimal: 0x81 (Megamax C does not support hexadecimal character constants) */
+ ornCoordSet = blitBitmap;
+ if (unreadableTextLineCount < 4) {
+ for (uint16 i = 0; i < 6; ++i)
+ byteFrame[i] = ornCoordSet[i];
+ ornCoordSet = byteFrame;
+ ornCoordSet[3] = unreadableInscriptionBoxY2[wallOrnDerivedBitmapIndexIncrement[viewWallIndex] * 3 + unreadableTextLineCount - 1];
+ }
+ }
+
+ Box tmpBox(ornCoordSet);
+ blitToBitmap(ornBlitBitmap, _bitmapViewport, tmpBox,
+ blitPosX, 0,
+ ornCoordSet[4], k112_byteWidthViewport, k10_ColorFlesh, ornCoordSet[5], k136_heightViewport);
+
+ if ((viewWallIndex == k12_ViewWall_D1C_FRONT) && _championPortraitOrdinal--) {
+ blitToBitmap(getNativeBitmapOrGraphic(k26_ChampionPortraitsIndice), _bitmapViewport, boxChampionPortraitOnWall,
+ (_championPortraitOrdinal & 0x0007) << 5, (_championPortraitOrdinal >> 3) * 29,
+ k128_byteWidth, k112_byteWidthViewport, k1_ColorDarkGary, 87, k136_heightViewport); /* A portrait is 32x29 pixels */
+ }
+
+ return isAlcove;
+}
+
+void DisplayMan::blitToBitmapShrinkWithPalChange(byte *srcBitmap, byte *destBitmap,
+ int16 srcPixelWidth, int16 srcHeight,
+ int16 destPixelWidth, int16 destHeight, byte *palChange) {
+ warning("DUMMY CODE: f129_blitToBitmapShrinkWithPalChange");
+ warning("MISSING CODE: No palette change takes place in f129_blitToBitmapShrinkWithPalChange");
+
+
+ destPixelWidth = (destPixelWidth + 1) & 0xFFFE;
+
+ uint32 scaleX = (kScaleThreshold * srcPixelWidth) / destPixelWidth;
+ uint32 scaleY = (kScaleThreshold * srcHeight) / destHeight;
+
+ // Loop through drawing output lines
+ for (uint32 destY = 0, scaleYCtr = 0; destY < (uint32)destHeight; ++destY, scaleYCtr += scaleY) {
+ const byte *srcLine = &srcBitmap[(scaleYCtr / kScaleThreshold) * srcPixelWidth];
+ byte *destLine = &destBitmap[destY * destPixelWidth];
+
+ // Loop through drawing the pixels of the row
+ for (uint32 destX = 0, xCtr = 0, scaleXCtr = 0; destX < (uint32)destPixelWidth; ++destX, ++xCtr, scaleXCtr += scaleX)
+ destLine[xCtr] = srcLine[scaleXCtr / kScaleThreshold];
+ }
+}
+
+byte *DisplayMan::getNativeBitmapOrGraphic(uint16 index) {
+ return _bitmaps[index];
+}
+
+Common::MemoryReadStream DisplayMan::getCompressedData(uint16 index) {
+ return Common::MemoryReadStream(_packedBitmaps + _packedItemPos[index], getCompressedDataSize(index), DisposeAfterUse::NO);
+}
+
+uint32 DisplayMan::getCompressedDataSize(uint16 index) {
+ return _packedItemPos[index + 1] - _packedItemPos[index];
+}
+
+void DisplayMan::drawField(FieldAspect* fieldAspect, Box& box) {
+ byte *bitmapMask = nullptr;
+
+ if (fieldAspect->_mask != kMaskFieldAspectNoMask) {
+ bitmapMask = _tmpBitmap;
+ memmove(bitmapMask, getNativeBitmapOrGraphic(k69_FieldMask_D3R_GraphicIndice + getFlag(fieldAspect->_mask, kMaskFieldAspectIndex)),
+ fieldAspect->_height * fieldAspect->_byteWidth * 2);
+ if (getFlag(fieldAspect->_mask, kMaskFieldAspectFlipMask)) {
+ flipBitmapHorizontal(bitmapMask, fieldAspect->_byteWidth, fieldAspect->_height);
+ }
+ }
+
+ isDerivedBitmapInCache(k0_DerivedBitmapViewport);
+ byte *bitmap = getNativeBitmapOrGraphic(k73_FieldTeleporterGraphicIndice + fieldAspect->_nativeBitmapRelativeIndex);
+ blitBoxFilledWithMaskedBitmap(bitmap, _bitmapViewport, bitmapMask, getDerivedBitmap(k0_DerivedBitmapViewport), box,
+ _vm->getRandomNumber(2) + fieldAspect->_baseStartUnitIndex, _vm->getRandomNumber(32), k112_byteWidthViewport,
+ (Color)fieldAspect->_transparentColor, fieldAspect->_xPos, 0, 136, fieldAspect->_bitplaneWordCount);
+ addDerivedBitmap(k0_DerivedBitmapViewport);
+ releaseBlock(k0_DerivedBitmapViewport | 0x8000);
+}
+
+int16 DisplayMan::getScaledBitmapByteCount(int16 byteWidth, int16 height, int16 scale) {
+ return getNormalizedByteWidth(getScaledDimension(byteWidth, scale)) * getScaledDimension(height, scale);
+}
+
+int16 DisplayMan::getScaledDimension(int16 dimension, int16 scale) {
+ return (dimension * scale + scale / 2) / 32;
+}
+
+void DisplayMan::drawObjectsCreaturesProjectilesExplosions(Thing thingParam, Direction directionParam, int16 mapXpos,
+ int16 mapYpos, int16 viewSquareIndex, uint16 orderedViewCellOrdinals) {
+ int16 AL_0_creatureGraphicInfoRed;
+ int16 AL_0_creatureIndexRed;
+#define AL_1_viewSquareExplosionIndex viewSquareIndex
+ int16 L0126_i_Multiple;
+#define AL_2_viewCell L0126_i_Multiple
+#define AL_2_cellPurpleMan L0126_i_Multiple
+#define AL_2_explosionSize L0126_i_Multiple
+ int16 L0127_i_Multiple;
+#define AL_4_thingType L0127_i_Multiple
+#define AL_4_nativeBitmapIndex L0127_i_Multiple
+#define AL_4_xPos L0127_i_Multiple
+#define AL_4_groupCells L0127_i_Multiple
+#define AL_4_normalizdByteWidth L0127_i_Multiple
+#define AL_4_yPos L0127_i_Multiple
+#define AL_4_projectileAspect L0127_i_Multiple
+#define AL_4_explosionType L0127_i_Multiple
+#define AL_4_explosionAspectIndex L0127_i_Multiple
+ ObjectAspect* objectAspect;
+ uint32 remainingViewCellOrdinalsToProcess;
+ byte *coordinateSet;
+ int16 derivedBitmapIndex = -1;
+ bool L0135_B_DrawAlcoveObjects;
+ int16 byteWidth;
+ int16 heightRedEagle;
+ int16 viewLane; /* The lane (center/left/right) that the specified square is part of */
+ int16 cellYellowBear;
+ int16 paddingPixelCount;
+ int16 heightGreenGoat;
+ bool useAlcoveObjectImage; /* true for objects that have a special graphic when drawn in an alcove, like the Chest */
+ bool flipHorizontal;
+ bool drawingGrabbableObject;
+ Box boxByteGreen;
+ Thing firstThingToDraw; /* Initialized to thingParam and never changed afterwards. Used as a backup of the specified first object to draw */
+ uint16 L0147_ui_Multiple;
+#define AL_10_viewSquareIndexBackup L0147_ui_Multiple
+#define AL_10_explosionScaleIndex L0147_ui_Multiple
+ int16 cellCounter;
+ uint16 objectShiftIndex;
+ uint16 L0150_ui_Multiple = 0;
+#define AL_8_shiftSetIndex L0150_ui_Multiple
+#define AL_8_projectileScaleIndex L0150_ui_Multiple
+ CreatureAspect* creatureAspectStruct;
+ int16 creatureSize;
+ int16 creatureDirectionDelta;
+ int16 creatureGraphicInfoGreen;
+ int16 creatureAspectInt;
+ int16 creatureIndexGreen;
+ int16 transparentColor;
+ int16 creaturePaddingPixelCount;
+ bool drawingLastBackRowCell;
+ bool useCreatureSideBitmap;
+ bool useCreatureBackBitmap;
+ bool useCreatureSpecialD2FrontBitmap;
+ bool useCreatureAttackBitmap;
+ bool useFlippedHorizontallyCreatureFrontImage;
+ bool drawCreaturesCompleted; /* Set to true when the last creature that the function should draw is being drawn. This is used to avoid processing the code to draw creatures for the remaining square cells */
+ int16 doorFrontViewDrawingPass; /* Value 0, 1 or 2 */
+ int16 projectilePosX;
+ int16 projectileDirection;
+ int16 projectileAspectType;
+ int16 projectileBitmapIndexDelta;
+ bool drawProjectileAsObject; /* When true, the code section to draw an object is called (with a goto) to draw the projectile, then the code section goes back to projectile processing with another goto */
+ uint16 currentViewCellToDraw = 0;
+ bool projectileFlipVertical = false;
+
+ /* This is the full dungeon view */
+ static Box boxExplosionPatternD0C = Box(0, 223, 0, 135); // @ G0105_s_Graphic558_Box_ExplosionPattern_D0C
+
+ static byte explosionBaseScales[5] = { // @ G0216_auc_Graphic558_ExplosionBaseScales
+ 10, /* D4 */
+ 16, /* D3 */
+ 23, /* D2 */
+ 32, /* D1 */
+ 32 /* D0 */
+ };
+
+ static byte objectPileShiftSetIndices[16][2] = { // @ G0217_aauc_Graphic558_ObjectPileShiftSetIndices
+ /* { X shift index, Y shift index } */
+ {2, 5},
+ {0, 6},
+ {5, 7},
+ {3, 0},
+ {7, 1},
+ {1, 2},
+ {6, 3},
+ {3, 3},
+ {5, 5},
+ {2, 6},
+ {7, 7},
+ {1, 0},
+ {3, 1},
+ {6, 2},
+ {1, 3},
+ {5, 3}
+ };
+
+ static byte objectCoordinateSets[3][10][5][2] = { // @ G0218_aaaauc_Graphic558_ObjectCoordinateSets
+ /* { {X, Y }, {X, Y }, {X, Y }, {X, Y }, {X, Y } } */
+ {
+ {{ 0, 0}, { 0, 0}, {125, 72}, { 95, 72}, {112, 64}}, /* D3C */
+ {{ 0, 0}, { 0, 0}, { 62, 72}, { 25, 72}, { 24, 64}}, /* D3L */
+ {{ 0, 0}, { 0, 0}, {200, 72}, {162, 72}, {194, 64}}, /* D3R */
+ {{ 92, 78}, {132, 78}, {136, 86}, { 88, 86}, {112, 74}}, /* D2C */
+ {{ 10, 78}, { 53, 78}, { 41, 86}, { 0, 0}, { 3, 74}}, /* D2L */
+ {{171, 78}, {218, 78}, { 0, 0}, {183, 86}, {219, 74}}, /* D2R */
+ {{ 83, 96}, {141, 96}, {148, 111}, { 76, 111}, {112, 94}}, /* D1C */
+ {{ 0, 0}, { 26, 96}, { 5, 111}, { 0, 0}, { 0, 0}}, /* D1L */
+ {{197, 96}, { 0, 0}, { 0, 0}, {220, 111}, { 0, 0}}, /* D1R */
+ {{ 66, 131}, {158, 131}, { 0, 0}, { 0, 0}, { 0, 0}} /* D0C */
+ },
+ {
+ {{ 0, 0}, { 0, 0}, {125, 72}, { 95, 72}, {112, 63}}, /* D3C */
+ {{ 0, 0}, { 0, 0}, { 62, 72}, { 25, 72}, { 24, 63}}, /* D3L */
+ {{ 0, 0}, { 0, 0}, {200, 72}, {162, 72}, {194, 63}}, /* D3R */
+ {{ 92, 78}, {132, 78}, {136, 86}, { 88, 86}, {112, 73}}, /* D2C */
+ {{ 10, 78}, { 53, 78}, { 41, 86}, { 0, 0}, { 3, 73}}, /* D2L */
+ {{171, 78}, {218, 78}, { 0, 0}, {183, 86}, {219, 73}}, /* D2R */
+ {{ 83, 96}, {141, 96}, {148, 111}, { 76, 111}, {112, 89}}, /* D1C */
+ {{ 0, 0}, { 26, 96}, { 5, 111}, { 0, 0}, { 0, 0}}, /* D1L */
+ {{197, 96}, { 0, 0}, { 0, 0}, {220, 111}, { 0, 0}}, /* D1R */
+ {{ 66, 131}, {158, 131}, { 0, 0}, { 0, 0}, { 0, 0}} /* D0C */
+ },
+ {
+ {{ 0, 0}, { 0, 0}, {125, 75}, { 95, 75}, {112, 65}}, /* D3C */
+ {{ 0, 0}, { 0, 0}, { 62, 75}, { 25, 75}, { 24, 65}}, /* D3L */
+ {{ 0, 0}, { 0, 0}, {200, 75}, {162, 75}, {194, 65}}, /* D3R */
+ {{ 92, 81}, {132, 81}, {136, 88}, { 88, 88}, {112, 76}}, /* D2C */
+ {{ 10, 81}, { 53, 81}, { 41, 88}, { 0, 0}, { 3, 76}}, /* D2L */
+ {{171, 81}, {218, 81}, { 0, 0}, {183, 88}, {219, 76}}, /* D2R */
+ {{ 83, 98}, {141, 98}, {148, 115}, { 76, 115}, {112, 98}}, /* D1C */
+ {{ 0, 0}, { 26, 98}, { 5, 115}, { 0, 0}, { 0, 0}}, /* D1L */
+ {{197, 98}, { 0, 0}, { 0, 0}, {220, 115}, { 0, 0}}, /* D1R */
+ {{ 66, 135}, {158, 135}, { 0, 0}, { 0, 0}, { 0, 0}} /* D0C */
+ }
+ };
+
+ static int16 shiftSets[3][8] = { // @ G0223_aac_Graphic558_ShiftSets
+ {0, 1, 2, 3, 0, -3, -2, -1}, /* D0 Back or D1 Front */
+ {0, 1, 1, 2, 0, -2, -1, -1}, /* D1 Back or D2 Front */
+ {0, 1, 1, 1, 0, -1, -1, -1} /* D2 Back or D3 Front */
+ };
+
+ static byte creatureCoordinateSets[3][11][5][2] = { // @ G0224_aaaauc_Graphic558_CreatureCoordinateSets
+ /* { { X, Y }, { X, Y }, { X, Y }, { X, Y }, { X, Y } } */
+ {
+ {{ 95, 70}, {127, 70}, {129, 75}, { 93, 75}, {111, 72}}, /* D3C */
+ {{131, 70}, {163, 70}, {158, 75}, {120, 75}, {145, 72}}, /* D3L */
+ {{ 59, 70}, { 91, 70}, {107, 75}, { 66, 75}, { 79, 72}}, /* D3R */
+ {{ 92, 81}, {131, 81}, {132, 90}, { 91, 90}, {111, 85}}, /* D2C */
+ {{ 99, 81}, {146, 81}, {135, 90}, { 80, 90}, {120, 85}}, /* D2L */
+ {{ 77, 81}, {124, 81}, {143, 90}, { 89, 90}, {105, 85}}, /* D2R */
+ {{ 83, 103}, {141, 103}, {148, 119}, { 76, 119}, {109, 111}}, /* D1C */
+ {{ 46, 103}, {118, 103}, {101, 119}, { 0, 0}, { 79, 111}}, /* D1L */
+ {{107, 103}, {177, 103}, { 0, 0}, {123, 119}, {144, 111}}, /* D1R */
+ {{ 0, 0}, { 67, 135}, { 0, 0}, { 0, 0}, { 0, 0}}, /* D0L */
+ {{156, 135}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}} /* D0R */
+ },
+ {
+ {{ 94, 75}, {128, 75}, {111, 70}, {111, 72}, {111, 75}}, /* D3C */
+ {{120, 75}, {158, 75}, {149, 70}, {145, 72}, {150, 75}}, /* D3L */
+ {{ 66, 75}, {104, 75}, { 75, 70}, { 79, 72}, { 73, 75}}, /* D3R */
+ {{ 91, 90}, {132, 90}, {111, 83}, {111, 85}, {111, 90}}, /* D2C */
+ {{ 80, 90}, {135, 90}, {125, 83}, {120, 85}, {125, 90}}, /* D2L */
+ {{ 89, 90}, {143, 90}, { 99, 83}, {105, 85}, { 98, 90}}, /* D2R */
+ {{ 81, 119}, {142, 119}, {111, 105}, {111, 111}, {111, 119}}, /* D1C */
+ {{ 0, 0}, {101, 119}, { 84, 105}, { 70, 111}, { 77, 119}}, /* D1L */
+ {{123, 119}, { 0, 0}, {139, 105}, {153, 111}, {146, 119}}, /* D1R */
+ {{ 0, 0}, { 83, 130}, { 57, 121}, { 47, 126}, { 57, 130}}, /* D0L */
+ {{140, 130}, { 0, 0}, {166, 121}, {176, 126}, {166, 130}} /* D0R */
+ },
+ {
+ {{ 95, 59}, {127, 59}, {129, 61}, { 93, 61}, {111, 60}}, /* D3C */
+ {{131, 59}, {163, 59}, {158, 61}, {120, 61}, {145, 60}}, /* D3L */
+ {{ 59, 59}, { 91, 59}, {107, 61}, { 66, 61}, { 79, 60}}, /* D3R */
+ {{ 92, 65}, {131, 65}, {132, 67}, { 91, 67}, {111, 66}}, /* D2C */
+ {{ 99, 65}, {146, 65}, {135, 67}, { 80, 67}, {120, 66}}, /* D2L */
+ {{ 77, 65}, {124, 65}, {143, 67}, { 89, 67}, {105, 66}}, /* D2R */
+ {{ 83, 79}, {141, 79}, {148, 85}, { 76, 85}, {111, 81}}, /* D1C */
+ {{ 46, 79}, {118, 79}, {101, 85}, { 0, 0}, { 79, 81}}, /* D1L */
+ {{107, 79}, {177, 79}, { 0, 0}, {123, 85}, {144, 81}}, /* D1R */
+ {{ 0, 0}, { 67, 96}, { 0, 0}, { 0, 0}, { 0, 0}}, /* D0L */
+ {{156, 96}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}} /* D0R */
+ }
+ };
+
+ static int16 explosionCoordinatesArray[15][2][2] = { // @ G0226_aaai_Graphic558_ExplosionCoordinates
+ /* { { Front Left X, Front Left Y }, { Front Right X, Front Right Y } } */
+ {{100, 47}, {122, 47}}, /* D4C */
+ {{ 52, 47}, { 76, 47}}, /* D4L */
+ {{148, 47}, {172, 47}}, /* D4R */
+ {{ 95, 50}, {127, 50}}, /* D3C */
+ {{ 31, 50}, { 63, 50}}, /* D3L */
+ {{159, 50}, {191, 50}}, /* D3R */
+ {{ 92, 53}, {131, 53}}, /* D2C */
+ {{ -3, 53}, { 46, 53}}, /* D2L */
+ {{177, 53}, {226, 53}}, /* D2R */
+ {{ 83, 57}, {141, 57}}, /* D1C */
+ {{-54, 57}, { 18, 57}}, /* D1L */
+ {{207, 57}, {277, 57}}, /* D1R */
+ {{ 0, 0}, { 0, 0}}, /* D0C */
+ {{-73, 60}, {-33, 60}}, /* D0L */
+ {{256, 60}, {296, 60}} /* D0R */
+ };
+
+ static int16 rebirthStep2ExplosionCoordinates[7][3] = { // @ G0227_aai_Graphic558_RebirthStep2ExplosionCoordinates
+ /* { X, Y, Scale } */
+ {113, 57, 12}, /* D3C */
+ { 24, 57, 12}, /* D3L */
+ {195, 57, 12}, /* D3R */
+ {111, 63, 16}, /* D2C */
+ { 12, 63, 16}, /* D2L */
+ {213, 63, 16}, /* D2R */
+ {112, 76, 24} /* D1C */
+ };
+
+ static int16 rebirthStep1ExplosionCoordinates[7][3] = { // @ G0228_aai_Graphic558_RebirthStep1ExplosionCoordinates
+ /* { X, Y, Scale } */
+ {112, 53, 15}, /* D3C */
+ { 24, 53, 15}, /* D3L */
+ {194, 53, 15}, /* D3R */
+ {112, 59, 20}, /* D2C */
+ { 15, 59, 20}, /* D2L */
+ {208, 59, 20}, /* D2R */
+ {112, 70, 32} /* D1C */
+ };
+
+ static int16 centeredExplosionCoordinates[15][2] = { // @ G0225_aai_Graphic558_CenteredExplosionCoordinates
+ /* { X, Y } */
+ {111, 47}, /* D4C */
+ { 57, 47}, /* D4L */
+ {167, 47}, /* D4R */
+ {111, 50}, /* D3C */
+ { 45, 50}, /* D3L */
+ {179, 50}, /* D3R */
+ {111, 53}, /* D2C */
+ { 20, 53}, /* D2L */
+ {205, 53}, /* D2R */
+ {111, 57}, /* D1C */
+ {-30, 57}, /* D1L */
+ {253, 57}, /* D1R */
+ {111, 60}, /* D0C */
+ {-53, 60}, /* D0L */
+ {276, 60} /* D0R */
+ };
+
+ if (thingParam == Thing::_endOfList)
+ return;
+
+ Group *group = nullptr;
+ Thing groupThing = Thing::_none;
+ bool squareHasExplosion = drawCreaturesCompleted = false;
+ bool squareHasProjectile = false;
+ cellCounter = 0;
+ firstThingToDraw = thingParam;
+ if (getFlag(orderedViewCellOrdinals, k0x0008_CellOrder_DoorFront)) { /* If the function call is to draw objects on a door square viewed from the front */
+ doorFrontViewDrawingPass = (orderedViewCellOrdinals & 0x0001) + 1; /* Two function calls are made in that case to draw objects on both sides of the door frame. The door and its frame are drawn between the two calls. This value indicates the drawing pass so that creatures are drawn in the right order and so that Fluxcages are not drawn twice */
+ orderedViewCellOrdinals >>= 4; /* Remove the first nibble that was used for the door front view pass */
+ } else
+ doorFrontViewDrawingPass = 0; /* The function call is not to draw objects on a door square viewed from the front */
+
+ L0135_B_DrawAlcoveObjects = !(remainingViewCellOrdinalsToProcess = orderedViewCellOrdinals);
+ AL_10_viewSquareIndexBackup = viewSquareIndex;
+ viewLane = (viewSquareIndex + 3) % 3;
+ bool twoHalfSquareCreaturesFrontView;
+ byte *bitmapRedBanana;
+ byte *bitmapGreenAnt;
+ do {
+ /* Draw objects */
+ if (L0135_B_DrawAlcoveObjects) {
+ AL_2_viewCell = k4_ViewCellAlcove; /* Index of coordinates to draw objects in alcoves */
+ cellYellowBear = returnOppositeDir(directionParam); /* Alcove is on the opposite direction of the viewing direction */
+ objectShiftIndex = 2;
+ } else {
+ AL_2_viewCell = _vm->ordinalToIndex((int16)remainingViewCellOrdinalsToProcess & 0x000F); /* View cell is the index of coordinates to draw object */
+ currentViewCellToDraw = AL_2_viewCell;
+ remainingViewCellOrdinalsToProcess >>= 4; /* Proceed to the next cell ordinal */
+ cellCounter++;
+ cellYellowBear = normalizeModulo4(AL_2_viewCell + directionParam); /* Convert view cell to absolute cell */
+ thingParam = firstThingToDraw;
+ viewSquareIndex = AL_10_viewSquareIndexBackup; /* Restore value as it may have been modified while drawing a creature */
+ objectShiftIndex = 0;
+ }
+ objectShiftIndex += (cellYellowBear & 0x0001) << 3;
+ drawProjectileAsObject = false;
+ do {
+ if ((AL_4_thingType = thingParam.getType()) == k4_GroupThingType) {
+ groupThing = thingParam;
+ continue;
+ }
+
+ if (AL_4_thingType == k14_ProjectileThingType) {
+ squareHasProjectile = true;
+ continue;
+ }
+
+ if (AL_4_thingType == k15_ExplosionThingType) {
+ squareHasExplosion = true;
+ continue;
+ }
+
+ if ((viewSquareIndex >= k0_ViewSquare_D3C) && (viewSquareIndex <= k9_ViewSquare_D0C) && (thingParam.getCell() == cellYellowBear)) { /* Square where objects are visible and object is located on cell being processed */
+ objectAspect = &(_objectAspects209[_vm->_dungeonMan->_objectInfos[_vm->_dungeonMan->getObjectInfoIndex(thingParam)]._objectAspectIndex]);
+ AL_4_nativeBitmapIndex = k360_FirstObjectGraphicIndice + objectAspect->_firstNativeBitmapRelativeIndex;
+ useAlcoveObjectImage = (L0135_B_DrawAlcoveObjects && getFlag(objectAspect->_graphicInfo, k0x0010_ObjectAlcoveMask) && !viewLane);
+ if (useAlcoveObjectImage)
+ AL_4_nativeBitmapIndex++;
+
+ coordinateSet = objectCoordinateSets[objectAspect->_coordinateSet][viewSquareIndex][AL_2_viewCell];
+ if (!coordinateSet[1]) /* If object is not visible */
+ continue;
+T0115015_DrawProjectileAsObject:
+ flipHorizontal = getFlag(objectAspect->_graphicInfo, k0x0001_ObjectFlipOnRightMask) &&
+ !useAlcoveObjectImage &&
+ ((viewLane == k2_ViewLaneRight) || (!viewLane && ((AL_2_viewCell == k1_ViewCellFrontRight) || (AL_2_viewCell == k2_ViewCellBackRight))));
+ /* Flip horizontally if object graphic requires it and is not being drawn in an alcove and the object is either on the right lane or on the right column of the center lane */
+ paddingPixelCount = 0;
+
+ if ((viewSquareIndex == k9_ViewSquare_D0C) || ((viewSquareIndex >= k6_ViewSquare_D1C) && (AL_2_viewCell >= k2_ViewCellBackRight))) {
+ drawingGrabbableObject = (!viewLane && !drawProjectileAsObject); /* If object is in the center lane (only D0C or D1C with condition above) and is not a projectile */
+ AL_8_shiftSetIndex = k0_ShiftSet_D0BackD1Front;
+ bitmapRedBanana = getNativeBitmapOrGraphic(AL_4_nativeBitmapIndex); /* Use base graphic, no resizing */
+ byteWidth = objectAspect->_byteWidth;
+ heightRedEagle = objectAspect->_height;
+ if (flipHorizontal) {
+ copyBitmapAndFlipHorizontal(bitmapRedBanana, _tmpBitmap, byteWidth, heightRedEagle);
+ bitmapRedBanana = _tmpBitmap;
+ }
+ } else {
+ drawingGrabbableObject = false;
+ derivedBitmapIndex = k104_DerivedBitmapFirstObject + objectAspect->_firstDerivedBitmapRelativeIndex;
+ byte* paletteChanges;
+ if ((viewSquareIndex >= k6_ViewSquare_D1C) || ((viewSquareIndex >= k3_ViewSquare_D2C) && (AL_2_viewCell >= k2_ViewCellBackRight))) {
+ derivedBitmapIndex++;
+ AL_8_shiftSetIndex = k1_ShiftSet_D1BackD2Front;
+ byteWidth = getScaledDimension(objectAspect->_byteWidth, k20_Scale_D2);
+ heightRedEagle = getScaledDimension(objectAspect->_height, k20_Scale_D2);
+ paletteChanges = _palChangesFloorOrnD2;
+ } else {
+ AL_8_shiftSetIndex = k2_ShiftSet_D2BackD3Front;
+ byteWidth = getScaledDimension(objectAspect->_byteWidth, k16_Scale_D3);
+ heightRedEagle = getScaledDimension(objectAspect->_height, k16_Scale_D3);
+ paletteChanges = _palChangesFloorOrnD3;
+ }
+ if (flipHorizontal) {
+ derivedBitmapIndex += 2;
+ paddingPixelCount = (7 - ((byteWidth - 1) & 0x0007)) << 1;
+ } else if (useAlcoveObjectImage)
+ derivedBitmapIndex += 4;
+
+ if (isDerivedBitmapInCache(derivedBitmapIndex))
+ bitmapRedBanana = getDerivedBitmap(derivedBitmapIndex);
+ else {
+ bitmapGreenAnt = getNativeBitmapOrGraphic(AL_4_nativeBitmapIndex);
+ blitToBitmapShrinkWithPalChange(bitmapGreenAnt, bitmapRedBanana = getDerivedBitmap(derivedBitmapIndex), objectAspect->_byteWidth << 1, objectAspect->_height, byteWidth << 1, heightRedEagle, paletteChanges);
+ if (flipHorizontal)
+ flipBitmapHorizontal(bitmapRedBanana, getNormalizedByteWidth(byteWidth), heightRedEagle);
+
+ addDerivedBitmap(derivedBitmapIndex);
+ }
+ }
+ AL_4_xPos = coordinateSet[0];
+ boxByteGreen._y2 = coordinateSet[1];
+ if (!drawProjectileAsObject) { /* If drawing an object that is not a projectile */
+ AL_4_xPos += shiftSets[AL_8_shiftSetIndex][objectPileShiftSetIndices[objectShiftIndex][0]];
+ boxByteGreen._y2 += shiftSets[AL_8_shiftSetIndex][objectPileShiftSetIndices[objectShiftIndex][1]];
+ objectShiftIndex++; /* The next object drawn will use the next shift values */
+ if (L0135_B_DrawAlcoveObjects) {
+ if (objectShiftIndex >= 14)
+ objectShiftIndex = 2;
+ } else
+ objectShiftIndex &= 0x000F;
+ }
+ boxByteGreen._y1 = boxByteGreen._y2 - (heightRedEagle - 1);
+ if (boxByteGreen._y2 > 135)
+ boxByteGreen._y2 = 135;
+
+ boxByteGreen._x2 = MIN(223, AL_4_xPos + byteWidth);
+ boxByteGreen._x1 = MAX(0, AL_4_xPos - byteWidth + 1);
+ if (boxByteGreen._x1) {
+ if (flipHorizontal)
+ AL_4_xPos = paddingPixelCount;
+ else
+ AL_4_xPos = 0;
+ } else
+ AL_4_xPos = byteWidth - AL_4_xPos - 1;
+
+ if (drawingGrabbableObject) {
+ bitmapGreenAnt = bitmapRedBanana;
+
+ Box *AL_6_box = &_vm->_dungeonMan->_dungeonViewClickableBoxes[AL_2_viewCell];
+
+ if (AL_6_box->_x1 == 255) { /* If the grabbable object is the first */
+ *AL_6_box = boxByteGreen;
+ if ((heightGreenGoat = AL_6_box->_y2 - AL_6_box->_y1) < 14) { /* If the box is too small then enlarge it a little */
+ heightGreenGoat = heightGreenGoat >> 1;
+ AL_6_box->_y1 += heightGreenGoat - 7;
+ if (heightGreenGoat < 4)
+ AL_6_box->_y2 -= heightGreenGoat - 3;
+ }
+ } else { /* If there are several grabbable objects then enlarge the box so it includes all objects */
+ AL_6_box->_x1 = MIN(AL_6_box->_x1, boxByteGreen._x1);
+ AL_6_box->_x2 = MAX(AL_6_box->_x2, boxByteGreen._x2);
+ AL_6_box->_y1 = MIN(AL_6_box->_y1, boxByteGreen._y1);
+ AL_6_box->_y2 = MAX(AL_6_box->_y2, boxByteGreen._y2);
+ }
+ bitmapRedBanana = bitmapGreenAnt;
+ _vm->_dungeonMan->_pileTopObject[AL_2_viewCell] = thingParam; /* The object is at the top of the pile */
+ }
+ blitToBitmap(bitmapRedBanana, _bitmapViewport, boxByteGreen, AL_4_xPos, 0, getNormalizedByteWidth(byteWidth), k112_byteWidthViewport, k10_ColorFlesh, heightRedEagle, k136_heightViewport);
+ if (drawProjectileAsObject)
+ goto T0115171_BackFromT0115015_DrawProjectileAsObject;
+ }
+ } while ((thingParam = _vm->_dungeonMan->getNextThing(thingParam)) != Thing::_endOfList);
+ if (AL_2_viewCell == k4_ViewCellAlcove)
+ break; /* End of processing when drawing objects in an alcove */
+ if (viewSquareIndex < k0_ViewSquare_D3C)
+ break; /* End of processing if square is too far away at D4 */
+ /* Draw creatures */
+ drawingLastBackRowCell = ((AL_2_viewCell <= k1_ViewCellFrontRight) || (cellCounter == 1)) && (!remainingViewCellOrdinalsToProcess || ((remainingViewCellOrdinalsToProcess & 0x0000000F) >= 3)); /* If (draw cell on the back row or second cell being processed) and (no more cells to draw or next cell to draw is a cell on the front row) */
+ if ((groupThing == Thing::_none) || drawCreaturesCompleted)
+ goto T0115129_DrawProjectiles; /* Skip code to draw creatures */
+
+ ActiveGroup *activeGroup;
+ if (group == nullptr) { /* If all creature data and info has not already been gathered */
+ group = (Group *)_vm->_dungeonMan->getThingData(groupThing);
+ activeGroup = &_vm->_groupMan->_activeGroups[group->getActiveGroupIndex()];
+ CreatureInfo *creatureInfo = &_vm->_dungeonMan->_creatureInfos[group->_type];
+ creatureAspectStruct = &_creatureAspects219[creatureInfo->_creatureAspectIndex];
+ creatureSize = getFlag(creatureInfo->_attributes, k0x0003_MaskCreatureInfo_size);
+ creatureGraphicInfoGreen = creatureInfo->_graphicInfo;
+ }
+ objectAspect = (ObjectAspect *)creatureAspectStruct;
+ AL_0_creatureIndexRed = _vm->_groupMan->getCreatureOrdinalInCell(group, cellYellowBear);
+
+ if (AL_0_creatureIndexRed) { /* If there is a creature on the cell being processed */
+ AL_0_creatureIndexRed--; /* Convert ordinal to index */
+ creatureIndexGreen = AL_0_creatureIndexRed;
+ } else if (creatureSize == k1_MaskCreatureSizeHalf) {
+ AL_0_creatureIndexRed = 0;
+ creatureIndexGreen = -1;
+ } else
+ goto T0115129_DrawProjectiles; /* No creature to draw at cell, skip to projectiles */
+
+ creatureDirectionDelta = normalizeModulo4(directionParam - _vm->_groupMan->getCreatureValue(activeGroup->_directions, AL_0_creatureIndexRed));
+ twoHalfSquareCreaturesFrontView = false;
+ if ((AL_4_groupCells = activeGroup->_cells) == k255_CreatureTypeSingleCenteredCreature) { /* If there is a single centered creature in the group */
+ if (remainingViewCellOrdinalsToProcess || (doorFrontViewDrawingPass == 1))
+ goto T0115129_DrawProjectiles; /* Do not draw a single centered creature now, wait until second pass (for a front view door) or until all cells have been drawn so the creature is drawn over all the objects on the floor */
+
+ drawCreaturesCompleted = true;
+ if ((creatureSize == k1_MaskCreatureSizeHalf) && (creatureDirectionDelta & 0x0001)) /* Side view of half square creature */
+ AL_2_viewCell = k3_HalfSizedViewCell_CenterColumn;
+ else
+ AL_2_viewCell = k4_HalfSizedViewCell_FrontRow;
+ } else if ((creatureSize == k1_MaskCreatureSizeHalf) && (drawingLastBackRowCell || !remainingViewCellOrdinalsToProcess || (creatureIndexGreen < 0))) {
+ if (drawingLastBackRowCell && (doorFrontViewDrawingPass != 2)) {
+ if ((creatureIndexGreen >= 0) && (creatureDirectionDelta & 0x0001))
+ AL_2_viewCell = k2_HalfSizedViewCell_BackRow; /* Side view of a half square creature on the back row. Drawn during pass 1 for a door square */
+ else
+ goto T0115129_DrawProjectiles;
+ } else if ((doorFrontViewDrawingPass != 1) && !remainingViewCellOrdinalsToProcess) {
+ if (creatureDirectionDelta & 0x0001) {
+ if (creatureIndexGreen >= 0)
+ AL_2_viewCell = k4_HalfSizedViewCell_FrontRow; /* Side view of a half square creature on the front row. Drawn during pass 2 for a door square */
+ else
+ goto T0115129_DrawProjectiles;
+ } else {
+ drawCreaturesCompleted = true;
+ if (creatureIndexGreen < 0)
+ creatureIndexGreen = 0;
+
+ twoHalfSquareCreaturesFrontView = group->getCount();
+ if (((AL_4_groupCells = _vm->_groupMan->getCreatureValue(AL_4_groupCells, AL_0_creatureIndexRed)) == directionParam) || (AL_4_groupCells == returnPrevVal(directionParam)))
+ AL_2_viewCell = k0_HalfSizedViewCell_LeftColumn;
+ else
+ AL_2_viewCell = k1_HalfSizedViewCell_RightColumn;
+ }
+ } else
+ goto T0115129_DrawProjectiles;
+ } else if (creatureSize != k0_MaskCreatureSizeQuarter)
+ goto T0115129_DrawProjectiles;
+
+ creatureAspectInt = activeGroup->_aspect[creatureIndexGreen];
+ if (viewSquareIndex > k9_ViewSquare_D0C)
+ viewSquareIndex--;
+
+T0115077_DrawSecondHalfSquareCreature:
+ coordinateSet = creatureCoordinateSets[((CreatureAspect *)objectAspect)->getCoordSet()][viewSquareIndex][AL_2_viewCell];
+ if (!coordinateSet[1])
+ goto T0115126_CreatureNotVisible;
+ AL_0_creatureGraphicInfoRed = creatureGraphicInfoGreen;
+ AL_4_nativeBitmapIndex = k446_FirstCreatureGraphicIndice + ((CreatureAspect *)objectAspect)->_firstNativeBitmapRelativeIndex; /* By default, assume using the front image */
+ derivedBitmapIndex = ((CreatureAspect *)objectAspect)->_firstDerivedBitmapIndex;
+ int16 sourceByteWidth;
+ int16 sourceHeight;
+ useCreatureSideBitmap = getFlag(AL_0_creatureGraphicInfoRed, k0x0008_CreatureInfoGraphicMaskSide) && (creatureDirectionDelta & 0x0001);
+ if (useCreatureSideBitmap) {
+ useCreatureAttackBitmap = useFlippedHorizontallyCreatureFrontImage = useCreatureBackBitmap = false;
+ AL_4_nativeBitmapIndex++; /* Skip the front image. Side image is right after the front image */
+ derivedBitmapIndex += 2;
+ sourceByteWidth = byteWidth = ((CreatureAspect *)objectAspect)->_byteWidthSide;
+ sourceHeight = heightRedEagle = ((CreatureAspect *)objectAspect)->_heightSide;
+ } else {
+ useCreatureBackBitmap = getFlag(AL_0_creatureGraphicInfoRed, k0x0010_CreatureInfoGraphicMaskBack) && (creatureDirectionDelta == 0);
+ useCreatureAttackBitmap = !useCreatureBackBitmap;
+ if (useCreatureAttackBitmap && getFlag(creatureAspectInt, k0x0080_MaskActiveGroupIsAttacking) && getFlag(AL_0_creatureGraphicInfoRed, k0x0020_CreatureInfoGraphicMaskAttack)) {
+ useFlippedHorizontallyCreatureFrontImage = false;
+ sourceByteWidth = byteWidth = ((CreatureAspect *)objectAspect)->_byteWidthAttack;
+ sourceHeight = heightRedEagle = ((CreatureAspect *)objectAspect)->_heightAttack;
+ AL_4_nativeBitmapIndex++; /* Skip the front image */
+ derivedBitmapIndex += 2;
+ if (getFlag(AL_0_creatureGraphicInfoRed, k0x0008_CreatureInfoGraphicMaskSide)) {
+ AL_4_nativeBitmapIndex++; /* If the creature has a side image, it preceeds the attack image */
+ derivedBitmapIndex += 2;
+ }
+
+ if (getFlag(AL_0_creatureGraphicInfoRed, k0x0010_CreatureInfoGraphicMaskBack)) {
+ AL_4_nativeBitmapIndex++; /* If the creature has a back image, it preceeds the attack image */
+ derivedBitmapIndex += 2;
+ }
+ } else {
+ sourceByteWidth = byteWidth = ((CreatureAspect *)objectAspect)->_byteWidthFront;
+ sourceHeight = heightRedEagle = ((CreatureAspect *)objectAspect)->_heightFront;
+ if (useCreatureBackBitmap) {
+ useFlippedHorizontallyCreatureFrontImage = false;
+ if (getFlag(AL_0_creatureGraphicInfoRed, k0x0008_CreatureInfoGraphicMaskSide)) {
+ AL_4_nativeBitmapIndex += 2; /* If the creature has a side image, it preceeds the back image */
+ derivedBitmapIndex += 4;
+ } else {
+ AL_4_nativeBitmapIndex++; /* If the creature does not have a side image, the back image follows the front image */
+ derivedBitmapIndex += 2;
+ }
+ } else {
+ useFlippedHorizontallyCreatureFrontImage = getFlag(AL_0_creatureGraphicInfoRed, k0x0004_CreatureInfoGraphicMaskFlipNonAttack) && getFlag(creatureAspectInt, k0x0040_MaskActiveGroupFlipBitmap);
+ if (useFlippedHorizontallyCreatureFrontImage) {
+ derivedBitmapIndex += 2;
+ if (getFlag(AL_0_creatureGraphicInfoRed, k0x0008_CreatureInfoGraphicMaskSide))
+ derivedBitmapIndex += 2;
+
+ if (getFlag(AL_0_creatureGraphicInfoRed, k0x0010_CreatureInfoGraphicMaskBack))
+ derivedBitmapIndex += 2;
+
+ if (getFlag(AL_0_creatureGraphicInfoRed, k0x0020_CreatureInfoGraphicMaskAttack))
+ derivedBitmapIndex += 2;
+ }
+ }
+ }
+ }
+
+ int16 scale;
+ if (viewSquareIndex >= k6_ViewSquare_D1C) { /* Creature is on D1 */
+ creaturePaddingPixelCount = 0;
+ AL_8_shiftSetIndex = k0_ShiftSet_D0BackD1Front;
+ transparentColor = ((CreatureAspect *)objectAspect)->getTranspColour();
+ if (useCreatureSideBitmap) {
+ bitmapRedBanana = getNativeBitmapOrGraphic(AL_4_nativeBitmapIndex);
+ if (creatureDirectionDelta == 1) {
+ copyBitmapAndFlipHorizontal(bitmapRedBanana, _tmpBitmap, byteWidth, heightRedEagle);
+ bitmapRedBanana = _tmpBitmap;
+ }
+ } else if (useCreatureBackBitmap || !useFlippedHorizontallyCreatureFrontImage) {
+ bitmapRedBanana = getNativeBitmapOrGraphic(AL_4_nativeBitmapIndex);
+ if (useCreatureAttackBitmap && getFlag(creatureAspectInt, k0x0040_MaskActiveGroupFlipBitmap)) {
+ copyBitmapAndFlipHorizontal(bitmapRedBanana, _tmpBitmap, byteWidth, heightRedEagle);
+ bitmapRedBanana = _tmpBitmap;
+ }
+ } else if (isDerivedBitmapInCache(derivedBitmapIndex)) /* If derived graphic is already in memory */
+ bitmapRedBanana = getDerivedBitmap(derivedBitmapIndex);
+ else {
+ bitmapGreenAnt = getNativeBitmapOrGraphic(AL_4_nativeBitmapIndex);
+ if (getFlag(AL_0_creatureGraphicInfoRed, k0x0004_CreatureInfoGraphicMaskFlipNonAttack))
+ copyBitmapAndFlipHorizontal(bitmapGreenAnt, bitmapRedBanana = getDerivedBitmap(derivedBitmapIndex), byteWidth, heightRedEagle);
+
+ addDerivedBitmap(derivedBitmapIndex);
+ }
+ } else { /* Creature is on D2 or D3 */
+ if (useFlippedHorizontallyCreatureFrontImage)
+ derivedBitmapIndex++; /* Skip front D1 image in additional graphics */
+
+ byte* paletteChanges;
+ if (viewSquareIndex >= k3_ViewSquare_D2C) { /* Creature is on D2 */
+ derivedBitmapIndex++; /* Skip front D3 image in additional graphics */
+ AL_8_shiftSetIndex = k1_ShiftSet_D1BackD2Front;
+ useCreatureSpecialD2FrontBitmap = getFlag(AL_0_creatureGraphicInfoRed, k0x0080_CreatureInfoGraphicMaskSpecialD2Front) && !useCreatureSideBitmap && !useCreatureBackBitmap && !useCreatureAttackBitmap;
+ paletteChanges = _palChangesCreatureD2;
+ scale = k20_Scale_D2;
+ } else { /* Creature is on D3 */
+ AL_8_shiftSetIndex = k2_ShiftSet_D2BackD3Front;
+ useCreatureSpecialD2FrontBitmap = false;
+ paletteChanges = _palChangesCreatureD3;
+ scale = k16_Scale_D3;
+ }
+
+ byteWidth = getScaledDimension(sourceByteWidth, scale);
+ heightRedEagle = getScaledDimension(sourceHeight, scale);
+ transparentColor = paletteChanges[((CreatureAspect *)objectAspect)->getTranspColour()] / 10;
+
+ bool derivedBitmapInCache = isDerivedBitmapInCache(derivedBitmapIndex);
+ if (derivedBitmapInCache)
+ bitmapRedBanana = getDerivedBitmap(derivedBitmapIndex);
+ else {
+ bitmapGreenAnt = getNativeBitmapOrGraphic(AL_4_nativeBitmapIndex);
+ blitToBitmapShrinkWithPalChange(bitmapGreenAnt, bitmapRedBanana = getDerivedBitmap(derivedBitmapIndex), sourceByteWidth << 1, sourceHeight, byteWidth << 1, heightRedEagle, paletteChanges);
+ addDerivedBitmap(derivedBitmapIndex);
+ }
+ if ((useCreatureSideBitmap && (creatureDirectionDelta == 1)) || /* If creature is viewed from the right, the side view must be flipped */
+ (useCreatureAttackBitmap && getFlag(creatureAspectInt, k0x0040_MaskActiveGroupFlipBitmap)) ||
+ (useCreatureSpecialD2FrontBitmap && getFlag(AL_0_creatureGraphicInfoRed, k0x0100_CreatureInfoGraphicMaskSpecialD2FrontIsFlipped)) ||
+ (useFlippedHorizontallyCreatureFrontImage && getFlag(AL_0_creatureGraphicInfoRed, k0x0004_CreatureInfoGraphicMaskFlipNonAttack))) { /* If the graphic should be flipped */
+ if (!useFlippedHorizontallyCreatureFrontImage || !derivedBitmapInCache) {
+ AL_4_normalizdByteWidth = getNormalizedByteWidth(byteWidth);
+ if (!useFlippedHorizontallyCreatureFrontImage) {
+ memcpy(_tmpBitmap, bitmapRedBanana, sizeof(byte) * AL_4_normalizdByteWidth * heightRedEagle);
+ bitmapRedBanana = _tmpBitmap;
+ }
+ flipBitmapHorizontal(bitmapRedBanana, AL_4_normalizdByteWidth, heightRedEagle);
+ }
+ creaturePaddingPixelCount = (7 - ((byteWidth - 1) & 0x0007)) << 1;
+ } else
+ creaturePaddingPixelCount = 0;
+ }
+ AL_4_yPos = coordinateSet[1];
+ AL_4_yPos += shiftSets[AL_8_shiftSetIndex][getVerticalOffsetM23(creatureAspectInt)];
+ boxByteGreen._y2 = MIN(AL_4_yPos, (int16)135);
+ boxByteGreen._y1 = MAX(0, AL_4_yPos - (heightRedEagle - 1));
+ AL_4_xPos = coordinateSet[0];
+ AL_4_xPos += shiftSets[AL_8_shiftSetIndex][getHorizontalOffsetM22(creatureAspectInt)];
+
+ if (viewLane == k1_ViewLaneLeft)
+ AL_4_xPos -= 100;
+ else if (viewLane) /* Lane right */
+ AL_4_xPos += 100;
+
+ boxByteGreen._x2 = getBoundedValue(0, AL_4_xPos + byteWidth, 223);
+
+ if (!boxByteGreen._x2)
+ goto T0115126_CreatureNotVisible;
+ int16 AL_0_creaturePosX;
+ boxByteGreen._x1 = getBoundedValue(0, AL_4_xPos - byteWidth + 1, 223);
+ if (boxByteGreen._x1) {
+ if (boxByteGreen._x1 == 223)
+ goto T0115126_CreatureNotVisible;
+ AL_0_creaturePosX = creaturePaddingPixelCount;
+ } else
+ AL_0_creaturePosX = creaturePaddingPixelCount + (byteWidth - AL_4_xPos - 1);
+
+ blitToBitmap(bitmapRedBanana, _bitmapViewport, boxByteGreen, AL_0_creaturePosX, 0, getNormalizedByteWidth(byteWidth), k112_byteWidthViewport, (Color)transparentColor, heightRedEagle, 136);
+T0115126_CreatureNotVisible:
+ if (twoHalfSquareCreaturesFrontView) {
+ twoHalfSquareCreaturesFrontView = false;
+ creatureAspectInt = activeGroup->_aspect[!creatureIndexGreen]; /* Aspect of the other creature in the pair */
+ if (AL_2_viewCell == k1_HalfSizedViewCell_RightColumn)
+ AL_2_viewCell = k0_HalfSizedViewCell_LeftColumn;
+ else
+ AL_2_viewCell = k1_HalfSizedViewCell_RightColumn;
+
+ goto T0115077_DrawSecondHalfSquareCreature;
+ }
+ /* Draw projectiles */
+T0115129_DrawProjectiles:
+ //If there is no projectile to draw or if projectiles are not visible on the specified square or on the cell being drawn
+ if (!squareHasProjectile)
+ continue;
+ viewSquareIndex = AL_10_viewSquareIndexBackup;
+ if (viewSquareIndex > k9_ViewSquare_D0C)
+ continue;
+ AL_2_viewCell = currentViewCellToDraw;
+ projectilePosX = objectCoordinateSets[0][viewSquareIndex][AL_2_viewCell][0];
+ if (!projectilePosX) /* If there is no projectile to draw or if projectiles are not visible on the specified square or on the cell being drawn */
+ continue;
+
+ thingParam = firstThingToDraw; /* Restart processing list of objects from the beginning. The next loop draws only projectile objects among the list */
+ do {
+ if ((thingParam.getType() == k14_ProjectileThingType) && (thingParam.getCell() == cellYellowBear)) {
+ Projectile *projectile = (Projectile *)_vm->_dungeonMan->getThingData(thingParam);
+ if ((AL_4_projectileAspect = _vm->_dungeonMan->getProjectileAspect(projectile->_slot)) < 0) { /* Negative value: projectile aspect is the ordinal of a PROJECTIL_ASPECT */
+ objectAspect = (ObjectAspect *)&_projectileAspect[_vm->ordinalToIndex(-AL_4_projectileAspect)];
+ AL_4_nativeBitmapIndex = ((ProjectileAspect *)objectAspect)->_firstNativeBitmapRelativeIndex + k316_FirstProjectileGraphicIndice;
+ projectileAspectType = getFlag(((ProjectileAspect *)objectAspect)->_graphicInfo, k0x0003_ProjectileAspectTypeMask);
+
+ bool doNotScaleWithKineticEnergy = !getFlag(((ProjectileAspect *)objectAspect)->_graphicInfo, k0x0100_ProjectileScaleWithKineticEnergyMask);
+ if ((doNotScaleWithKineticEnergy || (projectile->_kineticEnergy == 255)) && (viewSquareIndex == k9_ViewSquare_D0C)) {
+ scale = 0; /* Use native bitmap without resizing */
+ byteWidth = ((ProjectileAspect *)objectAspect)->_byteWidth;
+ heightRedEagle = ((ProjectileAspect *)objectAspect)->_height;
+ } else {
+ AL_8_projectileScaleIndex = ((viewSquareIndex / 3) << 1) + (AL_2_viewCell >> 1);
+ scale = _projectileScales[AL_8_projectileScaleIndex];
+ if (!doNotScaleWithKineticEnergy) {
+ scale = (scale * MAX(96, projectile->_kineticEnergy + 1)) >> 8;
+ }
+ byteWidth = getScaledDimension(((ProjectileAspect *)objectAspect)->_byteWidth, scale);
+ heightRedEagle = getScaledDimension(((ProjectileAspect *)objectAspect)->_height, scale);
+ }
+ bool projectileAspectTypeHasBackGraphicAndRotation = (projectileAspectType == k0_ProjectileAspectHasBackGraphicRotation);
+ if (projectileAspectTypeHasBackGraphicAndRotation)
+ projectileFlipVertical = ((mapXpos + mapYpos) & 0x0001);
+
+ bool flipVertical;
+ if (projectileAspectType == k3_ProjectileAspectHasNone) {
+ projectileBitmapIndexDelta = 0;
+ flipVertical = flipHorizontal = false;
+ } else if (isOrientedWestEast(Direction(projectileDirection = _vm->_timeline->_events[projectile->_eventIndex]._C._projectile.getDir())) != isOrientedWestEast(directionParam)) {
+ if (projectileAspectType == k2_ProjectileAspectHasRotation)
+ projectileBitmapIndexDelta = 1;
+ else
+ projectileBitmapIndexDelta = 2;
+
+ if (projectileAspectTypeHasBackGraphicAndRotation) {
+ flipHorizontal = !AL_2_viewCell || (AL_2_viewCell == k3_ViewCellBackLeft);
+ if (!(flipVertical = projectileFlipVertical))
+ flipHorizontal = !flipHorizontal;
+ } else {
+ flipVertical = false;
+ flipHorizontal = (returnNextVal(directionParam) == projectileDirection);
+ }
+ } else {
+ if ((projectileAspectType >= k2_ProjectileAspectHasRotation) || ((projectileAspectType == k1_ProjectileAspectBackGraphic) && (projectileDirection != directionParam)) || (projectileAspectTypeHasBackGraphicAndRotation && projectileFlipVertical)) /* If the projectile does not have a back graphic or has one but is not seen from the back or if it has a back graphic and rotation and should be flipped vertically */
+ projectileBitmapIndexDelta = 0;
+ else
+ projectileBitmapIndexDelta = 1;
+
+ flipVertical = projectileAspectTypeHasBackGraphicAndRotation && (AL_2_viewCell < k2_ViewCellBackRight);
+ flipHorizontal = getFlag(((ProjectileAspect *)objectAspect)->_graphicInfo, k0x0010_ProjectileSideMask) && !((viewLane == k2_ViewLaneRight) || (!viewLane && ((AL_2_viewCell == k1_ViewCellFrontRight) || (AL_2_viewCell == k2_ViewCellBackRight))));
+ }
+
+ AL_4_nativeBitmapIndex += projectileBitmapIndexDelta;
+ paddingPixelCount = 0;
+ if (!scale) {
+ bitmapRedBanana = getNativeBitmapOrGraphic(AL_4_nativeBitmapIndex);
+ } else {
+ if (flipHorizontal)
+ paddingPixelCount = (7 - ((byteWidth - 1) & 0x0007)) << 1;
+
+ if (doNotScaleWithKineticEnergy && isDerivedBitmapInCache(derivedBitmapIndex = k282_DerivedBitmapFirstProjectile + ((ProjectileAspect *)objectAspect)->_firstDerivedBitmapRelativeIndex + (projectileBitmapIndexDelta * 6) + AL_8_projectileScaleIndex)) {
+ bitmapRedBanana = getDerivedBitmap(derivedBitmapIndex);
+ } else {
+ bitmapGreenAnt = getNativeBitmapOrGraphic(AL_4_nativeBitmapIndex);
+ if (doNotScaleWithKineticEnergy)
+ bitmapRedBanana = getDerivedBitmap(derivedBitmapIndex);
+ else
+ bitmapRedBanana = _tmpBitmap;
+
+ blitToBitmapShrinkWithPalChange(bitmapGreenAnt, bitmapRedBanana, ((ProjectileAspect *)objectAspect)->_byteWidth << 1, ((ProjectileAspect *)objectAspect)->_height, byteWidth << 1, heightRedEagle, _palChangesProjectile[AL_8_projectileScaleIndex >> 1]);
+ if (doNotScaleWithKineticEnergy) {
+ addDerivedBitmap(derivedBitmapIndex);
+ }
+ }
+ }
+ if (flipHorizontal || flipVertical) {
+ AL_4_normalizdByteWidth = getNormalizedByteWidth(byteWidth);
+ if (bitmapRedBanana != _tmpBitmap) {
+ memcpy(_tmpBitmap, bitmapRedBanana, sizeof(byte) * AL_4_normalizdByteWidth * heightRedEagle);
+ bitmapRedBanana = _tmpBitmap;
+ }
+ if (flipVertical)
+ flipBitmapVertical(bitmapRedBanana, AL_4_normalizdByteWidth, heightRedEagle);
+
+ if (flipHorizontal)
+ flipBitmapHorizontal(bitmapRedBanana, AL_4_normalizdByteWidth, heightRedEagle);
+ }
+ boxByteGreen._y2 = (heightRedEagle >> 1) + 47;
+ boxByteGreen._y1 = 47 - (heightRedEagle >> 1) + !(heightRedEagle & 0x0001);
+ boxByteGreen._x2 = MIN(223, projectilePosX + byteWidth);
+ boxByteGreen._x1 = MAX(0, projectilePosX - byteWidth + 1);
+ if (boxByteGreen._x1) {
+ if (flipHorizontal)
+ AL_4_xPos = paddingPixelCount;
+ else
+ AL_4_xPos = 0;
+ } else
+ AL_4_xPos = MAX(paddingPixelCount, int16(byteWidth - projectilePosX - 1)); /* BUG0_06 Graphical glitch when drawing projectiles or explosions. If a projectile or explosion bitmap is cropped because it is only partly visible on the left side of the viewport (boxByteGreen.X1 = 0) and the bitmap is flipped horizontally (flipHorizontal = true) then a wrong part of the bitmap is drawn on screen. To fix this bug, "+ paddingPixelCount" must be added to the second parameter of this function call */
+
+ blitToBitmap(bitmapRedBanana, _bitmapViewport, boxByteGreen, AL_4_xPos, 0, getNormalizedByteWidth(byteWidth), k112_byteWidthViewport, k10_ColorFlesh, heightRedEagle, k136_heightViewport);
+ } else { /* Positive value: projectile aspect is the index of a OBJECT_ASPECT */
+ useAlcoveObjectImage = false;
+ byte projectileCoordinates[2];
+ projectileCoordinates[0] = projectilePosX;
+ projectileCoordinates[1] = 47;
+ coordinateSet = projectileCoordinates;
+ objectAspect = &_objectAspects209[AL_4_projectileAspect];
+ AL_4_nativeBitmapIndex = objectAspect->_firstNativeBitmapRelativeIndex + k360_FirstObjectGraphicIndice;
+ drawProjectileAsObject = true;
+ goto T0115015_DrawProjectileAsObject; /* Go to code section to draw an object. Once completed, it jumps back to T0115171_BackFromT0115015_DrawProjectileAsObject below */
+ }
+ }
+T0115171_BackFromT0115015_DrawProjectileAsObject:;
+ } while ((thingParam = _vm->_dungeonMan->getNextThing(thingParam)) != Thing::_endOfList);
+ } while (remainingViewCellOrdinalsToProcess);
+
+ /* Draw explosions */
+ if (!squareHasExplosion)
+ return;
+
+ Explosion *fluxcageExplosion = nullptr;
+ int16 *explosionCoordinates;
+
+ AL_1_viewSquareExplosionIndex = AL_10_viewSquareIndexBackup + 3; /* Convert square index to square index for explosions */
+ AL_10_explosionScaleIndex = AL_1_viewSquareExplosionIndex / 3;
+ thingParam = firstThingToDraw; /* Restart processing list of things from the beginning. The next loop draws only explosion things among the list */
+ do {
+ if (thingParam.getType() == k15_ExplosionThingType) {
+ AL_2_cellPurpleMan = thingParam.getCell();
+ Explosion *explosion = (Explosion *)_vm->_dungeonMan->getThingData(thingParam);
+ bool rebirthExplosion = ((uint16)(AL_4_explosionType = explosion->getType()) >= k100_ExplosionType_RebirthStep1);
+ if (rebirthExplosion && ((AL_1_viewSquareExplosionIndex < k3_ViewSquare_D3C_Explosion) || (AL_1_viewSquareExplosionIndex > k9_ViewSquare_D1C_Explosion) || (AL_2_cellPurpleMan != cellYellowBear))) /* If explosion is rebirth and is not visible */
+ continue;
+ bool smoke = false;
+ if ((AL_4_explosionType == k0_ExplosionType_Fireball) || (AL_4_explosionType == k2_ExplosionType_LightningBolt) || (AL_4_explosionType == k101_ExplosionType_RebirthStep2)) {
+ AL_4_explosionAspectIndex = k0_ExplosionAspectFire;
+ } else {
+ if ((AL_4_explosionType == k6_ExplosionType_PoisonBolt) || (AL_4_explosionType == k7_ExplosionType_PoisonCloud)) {
+ AL_4_explosionAspectIndex = k2_ExplosionAspectPoison;
+ } else {
+ if (AL_4_explosionType == k40_ExplosionType_Smoke) {
+ smoke = true;
+ AL_4_explosionAspectIndex = k3_ExplosionAspectSmoke;
+ } else {
+ if (AL_4_explosionType == k100_ExplosionType_RebirthStep1) {
+ objectAspect = (ObjectAspect *)&_projectileAspect[_vm->ordinalToIndex(-_vm->_dungeonMan->getProjectileAspect(Thing::_explLightningBolt))];
+ bitmapRedBanana = getNativeBitmapOrGraphic(((ProjectileAspect *)objectAspect)->_firstNativeBitmapRelativeIndex + (k316_FirstProjectileGraphicIndice + 1));
+ explosionCoordinates = rebirthStep1ExplosionCoordinates[AL_1_viewSquareExplosionIndex - 3];
+ byteWidth = getScaledDimension((((ProjectileAspect *)objectAspect)->_byteWidth), explosionCoordinates[2]);
+ heightRedEagle = getScaledDimension((((ProjectileAspect *)objectAspect)->_height), explosionCoordinates[2]);
+ if (AL_1_viewSquareExplosionIndex != k9_ViewSquare_D1C_Explosion) {
+ blitToBitmapShrinkWithPalChange(bitmapRedBanana, _tmpBitmap, ((ProjectileAspect *)objectAspect)->_byteWidth << 1, ((ProjectileAspect *)objectAspect)->_height, byteWidth << 1, heightRedEagle, _palChangesNoChanges);
+ bitmapRedBanana = _tmpBitmap;
+ }
+ goto T0115200_DrawExplosion;
+ }
+ if (AL_4_explosionType == k50_ExplosionType_Fluxcage) {
+ if (AL_1_viewSquareExplosionIndex >= k4_ViewSquare_D3L_Explosion) {
+ fluxcageExplosion = explosion;
+ }
+ continue;
+ }
+ AL_4_explosionAspectIndex = k1_ExplosionAspectSpell;
+ }
+ }
+ }
+ if (AL_1_viewSquareExplosionIndex == k12_ViewSquare_D0C_Explosion) {
+ if (smoke)
+ AL_4_explosionAspectIndex--; /* Smoke uses the same graphics as Poison Cloud, but with palette changes */
+
+ AL_4_explosionAspectIndex = AL_4_explosionAspectIndex * 3; /* 3 graphics per explosion pattern */
+ AL_2_explosionSize = (explosion->getAttack() >> 5);
+ if (AL_2_explosionSize) {
+ AL_4_explosionAspectIndex++; /* Use second graphic in the pattern for medium explosion attack */
+ if (AL_2_explosionSize > 3)
+ AL_4_explosionAspectIndex++; /* Use third graphic in the pattern for large explosion attack */
+ }
+ isDerivedBitmapInCache(k0_DerivedBitmapViewport);
+ bitmapRedBanana = getNativeBitmapOrGraphic(AL_4_explosionAspectIndex + k351_FirstExplosionPatternGraphicIndice);
+ if (smoke) {
+ blitToBitmapShrinkWithPalChange(bitmapRedBanana, _tmpBitmap, 48, 32, 48, 32, _palChangeSmoke);
+ bitmapRedBanana = _tmpBitmap;
+ }
+ blitBoxFilledWithMaskedBitmap(bitmapRedBanana, _bitmapViewport, 0, getDerivedBitmap(k0_DerivedBitmapViewport), boxExplosionPatternD0C, _vm->getRandomNumber(4) + 87, _vm->getRandomNumber(64), k112_byteWidthViewport, Color(k0x0080_BlitDoNotUseMask | k10_ColorFlesh), 0, 0, 136, 93);
+ addDerivedBitmap(k0_DerivedBitmapViewport);
+ warning("DISABLED CODE: f480_releaseBlock in drawObjectsCreaturesProjectilesExplosions");
+ //f480_releaseBlock(k0_DerivedBitmapViewport | 0x8000);
+ } else {
+ int16 explosionScale;
+ if (rebirthExplosion) {
+ explosionCoordinates = rebirthStep2ExplosionCoordinates[AL_1_viewSquareExplosionIndex - 3];
+ explosionScale = explosionCoordinates[2];
+ } else {
+ if (explosion->getCentered()) {
+ explosionCoordinates = centeredExplosionCoordinates[AL_1_viewSquareExplosionIndex];
+ } else {
+ if ((AL_2_cellPurpleMan == directionParam) || (AL_2_cellPurpleMan == returnPrevVal(directionParam)))
+ AL_2_viewCell = k0_ViewCellFronLeft;
+ else
+ AL_2_viewCell = k1_ViewCellFrontRight;
+
+ explosionCoordinates = explosionCoordinatesArray[AL_1_viewSquareExplosionIndex][AL_2_viewCell];
+ }
+ explosionScale = MAX(4, (MAX(48, explosion->getAttack() + 1) * explosionBaseScales[AL_10_explosionScaleIndex]) >> 8) & (int)0xFFFE;
+ }
+ bitmapRedBanana = getExplosionBitmap(AL_4_explosionAspectIndex, explosionScale, byteWidth, heightRedEagle);
+T0115200_DrawExplosion:
+ bool flipVertical = _vm->getRandomNumber(2);
+ paddingPixelCount = 0;
+ flipHorizontal = _vm->getRandomNumber(2);
+ if (flipHorizontal)
+ paddingPixelCount = (7 - ((byteWidth - 1) & 0x0007)) << 1; /* Number of unused pixels in the units on the right of the bitmap */
+
+ boxByteGreen._y2 = MIN(135, explosionCoordinates[1] + (heightRedEagle >> 1));
+ AL_4_yPos = MAX(0, explosionCoordinates[1] - (heightRedEagle >> 1) + !(heightRedEagle & 0x0001));
+ if (AL_4_yPos >= 136)
+ continue;
+ boxByteGreen._y1 = AL_4_yPos;
+ AL_4_xPos = MIN(223, explosionCoordinates[0] + byteWidth);
+ if (AL_4_xPos < 0)
+ continue;
+ boxByteGreen._x2 = AL_4_xPos;
+ AL_4_xPos = explosionCoordinates[0];
+ boxByteGreen._x1 = getBoundedValue(0, AL_4_xPos - byteWidth + 1, 223);
+
+ if (boxByteGreen._x1)
+ AL_4_xPos = paddingPixelCount;
+ else {
+ AL_4_xPos = MAX(paddingPixelCount, int16(byteWidth - AL_4_xPos - 1)); /* BUG0_07 Graphical glitch when drawing explosions. If an explosion bitmap is cropped because it is only partly visible on the left side of the viewport (boxByteGreen.X1 = 0) and the bitmap is not flipped horizontally (flipHorizontal = false) then the variable paddingPixelCount is not set before being used here. Its previous value (defined while drawing something else) is used and may cause an incorrect bitmap to be drawn */
+
+ /* BUG0_06 Graphical glitch when drawing projectiles or explosions. If a projectile or explosion bitmap is cropped because it is only partly visible on the left side of the viewport (boxByteGreen.X1 = 0) and the bitmap is flipped horizontally (flipHorizontal = true) then a wrong part of the bitmap is drawn on screen. To fix this bug, "+ paddingPixelCount" must be added to the second parameter of this function call */
+ }
+
+ if (boxByteGreen._x2 <= boxByteGreen._x1)
+ continue;
+
+ byteWidth = getNormalizedByteWidth(byteWidth);
+ if (flipHorizontal || flipVertical) {
+ memcpy(_tmpBitmap, bitmapRedBanana, sizeof(byte) * byteWidth * heightRedEagle);
+ bitmapRedBanana = _tmpBitmap;
+ }
+
+ if (flipHorizontal)
+ flipBitmapHorizontal(bitmapRedBanana, byteWidth, heightRedEagle);
+
+ if (flipVertical)
+ flipBitmapVertical(bitmapRedBanana, byteWidth, heightRedEagle);
+
+ blitToBitmap(bitmapRedBanana, _bitmapViewport, boxByteGreen, AL_4_xPos, 0, byteWidth, k112_byteWidthViewport, k10_ColorFlesh, heightRedEagle, k136_heightViewport);
+ }
+ }
+ } while ((thingParam = _vm->_dungeonMan->getNextThing(thingParam))!= Thing::_endOfList);
+
+ if ((fluxcageExplosion != 0) && (doorFrontViewDrawingPass != 1) && !_doNotDrawFluxcagesDuringEndgame) { /* Fluxcage is an explosion displayed as a field (like teleporters), above all other graphics */
+ AL_1_viewSquareExplosionIndex -= 3; /* Convert square index for explosions back to square index */
+ FieldAspect fieldAspect = _fieldAspects188[viewSquareIndex];
+ (fieldAspect._nativeBitmapRelativeIndex)++; /* NativeBitmapRelativeIndex is now the index of the Fluxcage field graphic */
+ drawField(&fieldAspect, _frameWalls163[viewSquareIndex]._box);
+ }
+}
+
+uint16 DisplayMan::getNormalizedByteWidth(uint16 byteWidth) {
+ return (byteWidth + 7) & 0xFFF8;
+}
+
+uint16 DisplayMan::getVerticalOffsetM23(uint16 val) {
+ return (val >> 3) & 0x7;
+}
+
+uint16 DisplayMan::getHorizontalOffsetM22(uint16 val) {
+ return (val & 0x7);
+}
+
+bool DisplayMan::isDerivedBitmapInCache(int16 derivedBitmapIndex) {
+ if (_derivedBitmaps[derivedBitmapIndex] == nullptr) {
+ // * 2, because the original uses 4 bits instead of 8 bits to store a pixel
+ _derivedBitmaps[derivedBitmapIndex] = new byte[_derivedBitmapByteCount[derivedBitmapIndex] * 2];
+ return false;
+ }
+
+ return true;
+}
+
+byte* DisplayMan::getDerivedBitmap(int16 derivedBitmapIndex) {
+ return _derivedBitmaps[derivedBitmapIndex];
+}
+
+void DisplayMan::addDerivedBitmap(int16 derivedBitmapIndex) {}
+
+void DisplayMan::releaseBlock(uint16 index) {
+ index &= ~0x8000;
+ delete[] _derivedBitmaps[index];
+ _derivedBitmaps[index] = nullptr;
+}
+
+uint16 DisplayMan::getDarkenedColor(uint16 RGBcolor) {
+ if (getFlag(RGBcolor, D12_MASK_BLUE_COMPONENT))
+ RGBcolor--;
+
+ if (getFlag(RGBcolor, D11_MASK_GREEN_COMPONENT))
+ RGBcolor -= 16;
+
+ if (getFlag(RGBcolor, D10_MASK_RED_COMPONENT))
+ RGBcolor -= 256;
+
+ return RGBcolor;
+}
+
+void DisplayMan::startEndFadeToPalette(uint16* P0849_pui_Palette) {
+ uint16 *paletteRegister = _paletteFadeTemporary;
+
+ for (int16 i = 0; i < 16; i++)
+ paletteRegister[i] = _paletteFadeFrom[i];
+
+ for (int16 i = 0; i < 8; i++) {
+ paletteRegister = _paletteFadeTemporary;
+ for (int16 colIdx = 0; colIdx < 16; colIdx++, paletteRegister++) {
+ uint16 currentRGBColor = getFlag(*paletteRegister, D12_MASK_BLUE_COMPONENT);
+ int16 targetRGBColor = getFlag(P0849_pui_Palette[colIdx], D12_MASK_BLUE_COMPONENT);
+ if (currentRGBColor > targetRGBColor) {
+ if (currentRGBColor > targetRGBColor + 1)
+ *paletteRegister -= 2;
+ else
+ *paletteRegister -= 1;
+ } else if (currentRGBColor < targetRGBColor) {
+ if (currentRGBColor < targetRGBColor - 1)
+ *paletteRegister += 2;
+ else
+ *paletteRegister += 1;
+ }
+ currentRGBColor = getFlag(*paletteRegister, D11_MASK_GREEN_COMPONENT) >> 4;
+ targetRGBColor = getFlag(P0849_pui_Palette[colIdx], D11_MASK_GREEN_COMPONENT) >> 4;
+ if (currentRGBColor > targetRGBColor) {
+ if (currentRGBColor > targetRGBColor + 1)
+ *paletteRegister -= 32;
+ else
+ *paletteRegister -= 16;
+ } else if (currentRGBColor < targetRGBColor) {
+ if (currentRGBColor < targetRGBColor - 1)
+ *paletteRegister += 32;
+ else
+ *paletteRegister += 16;
+ }
+ currentRGBColor = getFlag(*paletteRegister, D10_MASK_RED_COMPONENT) >> 8;
+ targetRGBColor = getFlag(P0849_pui_Palette[colIdx], D10_MASK_RED_COMPONENT) >> 8;
+ if (currentRGBColor > targetRGBColor) {
+ if (currentRGBColor > targetRGBColor + 1)
+ *paletteRegister -= 512;
+ else
+ *paletteRegister -= 256;
+ } else if (currentRGBColor < targetRGBColor) {
+ if (currentRGBColor < targetRGBColor - 1)
+ *paletteRegister += 512;
+ else
+ *paletteRegister += 256;
+ }
+ }
+ _vm->delay(1);
+ _vm->_eventMan->discardAllInput();
+ buildPaletteChangeCopperList(_paletteFadeTemporary, _paletteFadeTemporary);
+ }
+}
+
+void DisplayMan::buildPaletteChangeCopperList(uint16* middleScreen, uint16* topAndBottom) {
+ _paletteFadeFrom = topAndBottom;
+ byte colorPalette[32 * 3];
+ for (int i = 0; i < 16; ++i) {
+ colorPalette[i * 3] = (topAndBottom[i] >> 8) * (256 / 16);
+ colorPalette[i * 3 + 1] = (topAndBottom[i] >> 4) * (256 / 16);
+ colorPalette[i * 3 + 2] = topAndBottom[i] * (256 / 16);
+ }
+ for (int i = 16; i < 32; ++i) {
+ colorPalette[i * 3] = (middleScreen[i - 16] >> 8) * (256 / 16);
+ colorPalette[i * 3 + 1] = (middleScreen[i - 16] >> 4) * (256 / 16);
+ colorPalette[i * 3 + 2] = middleScreen[i - 16] * (256 / 16);
+ }
+ g_system->getPaletteManager()->setPalette(colorPalette, 0, 32);
+}
+
+}
diff --git a/engines/dm/gfx.h b/engines/dm/gfx.h
new file mode 100644
index 0000000000..76f08e8859
--- /dev/null
+++ b/engines/dm/gfx.h
@@ -0,0 +1,832 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_GFX_H
+#define DM_GFX_H
+
+#include "common/scummsys.h"
+#include "common/rect.h"
+#include "common/memstream.h"
+#include "common/array.h"
+
+#include "dm/dm.h"
+
+namespace DM {
+
+#define k0_viewFloor_D3L 0 // @ C0_VIEW_FLOOR_D3L
+#define k1_viewFloor_D3C 1 // @ C1_VIEW_FLOOR_D3C
+#define k2_viewFloor_D3R 2 // @ C2_VIEW_FLOOR_D3R
+#define k3_viewFloor_D2L 3 // @ C3_VIEW_FLOOR_D2L
+#define k4_viewFloor_D2C 4 // @ C4_VIEW_FLOOR_D2C
+#define k5_viewFloor_D2R 5 // @ C5_VIEW_FLOOR_D2R
+#define k6_viewFloor_D1L 6 // @ C6_VIEW_FLOOR_D1L
+#define k7_viewFloor_D1C 7 // @ C7_VIEW_FLOOR_D1C
+#define k8_viewFloor_D1R 8 // @ C8_VIEW_FLOOR_D1R
+
+#define k0_doorState_OPEN 0 // @ C0_DOOR_STATE_OPEN
+#define k1_doorState_FOURTH 1 // @ C1_DOOR_STATE_CLOSED_ONE_FOURTH
+#define k2_doorState_HALF 2 // @ k2_DoorStateAspect_CLOSED_HALF
+#define k3_doorState_FOURTH 3 // @ C3_DOOR_STATE_CLOSED_THREE_FOURTH
+#define k4_doorState_CLOSED 4 // @ C4_DOOR_STATE_CLOSED
+#define k5_doorState_DESTROYED 5 // @ C5_DOOR_STATE_DESTROYED
+
+#define k0_ViewDoorOrnament_D3LCR 0 // @ C0_VIEW_DOOR_ORNAMENT_D3LCR
+#define k1_ViewDoorOrnament_D2LCR 1 // @ C1_VIEW_DOOR_ORNAMENT_D2LCR
+#define k2_ViewDoorOrnament_D1LCR 2 // @ C2_VIEW_DOOR_ORNAMENT_D1LCR
+
+#define k0_viewDoorButton_D3R 0 // @ C0_VIEW_DOOR_BUTTON_D3R
+#define k1_viewDoorButton_D3C 1 // @ C1_VIEW_DOOR_BUTTON_D3C
+#define k2_viewDoorButton_D2C 2 // @ C2_VIEW_DOOR_BUTTON_D2C
+#define k3_viewDoorButton_D1C 3 // @ C3_VIEW_DOOR_BUTTON_D1C
+
+#define k0x0001_MaskDoorInfo_CraturesCanSeeThrough 0x0001 // @ MASK0x0001_CREATURES_CAN_SEE_THROUGH
+#define k0x0002_MaskDoorInfo_ProjectilesCanPassThrough 0x0002 // @ MASK0x0002_PROJECTILES_CAN_PASS_THROUGH
+#define k0x0004_MaskDoorInfo_Animated 0x0004 // @ MASK0x0004_ANIMATED
+
+
+#define k2_FloorSetGraphicCount 2 // @ C002_FLOOR_SET_GRAPHIC_COUNT
+#define k13_WallSetGraphicCount 13 // @ C013_WALL_SET_GRAPHIC_COUNT
+#define k18_StairsGraphicCount 18 // @ C018_STAIRS_GRAPHIC_COUNT
+#define k3_DoorSetGraphicsCount 3 // @ C003_DOOR_SET_GRAPHIC_COUNT
+#define k1_DoorButtonCount 1 // @ C001_DOOR_BUTTON_COUNT
+#define k3_AlcoveOrnCount 3 // @ C003_ALCOVE_ORNAMENT_COUNT
+#define k1_FountainOrnCount 1 // @ C001_FOUNTAIN_ORNAMENT_COUNT
+#define k27_CreatureTypeCount 27 // @ C027_CREATURE_TYPE_COUNT
+#define k4_ExplosionAspectCount 4 // @ C004_EXPLOSION_ASPECT_COUNT
+#define k14_ProjectileAspectCount 14 // @ C014_PROJECTILE_ASPECT_COUNT
+#define k85_ObjAspectCount 85 // @ C085_OBJECT_ASPECT_COUNT
+
+#define k0_NativeBitmapIndex 0 // @ C0_NATIVE_BITMAP_INDEX
+#define k1_CoordinateSet 1 // @ C1_COORDINATE_SET
+
+/* View lanes */
+#define k0_ViewLaneCenter 0 // @ C0_VIEW_LANE_CENTER
+#define k1_ViewLaneLeft 1 // @ C1_VIEW_LANE_LEFT
+#define k2_ViewLaneRight 2 // @ C2_VIEW_LANE_RIGHT
+
+#define k0_HalfSizedViewCell_LeftColumn 0 // @ C00_VIEW_CELL_LEFT_COLUMN
+#define k1_HalfSizedViewCell_RightColumn 1 // @ C01_VIEW_CELL_RIGHT_COLUMN
+#define k2_HalfSizedViewCell_BackRow 2 // @ C02_VIEW_CELL_BACK_ROW
+#define k3_HalfSizedViewCell_CenterColumn 3 // @ C03_VIEW_CELL_CENTER_COLUMN
+#define k4_HalfSizedViewCell_FrontRow 4 // @ C04_VIEW_CELL_FRONT_ROW
+
+/* Shift sets */
+#define k0_ShiftSet_D0BackD1Front 0 // @ C0_SHIFT_SET_D0_BACK_OR_D1_FRONT
+#define k1_ShiftSet_D1BackD2Front 1 // @ C1_SHIFT_SET_D1_BACK_OR_D2_FRONT
+#define k2_ShiftSet_D2BackD3Front 2 // @ C2_SHIFT_SET_D2_BACK_OR_D3_FRONT
+
+#define k0x0008_CellOrder_DoorFront 0x0008 // @ MASK0x0008_DOOR_FRONT
+#define k0x0000_CellOrder_Alcove 0x0000 // @ C0000_CELL_ORDER_ALCOVE
+#define k0x0001_CellOrder_BackLeft 0x0001 // @ C0001_CELL_ORDER_BACKLEFT
+#define k0x0002_CellOrder_BackRight 0x0002 // @ C0002_CELL_ORDER_BACKRIGHT
+#define k0x0018_CellOrder_DoorPass1_BackLeft 0x0018 // @ C0018_CELL_ORDER_DOORPASS1_BACKLEFT
+#define k0x0021_CellOrder_BackLeft_BackRight 0x0021 // @ C0021_CELL_ORDER_BACKLEFT_BACKRIGHT
+#define k0x0028_CellOrder_DoorPass1_BackRight 0x0028 // @ C0028_CELL_ORDER_DOORPASS1_BACKRIGHT
+#define k0x0032_CellOrder_BackRight_FrontRight 0x0032 // @ C0032_CELL_ORDER_BACKRIGHT_FRONTRIGHT
+#define k0x0039_CellOrder_DoorPass2_FrontRight 0x0039 // @ C0039_CELL_ORDER_DOORPASS2_FRONTRIGHT
+#define k0x0041_CellOrder_BackLeft_FrontLeft 0x0041 // @ C0041_CELL_ORDER_BACKLEFT_FRONTLEFT
+#define k0x0049_CellOrder_DoorPass2_FrontLeft 0x0049 // @ C0049_CELL_ORDER_DOORPASS2_FRONTLEFT
+#define k0x0128_CellOrder_DoorPass1_BackRight_BackLeft 0x0128 // @ C0128_CELL_ORDER_DOORPASS1_BACKRIGHT_BACKLEFT
+#define k0x0218_CellOrder_DoorPass1_BackLeft_BackRight 0x0218 // @ C0218_CELL_ORDER_DOORPASS1_BACKLEFT_BACKRIGHT
+#define k0x0321_CellOrder_BackLeft_BackRight_FrontRight 0x0321 // @ C0321_CELL_ORDER_BACKLEFT_BACKRIGHT_FRONTRIGHT
+#define k0x0342_CellOrder_BackRight_FrontLeft_FrontRight 0x0342 // @ C0342_CELL_ORDER_BACKRIGHT_FRONTLEFT_FRONTRIGHT
+#define k0x0349_CellOrder_DoorPass2_FrontLeft_FrontRight 0x0349 // @ C0349_CELL_ORDER_DOORPASS2_FRONTLEFT_FRONTRIGHT
+#define k0x0412_CellOrder_BackRight_BackLeft_FrontLeft 0x0412 // @ C0412_CELL_ORDER_BACKRIGHT_BACKLEFT_FRONTLEFT
+#define k0x0431_CellOrder_BackLeft_FrontRight_FrontLeft 0x0431 // @ C0431_CELL_ORDER_BACKLEFT_FRONTRIGHT_FRONTLEFT
+#define k0x0439_CellOrder_DoorPass2_FrontRight_FrontLeft 0x0439 // @ C0439_CELL_ORDER_DOORPASS2_FRONTRIGHT_FRONTLEFT
+#define k0x3421_CellOrder_BackLeft_BackRight_FrontLeft_FrontRight 0x3421 // @ C3421_CELL_ORDER_BACKLEFT_BACKRIGHT_FRONTLEFT_FRONTRIGHT
+#define k0x4312_CellOrder_BackRight_BackLeft_FrontRight_FrontLeft 0x4312 // @ C4312_CELL_ORDER_BACKRIGHT_BACKLEFT_FRONTRIGHT_FRONTLEFT
+
+/* Explosion aspects */
+#define k0_ExplosionAspectFire 0 // @ C0_EXPLOSION_ASPECT_FIRE
+#define k1_ExplosionAspectSpell 1 // @ C1_EXPLOSION_ASPECT_SPELL
+#define k2_ExplosionAspectPoison 2 // @ C2_EXPLOSION_ASPECT_POISON
+#define k3_ExplosionAspectSmoke 3 // @ C3_EXPLOSION_ASPECT_SMOKE
+
+/* Creature info GraphicInfo */
+#define k0x0003_CreatureInfoGraphicMaskAdditional 0x0003 // @ MASK0x0003_ADDITIONAL
+#define k0x0004_CreatureInfoGraphicMaskFlipNonAttack 0x0004 // @ MASK0x0004_FLIP_NON_ATTACK
+#define k0x0008_CreatureInfoGraphicMaskSide 0x0008 // @ MASK0x0008_SIDE
+#define k0x0010_CreatureInfoGraphicMaskBack 0x0010 // @ MASK0x0010_BACK
+#define k0x0020_CreatureInfoGraphicMaskAttack 0x0020 // @ MASK0x0020_ATTACK
+#define k0x0080_CreatureInfoGraphicMaskSpecialD2Front 0x0080 // @ MASK0x0080_SPECIAL_D2_FRONT
+#define k0x0100_CreatureInfoGraphicMaskSpecialD2FrontIsFlipped 0x0100 // @ MASK0x0100_SPECIAL_D2_FRONT_IS_FLIPPED_FRONT
+#define k0x0200_CreatureInfoGraphicMaskFlipAttack 0x0200 // @ MASK0x0200_FLIP_ATTACK
+#define k0x0400_CreatureInfoGraphicMaskFlipDuringAttack 0x0400 // @ MASK0x0400_FLIP_DURING_ATTACK
+
+#define k75_FirstFloorSet 75 // @ C075_GRAPHIC_FIRST_FLOOR_SET
+#define k77_FirstWallSet 77 // @ C077_GRAPHIC_FIRST_WALL_SET
+#define k90_FirstStairs 90 // @ C090_GRAPHIC_FIRST_STAIRS
+#define k108_FirstDoorSet 108 // @ C108_GRAPHIC_FIRST_DOOR_SET
+#define k120_InscriptionFont 120 // @ C120_GRAPHIC_INSCRIPTION_FONT
+#define k121_FirstWallOrn 121 // @ C121_GRAPHIC_FIRST_WALL_ORNAMENT
+#define k247_FirstFloorOrn 247 // @ C247_GRAPHIC_FIRST_FLOOR_ORNAMENT
+#define k303_FirstDoorOrn 303 // @ C303_GRAPHIC_FIRST_DOOR_ORNAMENT
+#define k730_DerivedBitmapMaximumCount 730 // @ C730_DERIVED_BITMAP_MAXIMUM_COUNT
+
+/* Field Aspect Mask */
+#define kMaskFieldAspectFlipMask 0x0080 // @ MASK0x0080_FLIP_MASK
+#define kMaskFieldAspectIndex 0x007F // @ MASK0x007F_MASK_INDEX
+#define kMaskFieldAspectNoMask 255 // @ C255_NO_MASK
+
+enum ViewSquare {
+ kM3_ViewSquare_D4C = -3, // @ CM3_VIEW_SQUARE_D4C
+ kM2_ViewSquare_D4L = -2, // @ CM2_VIEW_SQUARE_D4L
+ kM1_ViewSquare_D4R = -1, // @ CM1_VIEW_SQUARE_D4R
+ k0_ViewSquare_D3C = 0, // @ C00_VIEW_SQUARE_D3C
+ k1_ViewSquare_D3L = 1, // @ C01_VIEW_SQUARE_D3L
+ k2_ViewSquare_D3R = 2, // @ C02_VIEW_SQUARE_D3R
+ k3_ViewSquare_D2C = 3, // @ C03_VIEW_SQUARE_D2C
+ k4_ViewSquare_D2L = 4, // @ C04_VIEW_SQUARE_D2L
+ k5_ViewSquare_D2R = 5, // @ C05_VIEW_SQUARE_D2R
+ k6_ViewSquare_D1C = 6, // @ C06_VIEW_SQUARE_D1C
+ k7_ViewSquare_D1L = 7, // @ C07_VIEW_SQUARE_D1L
+ k8_ViewSquare_D1R = 8, // @ C08_VIEW_SQUARE_D1R
+ k9_ViewSquare_D0C = 9, // @ C09_VIEW_SQUARE_D0C
+ k10_ViewSquare_D0L = 10, // @ C10_VIEW_SQUARE_D0L
+ k11_ViewSquare_D0R = 11, // @ C11_VIEW_SQUARE_D0R
+ k3_ViewSquare_D3C_Explosion = 3, // @ C03_VIEW_SQUARE_D3C_EXPLOSION
+ k4_ViewSquare_D3L_Explosion = 4, // @ C04_VIEW_SQUARE_D3L_EXPLOSION
+ k9_ViewSquare_D1C_Explosion = 9, // @ C09_VIEW_SQUARE_D1C_EXPLOSION
+ k12_ViewSquare_D0C_Explosion = 12 // @ C12_VIEW_SQUARE_D0C_EXPLOSION
+};
+
+class ExplosionAspect {
+public:
+ uint16 _byteWidth;
+ uint16 _height;
+
+ ExplosionAspect(uint16 byteWidth, uint16 height) :_byteWidth(byteWidth), _height(height) {}
+ ExplosionAspect() : _byteWidth(0), _height(0) {}
+}; // @ EXPLOSION_ASPECT
+
+extern ExplosionAspect g211_ExplosionAspects[k4_ExplosionAspectCount]; // @ G0211_as_Graphic558_ExplosionAspects
+
+extern byte g215_ProjectileScales[7]; // @ G0215_auc_Graphic558_ProjectileScales
+
+
+#define k0_DerivedBitmapViewport 0 // @ C000_DERIVED_BITMAP_VIEWPORT
+#define k1_DerivedBitmapThievesEyeVisibleArea 1 // @ C001_DERIVED_BITMAP_THIEVES_EYE_VISIBLE_AREA
+#define k2_DerivedBitmapDamageToCreatureMedium 2 // @ C002_DERIVED_BITMAP_DAMAGE_TO_CREATURE_MEDIUM
+#define k3_DerivedBitmapDamageToCreatureSmall 3 // @ C003_DERIVED_BITMAP_DAMAGE_TO_CREATURE_SMALL
+#define k4_DerivedBitmapFirstWallOrnament 4 // @ C004_DERIVED_BITMAP_FIRST_WALL_ORNAMENT
+#define k68_DerivedBitmapFirstDoorOrnament_D3 68 // @ C068_DERIVED_BITMAP_FIRST_DOOR_ORNAMENT_D3
+#define k69_DerivedBitmapFirstDoorOrnament_D2 69 // @ C069_DERIVED_BITMAP_FIRST_DOOR_ORNAMENT_D2
+#define k102_DerivedBitmapFirstDoorButton 102 // @ C102_DERIVED_BITMAP_FIRST_DOOR_BUTTON
+#define k104_DerivedBitmapFirstObject 104 // @ C104_DERIVED_BITMAP_FIRST_OBJECT
+#define k282_DerivedBitmapFirstProjectile 282 // @ C282_DERIVED_BITMAP_FIRST_PROJECTILE
+#define k438_DerivedBitmapFirstExplosion 438 // @ C438_DERIVED_BITMAP_FIRST_EXPLOSION
+#define k495_DerivedBitmapFirstCreature 495 // @ C495_DERIVED_BITMAP_FIRST_CREATURE
+
+
+#define k16_Scale_D3 16 // @ C16_SCALE_D3
+#define k20_Scale_D2 20 // @ C20_SCALE_D2
+
+/* Object aspect GraphicInfo */
+#define k0x0001_ObjectFlipOnRightMask 0x0001 // @ MASK0x0001_FLIP_ON_RIGHT
+#define k0x0010_ObjectAlcoveMask 0x0010 // @ MASK0x0010_ALCOVE
+
+/* Projectile aspect GraphicInfo */
+#define k0x0010_ProjectileSideMask 0x0010 // @ MASK0x0010_SIDE
+#define k0x0100_ProjectileScaleWithKineticEnergyMask 0x0100 // @ MASK0x0100_SCALE_WITH_KINETIC_ENERGY
+#define k0x0003_ProjectileAspectTypeMask 0x0003 // @ MASK0x0003_ASPECT_TYPE
+
+/* Projectile aspect type */
+#define k0_ProjectileAspectHasBackGraphicRotation 0 // @ C0_PROJECTILE_ASPECT_TYPE_HAS_BACK_GRAPHIC_AND_ROTATION
+#define k1_ProjectileAspectBackGraphic 1 // @ C1_PROJECTILE_ASPECT_TYPE_HAS_BACK_GRAPHIC_AND_NO_ROTATION
+#define k2_ProjectileAspectHasRotation 2 // @ C2_PROJECTILE_ASPECT_TYPE_NO_BACK_GRAPHIC_AND_ROTATION
+#define k3_ProjectileAspectHasNone 3 // @ C3_PROJECTILE_ASPECT_TYPE_NO_BACK_GRAPHIC_AND_NO_ROTATION
+
+/* Projectile aspects */
+#define k3_ProjectileAspectExplosionLightningBolt 3 // @ C03_PROJECTILE_ASPECT_EXPLOSION_LIGHTNING_BOLT
+#define k10_ProjectileAspectExplosionFireBall 10 // @ C10_PROJECTILE_ASPECT_EXPLOSION_FIREBALL
+#define k11_ProjectileAspectExplosionDefault 11 // @ C11_PROJECTILE_ASPECT_EXPLOSION_DEFAULT
+#define k12_ProjectileAspectExplosionSlime 12 // @ C12_PROJECTILE_ASPECT_EXPLOSION_SLIME
+#define k13_ProjectileAspectExplosionPoisonBoltCloud 13 // @ C13_PROJECTILE_ASPECT_EXPLOSION_POISON_BOLT_POISON_CLOUD
+
+#define k0x0080_BlitDoNotUseMask 0x0080 // @ MASK0x0080_DO_NOT_USE_MASK
+#define kScaleThreshold 32768
+
+enum ViewCell {
+ k0_ViewCellFronLeft = 0, // @ C00_VIEW_CELL_FRONT_LEFT
+ k1_ViewCellFrontRight = 1, // @ C01_VIEW_CELL_FRONT_RIGHT
+ k2_ViewCellBackRight = 2, // @ C02_VIEW_CELL_BACK_RIGHT
+ k3_ViewCellBackLeft = 3, // @ C03_VIEW_CELL_BACK_LEFT
+ k4_ViewCellAlcove = 4, // @ C04_VIEW_CELL_ALCOVE
+ k5_ViewCellDoorButtonOrWallOrn = 5 // @ C05_VIEW_CELL_DOOR_BUTTON_OR_WALL_ORNAMENT
+};
+
+enum GraphicIndice {
+ k0_dialogBoxGraphicIndice = 0, // @ C000_GRAPHIC_DIALOG_BOX
+ k1_titleGraphicsIndice = 1, // @ C001_GRAPHIC_TITLE
+ k2_entranceLeftDoorGraphicIndice = 2, // @ C002_GRAPHIC_ENTRANCE_LEFT_DOOR
+ k3_entranceRightDoorGraphicIndice = 3, // @ C003_GRAPHIC_ENTRANCE_RIGHT_DOOR
+ k4_entranceGraphicIndice = 4, // @ C004_GRAPHIC_ENTRANCE
+ k5_creditsGraphicIndice = 5, // @ C005_GRAPHIC_CREDITS
+ k6_theEndIndice = 6, // @ C006_GRAPHIC_THE_END
+ k8_StatusBoxDeadChampion = 8, // @ C008_GRAPHIC_STATUS_BOX_DEAD_CHAMPION
+ k9_MenuSpellAreaBackground = 9, // @ C009_GRAPHIC_MENU_SPELL_AREA_BACKGROUND
+ k10_MenuActionAreaIndice = 10, // @ C010_GRAPHIC_MENU_ACTION_AREA
+ k11_MenuSpellAreLinesIndice = 11, // @ C011_GRAPHIC_MENU_SPELL_AREA_LINES
+ k13_MovementArrowsIndice = 13, // @ C013_GRAPHIC_MOVEMENT_ARROWS
+ k14_damageToCreatureIndice = 14, // @ C014_GRAPHIC_DAMAGE_TO_CREATURE
+ k15_damageToChampionSmallIndice = 15, // @ C015_GRAPHIC_DAMAGE_TO_CHAMPION_SMALL
+ k16_damageToChampionBig = 16, // @ C016_GRAPHIC_DAMAGE_TO_CHAMPION_BIG
+ k17_InventoryGraphicIndice = 17, // @ C017_GRAPHIC_INVENTORY
+ k18_ArrowForChestContentIndice = 18, // @ C018_GRAPHIC_ARROW_FOR_CHEST_CONTENT
+ k19_EyeForObjectDescriptionIndice = 19, // @ C019_GRAPHIC_EYE_FOR_OBJECT_DESCRIPTION
+ k20_PanelEmptyIndice = 20, // @ C020_GRAPHIC_PANEL_EMPTY
+ k23_PanelOpenScrollIndice = 23, // @ C023_GRAPHIC_PANEL_OPEN_SCROLL
+ k25_PanelOpenChestIndice = 25, // @ C025_GRAPHIC_PANEL_OPEN_CHEST
+ k26_ChampionPortraitsIndice = 26, // @ C026_GRAPHIC_CHAMPION_PORTRAITS
+ k27_PanelRenameChampionIndice = 27, // @ C027_GRAPHIC_PANEL_RENAME_CHAMPION
+ k28_ChampionIcons = 28, // @ C028_GRAPHIC_CHAMPION_ICONS
+ k29_ObjectDescCircleIndice = 29, // @ C029_GRAPHIC_OBJECT_DESCRIPTION_CIRCLE
+ k30_FoodLabelIndice = 30, // @ C030_GRAPHIC_FOOD_LABEL
+ k31_WaterLabelIndice = 31, // @ C031_GRAPHIC_WATER_LABEL
+ k32_PoisionedLabelIndice = 32, // @ C032_GRAPHIC_POISONED_LABEL
+ k33_SlotBoxNormalIndice = 33, // @ C033_GRAPHIC_SLOT_BOX_NORMAL
+ k34_SlotBoxWoundedIndice = 34, // @ C034_GRAPHIC_SLOT_BOX_WOUNDED
+ k35_SlotBoxActingHandIndice = 35, // @ C035_GRAPHIC_SLOT_BOX_ACTING_HAND
+ k37_BorderPartyShieldIndice = 37, // @ C037_GRAPHIC_BORDER_PARTY_SHIELD
+ k38_BorderPartyFireshieldIndice = 38, // @ C038_GRAPHIC_BORDER_PARTY_FIRESHIELD
+ k39_BorderPartySpellshieldIndice = 39, // @ C039_GRAPHIC_BORDER_PARTY_SPELLSHIELD
+ k40_PanelResurectReincaranteIndice = 40, // @ C040_GRAPHIC_PANEL_RESURRECT_REINCARNATE
+ k41_holeInWall_GraphicIndice = 41, // @ C041_GRAPHIC_HOLE_IN_WALL
+ k42_ObjectIcons_000_TO_031 = 42, // @ C042_GRAPHIC_OBJECT_ICONS_000_TO_031
+ k43_ObjectIcons_032_TO_063 = 43, // @ C043_GRAPHIC_OBJECT_ICONS_032_TO_063
+ k44_ObjectIcons_064_TO_095 = 44, // @ C044_GRAPHIC_OBJECT_ICONS_064_TO_095
+ k45_ObjectIcons_096_TO_127 = 45, // @ C045_GRAPHIC_OBJECT_ICONS_096_TO_127
+ k46_ObjectIcons_128_TO_159 = 46, // @ C046_GRAPHIC_OBJECT_ICONS_128_TO_159
+ k47_ObjectIcons_160_TO_191 = 47, // @ C047_GRAPHIC_OBJECT_ICONS_160_TO_191
+ k48_ObjectIcons_192_TO_223 = 48, // @ C048_GRAPHIC_OBJECT_ICONS_192_TO_223
+ k49_FloorPit_D3L_GraphicIndice = 49, // @ C049_GRAPHIC_FLOOR_PIT_D3L
+ k50_FloorPit_D3C_GraphicIndice = 50, // @ C050_GRAPHIC_FLOOR_PIT_D3C
+ k51_FloorPit_D2L_GraphicIndice = 51, // @ C051_GRAPHIC_FLOOR_PIT_D2L
+ k52_FloorPit_D2C_GraphicIndice = 52, // @ C052_GRAPHIC_FLOOR_PIT_D2C
+ k53_FloorPit_D1L_GraphicIndice = 53, // @ C053_GRAPHIC_FLOOR_PIT_D1L
+ k54_FloorPit_D1C_GraphicIndice = 54, // @ C054_GRAPHIC_FLOOR_PIT_D1C
+ k55_FloorPit_D0L_GraphicIndice = 55, // @ C055_GRAPHIC_FLOOR_PIT_D0L
+ k56_FloorPit_D0C_GraphicIndice = 56, // @ C056_GRAPHIC_FLOOR_PIT_D0C
+ k57_FloorPir_Invisible_D2L_GraphicIndice = 57, // @ C057_GRAPHIC_FLOOR_PIT_INVISIBLE_D2L
+ k58_FloorPit_invisible_D2C_GraphicIndice = 58, // @ C058_GRAPHIC_FLOOR_PIT_INVISIBLE_D2C
+ k59_floorPit_invisible_D1L_GraphicIndice = 59, // @ C059_GRAPHIC_FLOOR_PIT_INVISIBLE_D1L
+ k60_floorPitInvisibleD1C_GraphicIndice = 60, // @ C060_GRAPHIC_FLOOR_PIT_INVISIBLE_D1C
+ k61_floorPitInvisibleD0L_GraphicIndice = 61, // @ C061_GRAPHIC_FLOOR_PIT_INVISIBLE_D0L
+ k62_flootPitInvisibleD0C_graphicIndice = 62, // @ C062_GRAPHIC_FLOOR_PIT_INVISIBLE_D0C
+ k63_ceilingPit_D2L_GraphicIndice = 63, // @ C063_GRAPHIC_CEILING_PIT_D2L
+ k64_ceilingPitD2C_GraphicIndice = 64, // @ C064_GRAPHIC_CEILING_PIT_D2C
+ k65_ceilingPitD1L_GraphicIndice = 65, // @ C065_GRAPHIC_CEILING_PIT_D1L
+ k66_ceilingPitD1C_GraphicIndice = 66, // @ C066_GRAPHIC_CEILING_PIT_D1C
+ k67_ceilingPitD0L_grahicIndice = 67, // @ C067_GRAPHIC_CEILING_PIT_D0L
+ k68_ceilingPitD0C_graphicIndice = 68, // @ C068_GRAPHIC_CEILING_PIT_D0C
+ k69_FieldMask_D3R_GraphicIndice = 69, // @ C069_GRAPHIC_FIELD_MASK_D3R
+ k73_FieldTeleporterGraphicIndice = 73, // @ C073_GRAPHIC_FIELD_TELEPORTER
+ k120_InscriptionFontIndice = 120, // @ C120_GRAPHIC_INSCRIPTION_FONT
+ k208_wallOrn_43_champMirror = 208, // @ C208_GRAPHIC_WALL_ORNAMENT_43_CHAMPION_MIRROR
+ k241_FloorOrn_15_D3L_footprints = 241, // @ C241_GRAPHIC_FLOOR_ORNAMENT_15_D3L_FOOTPRINTS
+ k301_DoorMaskDestroyedIndice = 301, // @ C301_GRAPHIC_DOOR_MASK_DESTROYED
+ k315_firstDoorButton_GraphicIndice = 315, // @ C315_GRAPHIC_FIRST_DOOR_BUTTON
+ k316_FirstProjectileGraphicIndice = 316, // @ C316_GRAPHIC_FIRST_PROJECTILE
+ k348_FirstExplosionGraphicIndice = 348, // @ C348_GRAPHIC_FIRST_EXPLOSION
+ k351_FirstExplosionPatternGraphicIndice = 351, // @ C351_GRAPHIC_FIRST_EXPLOSION_PATTERN
+ k360_FirstObjectGraphicIndice = 360, // @ C360_GRAPHIC_FIRST_OBJECT
+ k446_FirstCreatureGraphicIndice = 446, // @ C446_GRAPHIC_FIRST_CREATURE
+ k557_FontGraphicIndice = 557 // @ C557_GRAPHIC_FONT
+};
+
+
+// in all cases, where a function takes a Box, it expects it to contain inclusive boundaries
+class Box {
+public:
+ int16 _x1;
+ int16 _x2;
+ int16 _y1;
+ int16 _y2;
+
+ Box(int16 x1, int16 x2, int16 y1, int16 y2) : _x1(x1), _x2(x2), _y1(y1), _y2(y2) {}
+ Box() {}
+ template <typename T>
+ explicit Box(T *ptr) {
+ _x1 = *ptr++;
+ _x2 = *ptr++;
+ _y1 = *ptr++;
+ _y2 = *ptr++;
+ }
+ bool isPointInside(Common::Point point) {
+ return (_x1 <= point.x) && (point.x <= _x2) && (_y1 <= point.y) && (point.y <= _y2); // <= because incluseive boundaries
+ }
+ bool isPointInside(int16 x, int16 y) { return isPointInside(Common::Point(x, y)); }
+ void setToZero() { _x1 = _x2 = _y1 = _y2 = 0; }
+}; // @ BOX_BYTE, BOX_WORD
+
+extern Box g2_BoxMovementArrows; // @ G0002_s_Graphic562_Box_MovementArrows
+
+class Frame {
+public:
+ Box _box;
+ uint16 _srcByteWidth, _srcHeight;
+ uint16 _srcX, _srcY;
+
+ Frame() {}
+ Frame(uint16 destFromX, uint16 destToX, uint16 destFromY, uint16 destToY,
+ uint16 srcWidth, uint16 srcHeight, uint16 srcX, uint16 srcY) :
+ _box(destFromX, destToX, destFromY, destToY),
+ _srcByteWidth(srcWidth), _srcHeight(srcHeight), _srcX(srcX), _srcY(srcY) {}
+};
+
+enum WallSet {
+ k0_WallSetStone = 0 // @ C0_WALL_SET_STONE
+};
+
+enum FloorSet {
+ k0_FloorSetStone = 0 // @ C0_FLOOR_SET_STONE
+};
+
+enum ViewWall {
+ k0_ViewWall_D3L_RIGHT = 0, // @ C00_VIEW_WALL_D3L_RIGHT
+ k1_ViewWall_D3R_LEFT = 1, // @ C01_VIEW_WALL_D3R_LEFT
+ k2_ViewWall_D3L_FRONT = 2, // @ C02_VIEW_WALL_D3L_FRONT
+ k3_ViewWall_D3C_FRONT = 3, // @ C03_VIEW_WALL_D3C_FRONT
+ k4_ViewWall_D3R_FRONT = 4, // @ C04_VIEW_WALL_D3R_FRONT
+ k5_ViewWall_D2L_RIGHT = 5, // @ C05_VIEW_WALL_D2L_RIGHT
+ k6_ViewWall_D2R_LEFT = 6, // @ C06_VIEW_WALL_D2R_LEFT
+ k7_ViewWall_D2L_FRONT = 7, // @ C07_VIEW_WALL_D2L_FRONT
+ k8_ViewWall_D2C_FRONT = 8, // @ C08_VIEW_WALL_D2C_FRONT
+ k9_ViewWall_D2R_FRONT = 9, // @ C09_VIEW_WALL_D2R_FRONT
+ k10_ViewWall_D1L_RIGHT = 10, // @ C10_VIEW_WALL_D1L_RIGHT
+ k11_ViewWall_D1R_LEFT = 11, // @ C11_VIEW_WALL_D1R_LEFT
+ k12_ViewWall_D1C_FRONT = 12 // @ C12_VIEW_WALL_D1C_FRONT
+};
+
+enum Color {
+ kM1_ColorNoTransparency = -1,
+ k0_ColorBlack = 0,
+ k1_ColorDarkGary = 1,
+ k2_ColorLightGray = 2,
+ k3_ColorDarkBrown = 3,
+ k4_ColorCyan = 4,
+ k5_ColorLightBrown = 5,
+ k6_ColorDarkGreen = 6,
+ k7_ColorLightGreen = 7,
+ k8_ColorRed = 8,
+ k9_ColorGold = 9,
+ k10_ColorFlesh = 10,
+ k11_ColorYellow = 11,
+ k12_ColorDarkestGray = 12,
+ k13_ColorLightestGray = 13,
+ k14_ColorBlue = 14,
+ k15_ColorWhite = 15
+};
+
+class FieldAspect {
+public:
+ uint16 _nativeBitmapRelativeIndex;
+ uint16 _baseStartUnitIndex; /* Index of the unit (16 pixels = 8 bytes) in bitmap where blit will start from. A random value of 0 or 1 is added to this base index */
+ uint16 _transparentColor; /* Bit 7: Do not use mask if set, Bits 6-0: Transparent color index. 0xFF = no transparency */
+ byte _mask; /* Bit 7: Flip, Bits 6-0: Mask index. 0xFF = no mask */
+ uint16 _byteWidth;
+ uint16 _height;
+ uint16 _xPos;
+ uint16 _bitplaneWordCount;
+ FieldAspect(uint16 native, uint16 base, uint16 transparent, byte mask, uint16 byteWidth, uint16 height, uint16 xPos, uint16 bitplane)
+ : _nativeBitmapRelativeIndex(native), _baseStartUnitIndex(base), _transparentColor(transparent), _mask(mask),
+ _byteWidth(byteWidth), _height(height), _xPos(xPos), _bitplaneWordCount(bitplane) {}
+ FieldAspect() {}
+}; // @ FIELD_ASPECT
+
+class CreatureAspect {
+public:
+ uint16 _firstNativeBitmapRelativeIndex;
+ uint16 _firstDerivedBitmapIndex;
+ byte _byteWidthFront;
+ byte _heightFront;
+ byte _byteWidthSide;
+ byte _heightSide;
+ byte _byteWidthAttack;
+ byte _heightAttack;
+private:
+ byte _coordinateSet_TransparentColor;
+ byte _replacementColorSetIndices;
+public:
+
+ CreatureAspect(uint16 uint161, uint16 uint162, byte byte0, byte byte1, byte byte2, byte byte3, byte byte4, byte byte5, byte byte6, byte byte7)
+ : _firstNativeBitmapRelativeIndex(uint161), _firstDerivedBitmapIndex(uint162), _byteWidthFront(byte0),
+ _heightFront(byte1), _byteWidthSide(byte2), _heightSide(byte3), _byteWidthAttack(byte4),
+ _heightAttack(byte5), _coordinateSet_TransparentColor(byte6), _replacementColorSetIndices(byte7) {}
+
+ CreatureAspect() :
+ _firstNativeBitmapRelativeIndex(0), _firstDerivedBitmapIndex(0), _byteWidthFront(0),
+ _heightFront(0), _byteWidthSide(0), _heightSide(0), _byteWidthAttack(0),
+ _heightAttack(0), _coordinateSet_TransparentColor(0), _replacementColorSetIndices(0) {}
+
+ byte getCoordSet() { return (_coordinateSet_TransparentColor >> 4) & 0xF; } // @ M71_COORDINATE_SET
+ byte getTranspColour() { return _coordinateSet_TransparentColor & 0xF; } // @ M72_TRANSPARENT_COLOR
+ byte getReplColour10() { return (_replacementColorSetIndices >> 4) & 0xF; } // @ M74_COLOR_10_REPLACEMENT_COLOR_SET
+ byte getReplColour9() { return _replacementColorSetIndices & 0xF; } // @ M73_COLOR_09_REPLACEMENT_COLOR_SET
+}; // @ CREATURE_ASPECT
+
+class ObjectAspect {
+public:
+ byte _firstNativeBitmapRelativeIndex;
+ byte _firstDerivedBitmapRelativeIndex;
+ byte _byteWidth;
+ byte _height;
+ byte _graphicInfo; /* Bits 7-5 and 3-1 Unreferenced */
+ byte _coordinateSet;
+ ObjectAspect(byte firstN, byte firstD, byte byteWidth, byte h, byte grap, byte coord) :
+ _firstNativeBitmapRelativeIndex(firstN), _firstDerivedBitmapRelativeIndex(firstD),
+ _byteWidth(byteWidth), _height(h), _graphicInfo(grap), _coordinateSet(coord) {}
+ ObjectAspect() : _firstNativeBitmapRelativeIndex(0), _firstDerivedBitmapRelativeIndex(0),
+ _byteWidth(0), _height(0), _graphicInfo(0), _coordinateSet(0) {}
+}; // @ OBJECT_ASPECT
+
+class ProjectileAspect {
+public:
+ byte _firstNativeBitmapRelativeIndex;
+ byte _firstDerivedBitmapRelativeIndex;
+ byte _byteWidth;
+ byte _height;
+ uint16 _graphicInfo; /* Bits 15-9, 7-5 and 3-2 Unreferenced */
+
+ ProjectileAspect(byte firstN, byte firstD, byte byteWidth, byte h, uint16 grap) :
+ _firstNativeBitmapRelativeIndex(firstN), _firstDerivedBitmapRelativeIndex(firstD),
+ _byteWidth(byteWidth), _height(h), _graphicInfo(grap) {}
+
+ ProjectileAspect() : _firstNativeBitmapRelativeIndex(0),
+ _firstDerivedBitmapRelativeIndex(0), _byteWidth(0), _height(0), _graphicInfo(0) {}
+}; // @ PROJECTIL_ASPECT
+
+class CreatureReplColorSet {
+public:
+ uint16 _RGBColor[6];
+ byte _d2ReplacementColor;
+ byte _d3ReplacementColor;
+
+ CreatureReplColorSet(uint16 col1, uint16 col2, uint16 col3, uint16 col4, uint16 col5, uint16 col6, byte d2Rep, byte d3Rep) {
+ _RGBColor[0] = col1;
+ _RGBColor[1] = col2;
+ _RGBColor[2] = col3;
+ _RGBColor[3] = col4;
+ _RGBColor[4] = col5;
+ _RGBColor[5] = col6;
+ _d2ReplacementColor = d2Rep;
+ _d3ReplacementColor = d3Rep;
+ }
+}; // @ CREATURE_REPLACEMENT_COLOR_SET
+
+
+#define k0_DoorButton 0 // @ C0_DOOR_BUTTON
+#define k0_WallOrnInscription 0 // @ C0_WALL_ORNAMENT_INSCRIPTION
+#define k15_FloorOrnFootprints 15 // @ C15_FLOOR_ORNAMENT_FOOTPRINTS
+#define k15_DoorOrnDestroyedMask 15 // @ C15_DOOR_ORNAMENT_DESTROYED_MASK
+#define k16_DoorOrnThivesEyeMask 16 // @ C16_DOOR_ORNAMENT_THIEVES_EYE_MASK
+
+#define k0_viewportNotDungeonView 0 // @ C0_VIEWPORT_NOT_DUNGEON_VIEW
+#define k1_viewportDungeonView 1 // @ C1_VIEWPORT_DUNGEON_VIEW
+#define k2_viewportAsBeforeSleepOrFreezeGame 2 // @ C2_VIEWPORT_AS_BEFORE_SLEEP_OR_FREEZE_GAME
+
+
+#define k112_byteWidthViewport 112 // @ C112_BYTE_WIDTH_VIEWPORT
+#define k136_heightViewport 136 // @ C136_HEIGHT_VIEWPORT
+
+#define k160_byteWidthScreen 160 // @ C160_BYTE_WIDTH_SCREEN
+#define k200_heightScreen 200 // @ C200_HEIGHT_SCREEN
+
+#define k8_byteWidth 8 // @ C008_BYTE_WIDTH
+#define k16_byteWidth 16 // @ C016_BYTE_WIDTH
+#define k24_byteWidth 24 // @ C024_BYTE_WIDTH
+#define k32_byteWidth 32 // @ C032_BYTE_WIDTH
+#define k40_byteWidth 40 // @ C040_BYTE_WIDTH
+#define k48_byteWidth 48 // @ C048_BYTE_WIDTH
+#define k64_byteWidth 64 // @ C064_BYTE_WIDTH
+#define k72_byteWidth 72 // @ C072_BYTE_WIDTH
+#define k128_byteWidth 128 // @ C128_BYTE_WIDTH
+#define k144_byteWidth 144 // @ C144_BYTE_WIDTH
+
+
+class DoorFrames {
+public:
+ Frame _closedOrDestroyed;
+ Frame _vertical[3];
+ Frame _leftHorizontal[3];
+ Frame _rightHorizontal[3];
+ DoorFrames(Frame f1, Frame f2_1, Frame f2_2, Frame f2_3,
+ Frame f3_1, Frame f3_2, Frame f3_3,
+ Frame f4_1, Frame f4_2, Frame f4_3) {
+ _closedOrDestroyed = f1;
+ _vertical[0] = f2_1;
+ _vertical[1] = f2_2;
+ _vertical[2] = f2_3;
+ _leftHorizontal[0] = f3_1;
+ _leftHorizontal[1] = f3_2;
+ _leftHorizontal[2] = f3_3;
+ _rightHorizontal[0] = f4_1;
+ _rightHorizontal[1] = f4_2;
+ _rightHorizontal[2] = f4_3;
+ }
+}; // @ DOOR_FRAMES
+
+#define D00_RGB_BLACK 0x0000
+#define D01_RGB_DARK_BLUE 0x0004
+#define D02_RGB_LIGHT_BROWN 0x0842
+#define D03_RGB_PINK 0x086F
+#define D04_RGB_LIGHTER_BROWN 0x0A62
+#define D05_RGB_DARK_GOLD 0x0A82
+#define D06_RGB_GOLD 0x0CA2
+#define D07_RGB_RED 0x0F00
+#define D08_RGB_YELLOW 0x0FF4
+#define D09_RGB_WHITE 0x0FFF
+#define D10_MASK_RED_COMPONENT 0x0F00
+#define D10_MASK_RED_COMPONENT 0x0F00
+#define D11_MASK_GREEN_COMPONENT 0x00F0
+#define D12_MASK_BLUE_COMPONENT 0x000F
+
+class DisplayMan {
+ friend class DM::TextMan;
+
+ DMEngine *_vm;
+
+ uint16 _grapItemCount; // @ G0632_ui_GraphicCount
+ uint32 *_bitmapCompressedByteCount;
+ uint32 *_bitmapDecompressedByteCount;
+ uint32 *_packedItemPos;
+ byte *_packedBitmaps;
+ byte **_bitmaps;
+ DoorFrames *_doorFrameD1C;
+ // pointers are not owned by these fields
+ byte *_palChangesProjectile[4]; // @G0075_apuc_PaletteChanges_Projectile
+
+ DisplayMan(const DisplayMan &other); // no implementation on purpose
+ void operator=(const DisplayMan &rhs); // no implementation on purpose
+
+ byte *getCurrentVgaBuffer();
+ // the original function has two position parameters, but they are always set to zero
+ void unpackGraphics();
+ void loadFNT1intoBitmap(uint16 index, byte *destBitmap);
+
+ void viewportSetPalette(uint16 *middleScreenPalette, uint16 *topAndBottomScreen); // @ F0565_VIEWPORT_SetPalette
+ void viewportBlitToScreen(); // @ F0566_VIEWPORT_BlitToScreen
+
+ void drawFloorPitOrStairsBitmapFlippedHorizontally(uint16 nativeIndex, Frame &frame); // @ F0105_DUNGEONVIEW_DrawFloorPitOrStairsBitmapFlippedHorizontally
+ void drawFloorPitOrStairsBitmap(uint16 nativeIndex, Frame &frame); // @ F0104_DUNGEONVIEW_DrawFloorPitOrStairsBitmap
+ void drawWallSetBitmap(byte *bitmap, Frame &f); // @ F0100_DUNGEONVIEW_DrawWallSetBitmap
+ void drawWallSetBitmapWithoutTransparency(byte *bitmap, Frame &f); // @ F0101_DUNGEONVIEW_DrawWallSetBitmapWithoutTransparency
+ void drawSquareD3L(Direction dir, int16 posX, int16 posY); // @ F0116_DUNGEONVIEW_DrawSquareD3L
+ void drawSquareD3R(Direction dir, int16 posX, int16 posY); // @ F0117_DUNGEONVIEW_DrawSquareD3R
+ void drawSquareD3C(Direction dir, int16 posX, int16 posY); // @ F0118_DUNGEONVIEW_DrawSquareD3C_CPSF
+ void drawSquareD2L(Direction dir, int16 posX, int16 posY); // @ F0119_DUNGEONVIEW_DrawSquareD2L
+ void drawSquareD2R(Direction dir, int16 posX, int16 posY); // @ F0120_DUNGEONVIEW_DrawSquareD2R_CPSF
+ void drawSquareD2C(Direction dir, int16 posX, int16 posY); // @ F0121_DUNGEONVIEW_DrawSquareD2C
+ void drawSquareD1L(Direction dir, int16 posX, int16 posY); // @ F0122_DUNGEONVIEW_DrawSquareD1L
+ void drawSquareD1R(Direction dir, int16 posX, int16 posY); // @ F0123_DUNGEONVIEW_DrawSquareD1R
+ void drawSquareD1C(Direction dir, int16 posX, int16 posY); // @ F0124_DUNGEONVIEW_DrawSquareD1C
+ void drawSquareD0L(Direction dir, int16 posX, int16 posY); // @ F0125_DUNGEONVIEW_DrawSquareD0L
+ void drawSquareD0R(Direction dir, int16 posX, int16 posY); // @ F0126_DUNGEONVIEW_DrawSquareD0R
+ void drawSquareD0C(Direction dir, int16 posX, int16 posY); // @ F0127_DUNGEONVIEW_DrawSquareD0C
+
+ void applyCreatureReplColors(int replacedColor, int replacementColor); // @ F0093_DUNGEONVIEW_ApplyCreatureReplacementColors
+
+ bool isDrawnWallOrnAnAlcove(int16 wallOrnOrd, ViewWall viewWallIndex); // @ F0107_DUNGEONVIEW_IsDrawnWallOrnamentAnAlcove_CPSF
+
+ uint16 *_derivedBitmapByteCount; // @ G0639_pui_DerivedBitmapByteCount
+ byte **_derivedBitmaps; // @ G0638_pui_DerivedBitmapBlockIndices
+
+ int16 _stairsNativeBitmapIndexUpFrontD3L; // @ G0675_i_StairsNativeBitmapIndex_Up_Front_D3L
+ int16 _stairsNativeBitmapIndexUpFrontD3C; // @ G0676_i_StairsNativeBitmapIndex_Up_Front_D3C
+ int16 _stairsNativeBitmapIndexUpFrontD2L; // @ G0677_i_StairsNativeBitmapIndex_Up_Front_D2L
+ int16 _stairsNativeBitmapIndexUpFrontD2C; // @ G0678_i_StairsNativeBitmapIndex_Up_Front_D2C
+ int16 _stairsNativeBitmapIndexUpFrontD1L; // @ G0679_i_StairsNativeBitmapIndex_Up_Front_D1L
+ int16 _stairsNativeBitmapIndexUpFrontD1C; // @ G0680_i_StairsNativeBitmapIndex_Up_Front_D1C
+ int16 _stairsNativeBitmapIndexUpFrontD0CLeft; // @ G0681_i_StairsNativeBitmapIndex_Up_Front_D0C_Left
+ int16 _stairsNativeBitmapIndexDownFrontD3L; // @ G0682_i_StairsNativeBitmapIndex_Down_Front_D3L
+ int16 _stairsNativeBitmapIndexDownFrontD3C; // @ G0683_i_StairsNativeBitmapIndex_Down_Front_D3C
+ int16 _stairsNativeBitmapIndexDownFrontD2L; // @ G0684_i_StairsNativeBitmapIndex_Down_Front_D2L
+ int16 _stairsNativeBitmapIndexDownFrontD2C; // @ G0685_i_StairsNativeBitmapIndex_Down_Front_D2C
+ int16 _stairsNativeBitmapIndexDownFrontD1L; // @ G0686_i_StairsNativeBitmapIndex_Down_Front_D1L
+ int16 _stairsNativeBitmapIndexDownFrontD1C; // @ G0687_i_StairsNativeBitmapIndex_Down_Front_D1C
+ int16 _stairsNativeBitmapIndexDownFrontD0CLeft; // @ G0688_i_StairsNativeBitmapIndex_Down_Front_D0C_Left
+ int16 _stairsNativeBitmapIndexSideD2L; // @ G0689_i_StairsNativeBitmapIndex_Side_D2L
+ int16 _stairsNativeBitmapIndexUpSideD1L; // @ G0690_i_StairsNativeBitmapIndex_Up_Side_D1L
+ int16 _stairsNativeBitmapIndexDownSideD1L; // @ G0691_i_StairsNativeBitmapIndex_Down_Side_D1L
+ int16 _stairsNativeBitmapIndexSideD0L; // @ G0692_i_StairsNativeBitmapIndex_Side_D0L
+
+ byte *_bitmapFloor; // @ G0084_puc_Bitmap_Floor
+ byte *_bitmapCeiling; // @ G0085_puc_Bitmap_Ceiling
+ byte *_bitmapWallSetD3L2; // @ G0697_puc_Bitmap_WallSet_Wall_D3L2
+ byte *_bitmapWallSetD3R2; // @ G0696_puc_Bitmap_WallSet_Wall_D3R2
+ byte *_bitmapWallSetD3LCR; // @ G0698_puc_Bitmap_WallSet_Wall_D3LCR
+ byte *_bitmapWallSetD2LCR; // @ G0699_puc_Bitmap_WallSet_Wall_D2LCR
+public:
+ byte *_bitmapWallSetD1LCR; // @ G0700_puc_Bitmap_WallSet_Wall_D1LCR
+private:
+ Box _boxThievesEyeViewPortVisibleArea; // @ G0106_s_Graphic558_Box_ThievesEye_ViewportVisibleArea
+ byte _palChangesDoorButtonAndWallOrnD3[16]; // @ G0198_auc_Graphic558_PaletteChanges_DoorButtonAndWallOrnament_D3
+ byte _palChangesDoorButtonAndWallOrnD2[16]; // @ G0199_auc_Graphic558_PaletteChanges_DoorButtonAndWallOrnament_D2
+
+ byte *bitmapWallSetWallD0L; // @ G0701_puc_Bitmap_WallSet_Wall_D0L
+ byte *_bitmapWallSetWallD0R; // @ G0702_puc_Bitmap_WallSet_Wall_D0R
+ byte *_bitmapWallSetDoorFrameTopD2LCR; // @ G0703_puc_Bitmap_WallSet_DoorFrameTop_D2LCR
+ byte *_bitmapWallSetDoorFrameTopD1LCR; // @ G0704_puc_Bitmap_WallSet_DoorFrameTop_D1LCR
+ byte *_bitmapWallSetDoorFrameLeftD3L; // @ G0705_puc_Bitmap_WallSet_DoorFrameLeft_D3L
+ byte *_bitmapWallSetDoorFrameLeftD3C; // @ G0706_puc_Bitmap_WallSet_DoorFrameLeft_D3C
+ byte *_bitmapWallSetDoorFrameLeftD2C; // @ G0707_puc_Bitmap_WallSet_DoorFrameLeft_D2C
+ byte *_bitmapWallSetDoorFrameLeftD1C; // @ G0708_puc_Bitmap_WallSet_DoorFrameLeft_D1C
+ byte *_bitmapWallSetDoorFrameRightD1C; // @ G0710_puc_Bitmap_WallSet_DoorFrameRight_D1C
+ byte *_bitmapWallSetDoorFrameFront; // @ G0709_puc_Bitmap_WallSet_DoorFrameFront
+
+ byte *_bitmapWallD3LCRFlipped; // @ G0090_puc_Bitmap_WallD3LCR_Flipped;
+ byte *_bitmapWallD2LCRFlipped; // @ G0091_puc_Bitmap_WallD2LCR_Flipped;
+ byte *_bitmapWallD1LCRFlipped; // @ G0092_puc_Bitmap_WallD1LCR_Flipped;
+ byte *_bitmapWallD0LFlipped; // @ G0093_puc_Bitmap_WallD0L_Flipped;
+ byte *_bitmapWallD0RFlipped; // @ G0094_puc_Bitmap_WallD0R_Flipped;
+ byte *_bitmapWallD3LCRNative; // @ G0095_puc_Bitmap_WallD3LCR_Native;
+ byte *_bitmapWallD2LCRNative; // @ G0096_puc_Bitmap_WallD2LCR_Native;
+ byte *_bitmapWallD1LCRNative; // @ G0097_puc_Bitmap_WallD1LCR_Native;
+ byte *_bitmapWallD0LNative; // @ G0098_puc_Bitmap_WallD0L_Native;
+ byte *_bitmapWallD0RNative; // @ G0099_puc_Bitmap_WallD0R_Native;
+
+ int16 _currentWallSet; // @ G0231_i_CurrentWallSet
+ int16 _currentFloorSet;// @ G0230_i_CurrentFloorSet
+
+ bool _useFlippedWallAndFootprintsBitmap; // @ G0076_B_UseFlippedWallAndFootprintsBitmaps
+
+ int16 _doorNativeBitmapIndexFrontD3LCR[2]; // @ G0693_ai_DoorNativeBitmapIndex_Front_D3LCR
+ int16 _doorNativeBitmapIndexFrontD2LCR[2]; // @ G0694_ai_DoorNativeBitmapIndex_Front_D2LCR
+ int16 _doorNativeBitmapIndexFrontD1LCR[2]; // @ G0695_ai_DoorNativeBitmapIndex_Front_D1LCR
+
+ uint16 *_paletteFadeFrom; // @ K0017_pui_Palette_FadeFrom
+ uint16 _paletteFadeTemporary[16]; // @ K0016_aui_Palette_FadeTemporary
+public:
+
+ uint16 _screenWidth;
+ uint16 _screenHeight;
+ byte *_bitmapScreen; // @ G0348_pl_Bitmap_Screen
+ byte *_bitmapViewport; // @ G0296_puc_Bitmap_Viewport
+
+ // some methods use this for a stratchpad, don't make assumptions about content between function calls
+ byte *_tmpBitmap; // @ G0074_puc_Bitmap_Temporary
+ bool _paletteSwitchingEnabled; // @ G0322_B_PaletteSwitchingEnabled
+ bool _refreshDungeonViewPaleteRequested; // @ G0342_B_RefreshDungeonViewPaletteRequested
+ int16 _dungeonViewPaletteIndex; // @ G0304_i_DungeonViewPaletteIndex
+ uint16 _blankBuffer[32]; // @G0345_aui_BlankBuffer
+ uint16 _paletteTopAndBottomScreen[16]; // @ G0347_aui_Palette_TopAndBottomScreen
+ uint16 _paletteMiddleScreen[16]; // @ G0346_aui_Palette_MiddleScreen
+
+ explicit DisplayMan(DMEngine *dmEngine);
+ ~DisplayMan();
+
+ void loadWallSet(WallSet set); // @ F0095_DUNGEONVIEW_LoadWallSet
+ void loadFloorSet(FloorSet set); // @ F0094_DUNGEONVIEW_LoadFloorSet
+
+ void loadIntoBitmap(uint16 index, byte *destBitmap); // @ F0466_EXPAND_GraphicToBitmap
+ void setUpScreens(uint16 width, uint16 height);
+ void loadGraphics(); // @ F0479_MEMORY_ReadGraphicsDatHeader
+ void initializeGraphicData(); // @ F0460_START_InitializeGraphicData
+ void loadCurrentMapGraphics(); // @ F0096_DUNGEONVIEW_LoadCurrentMapGraphics_CPSDF
+ void allocateFlippedWallBitmaps(); // @ F0461_START_AllocateFlippedWallBitmaps
+ void drawDoorBitmap(Frame *frame);// @ F0102_DUNGEONVIEW_DrawDoorBitmap
+ void drawDoorFrameBitmapFlippedHorizontally(byte *bitmap, Frame *frame); // @ F0103_DUNGEONVIEW_DrawDoorFrameBitmapFlippedHorizontally
+ void drawDoorButton(int16 doorButtonOrdinal, int16 viewDoorButtonIndex); // @ F0110_DUNGEONVIEW_DrawDoorButton
+
+ /// Gives the width of an IMG0 type item
+ uint16 getPixelWidth(uint16 index);
+ /// Gives the height of an IMG1 type item
+ uint16 getPixelHeight(uint16 index);
+
+ void copyBitmapAndFlipHorizontal(byte *srcBitmap, byte *destBitmap, uint16 byteWidth, uint16 height); // @ F0099_DUNGEONVIEW_CopyBitmapAndFlipHorizontal
+ void drawFloorOrnament(uint16 floorOrnOrdinal, uint16 viewFloorIndex); // @ F0108_DUNGEONVIEW_DrawFloorOrnament
+ void drawDoor(uint16 doorThingIndex, uint16 doorState, int16 *doorNativeBitmapIndices, int16 byteCount,
+ int16 viewDoorOrnIndex, DoorFrames *doorFrames); // @ F0111_DUNGEONVIEW_DrawDoor
+ void drawDoorOrnament(int16 doorOrnOdinal, int16 viewDoorOrnIndex); // @ F0109_DUNGEONVIEW_DrawDoorOrnament
+ void drawCeilingPit(int16 nativeBitmapIndex, Frame *frame, int16 mapX, int16 mapY, bool flipHorizontal); // @ F0112_DUNGEONVIEW_DrawCeilingPit
+
+ void blitToViewport(byte *bitmap, Box &box, int16 byteWidth, Color transparent, int16 height); // @ F0020_MAIN_BlitToViewport
+ void blitToViewport(byte *bitmap, int16 *box, int16 byteWidth, Color transparent, int16 height); // @ F0020_MAIN_BlitToViewport
+ void blitToScreen(byte *bitmap, const Box *box, int16 byteWidth, Color transparent, int16 height); // @ F0021_MAIN_BlitToScreen
+
+
+ /* srcHeight and destHeight are not necessary for blitting, only error checking, thus they are defaulted for existing code which
+ does not pass anything, newly imported calls do pass srcHeght and srcWidth, so this is a ceonvenience change so the the parameters
+ match the original exactly, if need arises for heights then we'll have to retrospectively add them in old function calls*/
+ /* Expects inclusive boundaries in box */
+ void blitToBitmap(byte *srcBitmap, byte *destBitmap, const Box &box, uint16 srcX, uint16 srcY, uint16 srcByteWidth,
+ uint16 destByteWidth, Color transparent, int16 srcHeight, int16 destHight); // @ F0132_VIDEO_Blit
+ /* Expects inclusive boundaries in box */
+ void blitBoxFilledWithMaskedBitmap(byte *src, byte *dest, byte *mask, byte *tmp, Box &box, int16 lastUnitIndex,
+ int16 firstUnitIndex, int16 destByteWidth, Color transparent,
+ int16 xPos, int16 yPos, int16 destHeight, int16 height2); // @ F0133_VIDEO_BlitBoxFilledWithMaskedBitmap
+ // this function takes pixel widths
+ void blitToBitmapShrinkWithPalChange(byte *srcBitmap, byte *destBitmap,
+ int16 srcPixelWidth, int16 srcHight, int16 destPixelWidth, int16 destHeight, byte *palChange); // @ F0129_VIDEO_BlitShrinkWithPaletteChanges
+ void flipBitmapHorizontal(byte *bitmap, uint16 byteWidth, uint16 height); // @ F0130_VIDEO_FlipHorizontal
+ void flipBitmapVertical(byte *bitmap, uint16 byteWidth, uint16 height);
+ byte *getExplosionBitmap(uint16 explosionAspIndex, uint16 scale, int16 &returnByteWidth, int16 &returnHeight); // @ F0114_DUNGEONVIEW_GetExplosionBitmap
+
+ void fillBitmap(byte *bitmap, Color color, uint16 byteWidth, uint16 height); // @ F0134_VIDEO_FillBitmap
+ void fillScreen(Color color);
+ /* Expects inclusive boundaries in box */
+ void fillScreenBox(Box &box, Color color); // @ D24_FillScreenBox, F0550_VIDEO_FillScreenBox
+ /* Expects inclusive boundaries in box */
+ void fillBoxBitmap(byte *destBitmap, Box &box, Color color, int16 byteWidth, int16 height); // @ F0135_VIDEO_FillBox
+ void drawDungeon(Direction dir, int16 posX, int16 posY); // @ F0128_DUNGEONVIEW_Draw_CPSF
+ void drawFloorAndCeiling(); // @ F0098_DUNGEONVIEW_DrawFloorAndCeiling
+ void updateScreen();
+ void drawViewport(int16 palSwitchingRequestedState); // @ F0097_DUNGEONVIEW_DrawViewport
+
+ byte *getNativeBitmapOrGraphic(uint16 index); // @ F0489_MEMORY_GetNativeBitmapOrGraphic
+ Common::MemoryReadStream getCompressedData(uint16 index);
+ uint32 getCompressedDataSize(uint16 index);
+ void drawField(FieldAspect *fieldAspect, Box &box); // @ F0113_DUNGEONVIEW_DrawField
+
+ int16 getScaledBitmapByteCount(int16 byteWidth, int16 height, int16 scale); // @ F0459_START_GetScaledBitmapByteCount
+ int16 getScaledDimension(int16 dimension, int16 scale); // @ M78_SCALED_DIMENSION
+ void drawObjectsCreaturesProjectilesExplosions(Thing thingParam, Direction directionParam,
+ int16 mapXpos, int16 mapYpos, int16 viewSquareIndex,
+ uint16 orderedViewCellOrdinals); // @ F0115_DUNGEONVIEW_DrawObjectsCreaturesProjectilesExplosions_CPSEF
+ uint16 getNormalizedByteWidth(uint16 byteWidth); // @ M77_NORMALIZED_BYTE_WIDTH
+ uint16 getVerticalOffsetM23(uint16 val); // @ M23_VERTICAL_OFFSET
+ uint16 getHorizontalOffsetM22(uint16 val); // @ M22_HORIZONTAL_OFFSET
+
+ int16 _championPortraitOrdinal; // @ G0289_i_DungeonView_ChampionPortraitOrdinal
+ int16 _currMapAlcoveOrnIndices[k3_AlcoveOrnCount]; // @ G0267_ai_CurrentMapAlcoveOrnamentIndices
+ int16 _currMapFountainOrnIndices[k1_FountainOrnCount]; // @ G0268_ai_CurrentMapFountainOrnamentIndices
+ int16 _currMapWallOrnInfo[16][2]; // @ G0101_aai_CurrentMapWallOrnamentsInfo
+ int16 _currMapFloorOrnInfo[16][2]; // @ G0102_aai_CurrentMapFloorOrnamentsInfo
+ int16 _currMapDoorOrnInfo[17][2]; // @ G0103_aai_CurrentMapDoorOrnamentsInfo
+ byte *_currMapAllowedCreatureTypes; // @ G0264_puc_CurrentMapAllowedCreatureTypes
+ byte _currMapWallOrnIndices[16]; // @ G0261_auc_CurrentMapWallOrnamentIndices
+ byte _currMapFloorOrnIndices[16]; // @ G0262_auc_CurrentMapFloorOrnamentIndices
+ byte _currMapDoorOrnIndices[18]; // @ G0263_auc_CurrentMapDoorOrnamentIndices
+
+ int16 _currMapViAltarIndex; // @ G0266_i_CurrentMapViAltarWallOrnamentIndex
+
+ Thing _inscriptionThing; // @ G0290_T_DungeonView_InscriptionThing
+
+ bool _drawFloorAndCeilingRequested; // @ G0297_B_DrawFloorAndCeilingRequested
+
+ // This tells blitting functions whether to assume a BYTE_BOX or a WORD_BOX has been passed to them,
+ // I only use WORD_BOX, so this will probably deem useless
+ bool _useByteBoxCoordinates; // @ G0578_B_UseByteBoxCoordinates
+ bool _doNotDrawFluxcagesDuringEndgame; // @ G0077_B_DoNotDrawFluxcagesDuringEndgame
+
+ Frame _doorFrameLeftD1C; // @ G0170_s_Graphic558_Frame_DoorFrameLeft_D1C
+ Frame _doorFrameRightD1C; // @ G0171_s_Graphic558_Frame_DoorFrameRight_D1C
+ FieldAspect _fieldAspects188[12];
+ Box _boxMovementArrows;
+ byte _palChangeSmoke[16];
+ byte _projectileScales[7];
+ ExplosionAspect _explosionAspects[k4_ExplosionAspectCount];
+ Frame _frameWallD3R2;
+ Frame _frameWalls163[12];
+ CreatureAspect _creatureAspects219[k27_CreatureTypeCount];
+ ObjectAspect _objectAspects209[k85_ObjAspectCount]; // @ G0209_as_Graphic558_ObjectAspects
+ ProjectileAspect _projectileAspect[k14_ProjectileAspectCount]; // @ G0210_as_Graphic558_ProjectileAspects
+ uint16 _palCredits[16]; // @ G0019_aui_Graphic562_Palette_Credits
+ uint16 _palDungeonView[6][16]; // @ G0021_aaui_Graphic562_Palette_DungeonView
+ byte _palChangesCreatureD3[16]; // @ G0221_auc_Graphic558_PaletteChanges_Creature_D3
+ byte _palChangesCreatureD2[16]; // @ G0222_auc_Graphic558_PaletteChanges_Creature_D2
+ byte _palChangesNoChanges[16]; // @ G0017_auc_Graphic562_PaletteChanges_NoChanges
+ byte _palChangesFloorOrnD3[16]; // @ G0213_auc_Graphic558_PaletteChanges_FloorOrnament_D3
+ byte _palChangesFloorOrnD2[16]; // @ G0214_auc_Graphic558_PaletteChanges_FloorOrnament_D2
+
+ bool isDerivedBitmapInCache(int16 derivedBitmapIndex); // @ F0491_CACHE_IsDerivedBitmapInCache
+ byte *getDerivedBitmap(int16 derivedBitmapIndex); // @ F0492_CACHE_GetDerivedBitmap
+ void addDerivedBitmap(int16 derivedBitmapIndex); // @ F0493_CACHE_AddDerivedBitmap
+ void releaseBlock(uint16 index); // @ F0480_CACHE_ReleaseBlock
+ uint16 getDarkenedColor(uint16 RGBcolor);
+ void startEndFadeToPalette(uint16 *P0849_pui_Palette); // @ F0436_STARTEND_FadeToPalette
+ void buildPaletteChangeCopperList(uint16 *middleScreen, uint16 *topAndBottom); // @ F0508_AMIGA_BuildPaletteChangeCopperList
+ void shadeScreenBox(Box *box, Color color) { warning("STUB METHOD: shadeScreenBox"); } // @ F0136_VIDEO_ShadeScreenBox
+
+private:
+ void initConstants();
+};
+
+}
+
+#endif
diff --git a/engines/dm/group.cpp b/engines/dm/group.cpp
new file mode 100644
index 0000000000..4ec74ef424
--- /dev/null
+++ b/engines/dm/group.cpp
@@ -0,0 +1,2036 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "dm/group.h"
+#include "dm/dungeonman.h"
+#include "dm/champion.h"
+#include "dm/movesens.h"
+#include "dm/projexpl.h"
+#include "dm/timeline.h"
+#include "dm/objectman.h"
+#include "dm/menus.h"
+#include "dm/sounds.h"
+
+
+namespace DM {
+int32 M32_setTime(int32 &map_time, int32 time) {
+ return map_time = (map_time & 0xFF000000) | time;
+}
+
+GroupMan::GroupMan(DMEngine *vm) : _vm(vm) {
+ for (uint16 i = 0; i < 4; ++i)
+ _dropMovingCreatureFixedPossessionsCell[i] = 0;
+ _dropMovingCreatureFixedPossCellCount = 0;
+ _fluxCageCount = 0;
+ for (uint16 i = 0; i < 4; ++i)
+ _fluxCages[i] = 0;
+ _currentGroupMapX = 0;
+ _currentGroupMapY = 0;
+ _currGroupThing = Thing(0);
+ for (uint16 i = 0; i < 4; ++i)
+ _groupMovementTestedDirections[i] = 0;
+ _currGroupDistanceToParty = 0;
+ _currGroupPrimaryDirToParty = 0;
+ _currGroupSecondaryDirToParty = 0;
+ _groupMovementBlockedByGroupThing = Thing(0);
+ _groupMovementBlockedByDoor = false;
+ _groupMovementBlockedByParty = false;
+ _groupMovBlockedByWallStairsPitFakeWalFluxCageTeleporter = false;
+ _maxActiveGroupCount = 60;
+ _activeGroups = nullptr;
+ _currActiveGroupCount = 0;
+ twoHalfSquareSizedCreaturesGroupLastDirectionSetTime = 0;
+}
+
+GroupMan::~GroupMan() {
+ delete[] _activeGroups;
+}
+
+void GroupMan::initActiveGroups() {
+ if (_vm->_newGameFl)
+ _maxActiveGroupCount = 60;
+
+ if (_activeGroups)
+ delete[] _activeGroups;
+
+ _activeGroups = new ActiveGroup[_maxActiveGroupCount];
+ for (uint16 i = 0; i < _maxActiveGroupCount; ++i)
+ _activeGroups[i]._groupThingIndex = -1;
+}
+
+uint16 GroupMan::getGroupCells(Group *group, int16 mapIndex) {
+ byte cells = group->_cells;
+ if (mapIndex == _vm->_dungeonMan->_partyMapIndex)
+ cells = _activeGroups[cells]._cells;
+ return cells;
+}
+
+uint16 GroupMan::getGroupDirections(Group *group, int16 mapIndex) {
+ static byte groupDirections[4] = {0x00, 0x55, 0xAA, 0xFF}; // @ G0258_auc_Graphic559_GroupDirections
+
+ if (mapIndex == _vm->_dungeonMan->_partyMapIndex)
+ return _activeGroups[group->getActiveGroupIndex()]._directions;
+
+ return groupDirections[group->getDir()];
+}
+
+int16 GroupMan::getCreatureOrdinalInCell(Group *group, uint16 cell) {
+ uint16 currMapIndex = _vm->_dungeonMan->_currMapIndex;
+ byte groupCells = getGroupCells(group, currMapIndex);
+ if (groupCells == k255_CreatureTypeSingleCenteredCreature)
+ return _vm->indexToOrdinal(0);
+
+ int retval = 0;
+ byte creatureIndex = group->getCount();
+ if (getFlag(_vm->_dungeonMan->_creatureInfos[group->_type]._attributes, k0x0003_MaskCreatureInfo_size) == k1_MaskCreatureSizeHalf) {
+ if ((getGroupDirections(group, currMapIndex) & 1) == (cell & 1))
+ cell = returnPrevVal(cell);
+
+ do {
+ byte creatureCell = getCreatureValue(groupCells, creatureIndex);
+ if (creatureCell == cell || creatureCell == returnNextVal(cell)) {
+ retval = _vm->indexToOrdinal(creatureIndex);
+ break;
+ }
+ } while (creatureIndex--);
+ } else {
+ do {
+ if (getCreatureValue(groupCells, creatureIndex) == cell) {
+ retval = _vm->indexToOrdinal(creatureIndex);
+ break;
+ }
+ } while (creatureIndex--);
+ }
+
+ return retval;
+}
+
+uint16 GroupMan::getCreatureValue(uint16 groupVal, uint16 creatureIndex) {
+ return (groupVal >> (creatureIndex << 1)) & 0x3;
+}
+
+void GroupMan::dropGroupPossessions(int16 mapX, int16 mapY, Thing groupThing, int16 mode) {
+ Group *group = (Group *)_vm->_dungeonMan->getThingData(groupThing);
+ uint16 creatureType = group->_type;
+ if ((mode >= k0_soundModePlayImmediately) && getFlag(_vm->_dungeonMan->_creatureInfos[creatureType]._attributes, k0x0200_MaskCreatureInfo_dropFixedPoss)) {
+ int16 creatureIndex = group->getCount();
+ uint16 groupCells = getGroupCells(group, _vm->_dungeonMan->_currMapIndex);
+ do {
+ dropCreatureFixedPossessions(creatureType, mapX, mapY,
+ (groupCells == k255_CreatureTypeSingleCenteredCreature) ? k255_CreatureTypeSingleCenteredCreature : getCreatureValue(groupCells, creatureIndex), mode);
+ } while (creatureIndex--);
+ }
+
+ Thing currentThing = group->_slot;
+ if ((currentThing) != Thing::_endOfList) {
+ bool L0371_B_WeaponDropped = false;
+ Thing nextThing;
+ do {
+ nextThing = _vm->_dungeonMan->getNextThing(currentThing);
+ currentThing = thingWithNewCell(currentThing, _vm->getRandomNumber(4));
+ if ((currentThing).getType() == k5_WeaponThingType) {
+ L0371_B_WeaponDropped = true;
+ }
+ _vm->_moveSens->getMoveResult(currentThing, kM1_MapXNotOnASquare, 0, mapX, mapY);
+ } while ((currentThing = nextThing) != Thing::_endOfList);
+
+ if (mode >= k0_soundModePlayImmediately)
+ _vm->_sound->requestPlay(L0371_B_WeaponDropped ? k00_soundMETALLIC_THUD : k04_soundWOODEN_THUD_ATTACK_TROLIN_ANTMAN_STONE_GOLEM, mapX, mapY, mode);
+ }
+}
+
+void GroupMan::dropCreatureFixedPossessions(uint16 creatureType, int16 mapX, int16 mapY, uint16 cell, int16 mode) {
+ static uint16 fixedPossessionCreature12Skeleton[3] = { // @ G0245_aui_Graphic559_FixedPossessionsCreature12Skeleton
+ k23_ObjectInfoIndexFirstWeapon + k9_WeaponTypeFalchion,
+ k69_ObjectInfoIndexFirstArmour + k30_ArmourTypeWoodenShield,
+ 0}
+ ;
+ static uint16 fixedPossessionCreature9StoneGolem[2] = { // @ G0246_aui_Graphic559_FixedPossessionsCreature09StoneGolem
+ k23_ObjectInfoIndexFirstWeapon + k24_WeaponTypeStoneClub,
+ 0
+ };
+ static uint16 fixedPossessionCreatur16TrolinAntman[2] = { // @ G0247_aui_Graphic559_FixedPossessionsCreature16Trolin_Antman
+ k23_ObjectInfoIndexFirstWeapon + k23_WeaponTypeClub,
+ 0
+ };
+ static uint16 fixedPossessionCreature18AnimatedArmourDethKnight[7] = { // @ G0248_aui_Graphic559_FixedPossessionsCreature18AnimatedArmour_DethKnight
+ k69_ObjectInfoIndexFirstArmour + k41_ArmourTypeFootPlate,
+ k69_ObjectInfoIndexFirstArmour + k40_ArmourTypeLegPlate,
+ k69_ObjectInfoIndexFirstArmour + k39_ArmourTypeTorsoPlate,
+ k23_ObjectInfoIndexFirstWeapon + k10_WeaponTypeSword,
+ k69_ObjectInfoIndexFirstArmour + k38_ArmourTypeArmet,
+ k23_ObjectInfoIndexFirstWeapon + k10_WeaponTypeSword,
+ 0
+ };
+ static uint16 fixedPossessionCreature7rockRockPile[5] = { // @ G0249_aui_Graphic559_FixedPossessionsCreature07Rock_RockPile
+ k127_ObjectInfoIndexFirstJunk + k25_JunkTypeBoulder,
+ k127_ObjectInfoIndexFirstJunk + k25_JunkTypeBoulder | k0x8000_randomDrop,
+ k23_ObjectInfoIndexFirstWeapon + k30_WeaponTypeRock | k0x8000_randomDrop,
+ k23_ObjectInfoIndexFirstWeapon + k30_WeaponTypeRock | k0x8000_randomDrop,
+ 0
+ };
+ static uint16 fixedPossessionCreature4PainRatHellHound[3] = { // @ G0250_aui_Graphic559_FixedPossessionsCreature04PainRat_Hellhound
+ k127_ObjectInfoIndexFirstJunk + k35_JunkTypeDrumstickShank,
+ k127_ObjectInfoIndexFirstJunk + k35_JunkTypeDrumstickShank | k0x8000_randomDrop,
+ 0
+ };
+ static uint16 fixedPossessionCreature6screamer[3] = { // @ G0251_aui_Graphic559_FixedPossessionsCreature06Screamer
+ k127_ObjectInfoIndexFirstJunk + k33_JunkTypeScreamerSlice,
+ k127_ObjectInfoIndexFirstJunk + k33_JunkTypeScreamerSlice | k0x8000_randomDrop,
+ 0
+ };
+ static uint16 fixedPossessionCreature15MagnetaWormWorm[4] = { // @ G0252_aui_Graphic559_FixedPossessionsCreature15MagentaWorm_Worm
+ k127_ObjectInfoIndexFirstJunk + k34_JunkTypeWormRound,
+ k127_ObjectInfoIndexFirstJunk + k34_JunkTypeWormRound | k0x8000_randomDrop,
+ k127_ObjectInfoIndexFirstJunk + k34_JunkTypeWormRound | k0x8000_randomDrop,
+ 0
+ };
+ static uint16 fixedPossessionCreature24RedDragon[11] = { // @ G0253_aui_Graphic559_FixedPossessionsCreature24RedDragon
+ k127_ObjectInfoIndexFirstJunk + k36_JunkTypeDragonSteak,
+ k127_ObjectInfoIndexFirstJunk + k36_JunkTypeDragonSteak,
+ k127_ObjectInfoIndexFirstJunk + k36_JunkTypeDragonSteak,
+ k127_ObjectInfoIndexFirstJunk + k36_JunkTypeDragonSteak,
+ k127_ObjectInfoIndexFirstJunk + k36_JunkTypeDragonSteak,
+ k127_ObjectInfoIndexFirstJunk + k36_JunkTypeDragonSteak,
+ k127_ObjectInfoIndexFirstJunk + k36_JunkTypeDragonSteak,
+ k127_ObjectInfoIndexFirstJunk + k36_JunkTypeDragonSteak,
+ k127_ObjectInfoIndexFirstJunk + k36_JunkTypeDragonSteak | k0x8000_randomDrop,
+ k127_ObjectInfoIndexFirstJunk + k36_JunkTypeDragonSteak | k0x8000_randomDrop, 0};
+
+ uint16 *fixedPossessions;
+ bool cursedPossessions = false;
+ switch (creatureType) {
+ case k4_CreatureTypePainRatHellHound:
+ fixedPossessions = fixedPossessionCreature4PainRatHellHound;
+ break;
+ case k6_CreatureTypeScreamer:
+ fixedPossessions = fixedPossessionCreature6screamer;
+ break;
+ case k7_CreatureTypeRockpile:
+ fixedPossessions = fixedPossessionCreature7rockRockPile;
+ break;
+ case k9_CreatureTypeStoneGolem:
+ fixedPossessions = fixedPossessionCreature9StoneGolem;
+ break;
+ case k12_CreatureTypeSkeleton:
+ fixedPossessions = fixedPossessionCreature12Skeleton;
+ break;
+ case k16_CreatureTypeTrolinAntman:
+ fixedPossessions = fixedPossessionCreatur16TrolinAntman;
+ break;
+ case k15_CreatureTypeMagnetaWormWorm:
+ fixedPossessions = fixedPossessionCreature15MagnetaWormWorm;
+ break;
+ case k18_CreatureTypeAnimatedArmourDethKnight:
+ cursedPossessions = true;
+ fixedPossessions = fixedPossessionCreature18AnimatedArmourDethKnight;
+ break;
+ case k24_CreatureTypeRedDragon:
+ fixedPossessions = fixedPossessionCreature24RedDragon;
+ break;
+ default:
+ return;
+ }
+
+ uint16 currFixedPossession = *fixedPossessions++;
+ bool weaponDropped = false;
+ while (currFixedPossession) {
+ if (getFlag(currFixedPossession, k0x8000_randomDrop) && _vm->getRandomNumber(2))
+ continue;
+
+ int16 currThingType;
+ if (clearFlag(currFixedPossession, k0x8000_randomDrop) >= k127_ObjectInfoIndexFirstJunk) {
+ currThingType = k10_JunkThingType;
+ currFixedPossession -= k127_ObjectInfoIndexFirstJunk;
+ } else if (currFixedPossession >= k69_ObjectInfoIndexFirstArmour) {
+ currThingType = k6_ArmourThingType;
+ currFixedPossession -= k69_ObjectInfoIndexFirstArmour;
+ } else {
+ weaponDropped = true;
+ currThingType = k5_WeaponThingType;
+ currFixedPossession -= k23_ObjectInfoIndexFirstWeapon;
+ }
+
+ Thing nextUnusedThing = _vm->_dungeonMan->getUnusedThing(currThingType);
+ if ((nextUnusedThing) == Thing::_none)
+ continue;
+
+ Weapon *currWeapon = (Weapon *)_vm->_dungeonMan->getThingData(nextUnusedThing);
+ /* The same pointer type is used no matter the actual type k5_WeaponThingType, k6_ArmourThingType or k10_JunkThingType */
+ currWeapon->setType(currFixedPossession);
+ currWeapon->setCursed(cursedPossessions);
+ nextUnusedThing = thingWithNewCell(nextUnusedThing, ((cell == k255_CreatureTypeSingleCenteredCreature) || !_vm->getRandomNumber(4)) ? _vm->getRandomNumber(4) : cell);
+ _vm->_moveSens->getMoveResult(nextUnusedThing, kM1_MapXNotOnASquare, 0, mapX, mapY);
+ currFixedPossession = *fixedPossessions++;
+ }
+ _vm->_sound->requestPlay(weaponDropped ? k00_soundMETALLIC_THUD : k04_soundWOODEN_THUD_ATTACK_TROLIN_ANTMAN_STONE_GOLEM, mapX, mapY, mode);
+}
+
+int16 GroupMan::getDirsWhereDestIsVisibleFromSource(int16 srcMapX, int16 srcMapY, int16 destMapX, int16 destMapY) {
+ if (srcMapX == destMapX) {
+ _vm->_projexpl->_secondaryDirToOrFromParty = (_vm->getRandomNumber(65536) & 0x0002) + 1; /* Resulting direction may be 1 or 3 (East or West) */
+ if (srcMapY > destMapY)
+ return kDirNorth;
+
+ return kDirSouth;
+ }
+ if (srcMapY == destMapY) {
+ _vm->_projexpl->_secondaryDirToOrFromParty = (_vm->getRandomNumber(65536) & 0x0002) + 0; /* Resulting direction may be 0 or 2 (North or South) */
+ if (srcMapX > destMapX)
+ return kDirWest;
+
+ return kDirEast;
+ }
+
+ int16 curDirection = kDirNorth;
+ for (;;) {
+ if (isDestVisibleFromSource(curDirection, srcMapX, srcMapY, destMapX, destMapY)) {
+ _vm->_projexpl->_secondaryDirToOrFromParty = returnNextVal(curDirection);
+ if (!isDestVisibleFromSource(_vm->_projexpl->_secondaryDirToOrFromParty, srcMapX, srcMapY, destMapX, destMapY)) {
+ _vm->_projexpl->_secondaryDirToOrFromParty = returnPrevVal(curDirection);
+ if ((curDirection != kDirNorth) || !isDestVisibleFromSource(_vm->_projexpl->_secondaryDirToOrFromParty, srcMapX, srcMapY, destMapX, destMapY)) {
+ _vm->_projexpl->_secondaryDirToOrFromParty = returnNextVal((_vm->getRandomNumber(65536) & 0x0002) + curDirection);
+ return curDirection;
+ }
+ }
+ if (_vm->getRandomNumber(2)) {
+ int16 primaryDirection = _vm->_projexpl->_secondaryDirToOrFromParty;
+ _vm->_projexpl->_secondaryDirToOrFromParty = curDirection;
+ return primaryDirection;
+ }
+ return curDirection;
+ }
+ curDirection++;
+ }
+}
+
+bool GroupMan::isDestVisibleFromSource(uint16 dir, int16 srcMapX, int16 srcMapY, int16 destMapX, int16 destMapY) {
+ switch (dir) { /* If direction is not 'West' then swap variables so that the same test as for west can be applied */
+ case kDirSouth:
+ SWAP(srcMapX, destMapY);
+ SWAP(destMapX, srcMapY);
+ break;
+ case kDirEast:
+ SWAP(srcMapX, destMapX);
+ SWAP(destMapY, srcMapY);
+ break;
+ case kDirNorth:
+ SWAP(srcMapX, srcMapY);
+ SWAP(destMapX, destMapY);
+ break;
+ }
+ return ((srcMapX -= (destMapX - 1)) > 0) && ((((srcMapY -= destMapY) < 0) ? -srcMapY : srcMapY) <= srcMapX);
+}
+
+bool GroupMan::groupIsDoorDestoryedByAttack(uint16 mapX, uint16 mapY, int16 attack, bool magicAttack, int16 ticks) {
+ Door *curDoor = (Door *)_vm->_dungeonMan->getSquareFirstThingData(mapX, mapY);
+ if ((magicAttack && !curDoor->isMagicDestructible()) || (!magicAttack && !curDoor->isMeleeDestructible()))
+ return false;
+
+ if (attack >= _vm->_dungeonMan->_currMapDoorInfo[curDoor->getType()]._defense) {
+ byte *curSquare = &_vm->_dungeonMan->_currMapData[mapX][mapY];
+ if (Square(*curSquare).getDoorState() == k4_doorState_CLOSED) {
+ if (ticks) {
+ TimelineEvent newEvent;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_currMapIndex, _vm->_gameTime + ticks);
+ newEvent._type = k2_TMEventTypeDoorDestruction;
+ newEvent._priority = 0;
+ newEvent._B._location._mapX = mapX;
+ newEvent._B._location._mapY = mapY;
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ } else {
+ ((Square *)curSquare)->setDoorState(k5_doorState_DESTROYED);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+Thing GroupMan::groupGetThing(int16 mapX, int16 mapY) {
+ Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
+ while ((curThing != Thing::_endOfList) && (curThing.getType() != k4_GroupThingType))
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+
+ return curThing;
+}
+
+int16 GroupMan::groupGetDamageCreatureOutcome(Group *group, uint16 creatureIndex, int16 mapX, int16 mapY, int16 damage, bool notMoving) {
+ uint16 creatureType = group->_type;
+ CreatureInfo *creatureInfo = &_vm->_dungeonMan->_creatureInfos[creatureType];
+ if (getFlag(creatureInfo->_attributes, k0x2000_MaskCreatureInfo_archenemy)) /* Lord Chaos cannot be damaged */
+ return k0_outcomeKilledNoCreaturesInGroup;
+
+ if (group->_health[creatureIndex] <= damage) {
+ uint16 groupCells = getGroupCells(group, _vm->_dungeonMan->_currMapIndex);
+ uint16 cell = (groupCells == k255_CreatureTypeSingleCenteredCreature) ? k255_CreatureTypeSingleCenteredCreature : getCreatureValue(groupCells, creatureIndex);
+ uint16 creatureCount = group->getCount();
+ uint16 retVal;
+
+ if (!creatureCount) { /* If there is a single creature in the group */
+ if (notMoving) {
+ dropGroupPossessions(mapX, mapY, groupGetThing(mapX, mapY), k2_soundModePlayOneTickLater);
+ groupDelete(mapX, mapY);
+ }
+ retVal = k2_outcomeKilledAllCreaturesInGroup;
+ } else { /* If there are several creatures in the group */
+ uint16 groupDirections = getGroupDirections(group, _vm->_dungeonMan->_currMapIndex);
+ if (getFlag(creatureInfo->_attributes, k0x0200_MaskCreatureInfo_dropFixedPoss)) {
+ if (notMoving)
+ dropCreatureFixedPossessions(creatureType, mapX, mapY, cell, k2_soundModePlayOneTickLater);
+ else
+ _dropMovingCreatureFixedPossessionsCell[_dropMovingCreatureFixedPossCellCount++] = cell;
+ }
+ bool currentMapIsPartyMap = (_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex);
+ ActiveGroup *activeGroup = nullptr;
+ if (currentMapIsPartyMap)
+ activeGroup = &_activeGroups[group->getActiveGroupIndex()];
+
+ if (group->getBehaviour() == k6_behavior_ATTACK) {
+ TimelineEvent *curEvent = _vm->_timeline->_events;
+ for (uint16 eventIndex = 0; eventIndex < _vm->_timeline->_eventMaxCount; eventIndex++) {
+ uint16 curEventType = curEvent->_type;
+ if ((getMap(curEvent->_mapTime) == _vm->_dungeonMan->_currMapIndex) &&
+ (curEvent->_B._location._mapX == mapX) &&
+ (curEvent->_B._location._mapY == mapY) &&
+ (curEventType > k32_TMEventTypeUpdateAspectGroup) &&
+ (curEventType < k41_TMEventTypeUpdateBehaviour_3 + 1)) {
+ uint16 nextCreatureIndex;
+ if (curEventType < k37_TMEventTypeUpdateBehaviourGroup)
+ nextCreatureIndex = curEventType - k33_TMEventTypeUpdateAspectCreature_0; /* Get creature index for events 33 to 36 */
+ else
+ nextCreatureIndex = curEventType - k38_TMEventTypeUpdateBehaviour_0; /* Get creature index for events 38 to 41 */
+
+ if (nextCreatureIndex == creatureIndex)
+ _vm->_timeline->deleteEvent(eventIndex);
+ else if (nextCreatureIndex > creatureIndex) {
+ curEvent->_type -= 1;
+ _vm->_timeline->fixChronology(_vm->_timeline->getIndex(eventIndex));
+ }
+ }
+ curEvent++;
+ }
+
+ uint16 fearResistance = creatureInfo->getFearResistance();
+ if (currentMapIsPartyMap && (fearResistance) != k15_immuneToFear) {
+ fearResistance += creatureCount - 1;
+ if (fearResistance < _vm->getRandomNumber(16)) { /* Test if the death of a creature frightens the remaining creatures in the group */
+ activeGroup->_delayFleeingFromTarget = _vm->getRandomNumber(100 - (fearResistance << 2)) + 20;
+ group->setBehaviour(k5_behavior_FLEE);
+ }
+ }
+ }
+ uint16 nextCreatureIndex = creatureIndex;
+ for (uint16 curCreatureIndex = creatureIndex; curCreatureIndex < creatureCount; curCreatureIndex++) {
+ nextCreatureIndex++;
+ group->_health[curCreatureIndex] = group->_health[nextCreatureIndex];
+ groupDirections = getGroupValueUpdatedWithCreatureValue(groupDirections, curCreatureIndex, getCreatureValue(groupDirections, nextCreatureIndex));
+ groupCells = getGroupValueUpdatedWithCreatureValue(groupCells, curCreatureIndex, getCreatureValue(groupCells, nextCreatureIndex));
+ if (currentMapIsPartyMap)
+ activeGroup->_aspect[curCreatureIndex] = activeGroup->_aspect[nextCreatureIndex];
+ }
+ groupCells &= 0x003F;
+ _vm->_dungeonMan->setGroupCells(group, groupCells, _vm->_dungeonMan->_currMapIndex);
+ _vm->_dungeonMan->setGroupDirections(group, groupDirections, _vm->_dungeonMan->_currMapIndex);
+ group->setCount(group->getCount() - 1);
+ retVal = k1_outcomeKilledSomeCreaturesInGroup;
+ }
+
+ uint16 creatureSize = getFlag(creatureInfo->_attributes, k0x0003_MaskCreatureInfo_size);
+ uint16 attack;
+ if (creatureSize == k0_MaskCreatureSizeQuarter)
+ attack = 110;
+ else if (creatureSize == k1_MaskCreatureSizeHalf)
+ attack = 190;
+ else
+ attack = 255;
+
+ _vm->_projexpl->createExplosion(Thing::_explSmoke, attack, mapX, mapY, cell); /* BUG0_66 Smoke is placed on the source map instead of the destination map when a creature dies by falling through a pit. The game has a special case to correctly drop the creature possessions on the destination map but there is no such special case for the smoke. Note that the death must be caused by the damage of the fall (there is no smoke if the creature is removed because its type is not allowed on the destination map). However this bug has no visible consequence because of BUG0_26: the smoke explosion falls in the pit right after being placed in the dungeon and before being drawn on screen so it is only visible on the destination square */
+ return retVal;
+ }
+
+ if (damage > 0)
+ group->_health[creatureIndex] -= damage;
+
+ return k0_outcomeKilledNoCreaturesInGroup;
+}
+
+void GroupMan::groupDelete(int16 mapX, int16 mapY) {
+ Thing groupThing = groupGetThing(mapX, mapY);
+ if (groupThing == Thing::_endOfList)
+ return;
+
+ Group *group = (Group *)_vm->_dungeonMan->getThingData(groupThing);
+ for (uint16 i = 0; i < 4; ++i)
+ group->_health[i] = 0;
+ _vm->_moveSens->getMoveResult(groupThing, mapX, mapY, kM1_MapXNotOnASquare, 0);
+ group->_nextThing = Thing::_none;
+ if (_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) {
+ _activeGroups[group->getActiveGroupIndex()]._groupThingIndex = -1;
+ _currActiveGroupCount--;
+ }
+ groupDeleteEvents(mapX, mapY);
+}
+
+void GroupMan::groupDeleteEvents(int16 mapX, int16 mapY) {
+ TimelineEvent *curEvent = _vm->_timeline->_events;
+ for (int16 eventIndex = 0; eventIndex < _vm->_timeline->_eventMaxCount; eventIndex++) {
+ uint16 curEventType = curEvent->_type;
+ if ((getMap(curEvent->_mapTime) == _vm->_dungeonMan->_currMapIndex) &&
+ (curEventType > k29_TMEventTypeGroupReactionDangerOnSquare - 1) && (curEventType < k41_TMEventTypeUpdateBehaviour_3 + 1) &&
+ (curEvent->_B._location._mapX == mapX) && (curEvent->_B._location._mapY == mapY)) {
+ _vm->_timeline->deleteEvent(eventIndex);
+ }
+ curEvent++;
+ }
+}
+
+uint16 GroupMan::getGroupValueUpdatedWithCreatureValue(uint16 groupVal, uint16 creatureIndex, uint16 creatureVal) {
+ creatureVal &= 0x0003;
+ creatureIndex <<= 1;
+ creatureVal <<= creatureIndex;
+ return creatureVal | (groupVal & ~(3 << creatureVal));
+}
+
+int16 GroupMan::getDamageAllCreaturesOutcome(Group *group, int16 mapX, int16 mapY, int16 attack, bool notMoving) {
+ bool killedSomeCreatures = false;
+ bool killedAllCreatures = true;
+ _dropMovingCreatureFixedPossCellCount = 0;
+ if (attack > 0) {
+ int16 creatureIndex = group->getCount();
+ uint16 randomAttackSeed = (attack >> 3) + 1;
+ attack -= randomAttackSeed;
+ randomAttackSeed <<= 1;
+ do {
+ int16 outcomeVal = groupGetDamageCreatureOutcome(group, creatureIndex, mapX, mapY, attack + _vm->getRandomNumber(randomAttackSeed), notMoving);
+ killedAllCreatures = outcomeVal && killedAllCreatures;
+ killedSomeCreatures = killedSomeCreatures || outcomeVal;
+ } while (creatureIndex--);
+ if (killedAllCreatures)
+ return k2_outcomeKilledAllCreaturesInGroup;
+
+ if (killedSomeCreatures)
+ return k1_outcomeKilledSomeCreaturesInGroup;
+ }
+
+ return k0_outcomeKilledNoCreaturesInGroup;
+}
+
+int16 GroupMan::groupGetResistanceAdjustedPoisonAttack(uint16 creatreType, int16 poisonAttack) {
+ int16 poisonResistance = _vm->_dungeonMan->_creatureInfos[creatreType].getPoisonResistance();
+
+ if (!poisonAttack || (poisonResistance == k15_immuneToPoison))
+ return 0;
+
+ return ((poisonAttack + _vm->getRandomNumber(4)) << 3) / (poisonResistance + 1);
+}
+
+void GroupMan::processEvents29to41(int16 eventMapX, int16 eventMapY, int16 eventType, uint16 ticks) {
+ int16 L0446_i_Multiple = 0;
+#define AL0446_i_Direction L0446_i_Multiple
+#define AL0446_i_Ticks L0446_i_Multiple
+#define AL0446_i_Behavior2Or3 L0446_i_Multiple
+#define AL0446_i_CreatureAspectIndex L0446_i_Multiple
+#define AL0446_i_Range L0446_i_Multiple
+#define AL0446_i_CreatureAttributes L0446_i_Multiple
+#define AL0446_i_Cell L0446_i_Multiple
+#define AL0446_i_GroupCellsCriteria L0446_i_Multiple
+ int16 L0447_i_Multiple;
+#define AL0447_i_Behavior L0447_i_Multiple
+#define AL0447_i_CreatureIndex L0447_i_Multiple
+#define AL0447_i_ReferenceDirection L0447_i_Multiple
+#define AL0447_i_Ticks L0447_i_Multiple
+ int16 L0450_i_Multiple;
+#define AL0450_i_DestinationMapX L0450_i_Multiple
+#define AL0450_i_DistanceXToParty L0450_i_Multiple
+#define AL0450_i_TargetMapX L0450_i_Multiple
+ int16 L0451_i_Multiple;
+#define AL0451_i_DestinationMapY L0451_i_Multiple
+#define AL0451_i_DistanceYToParty L0451_i_Multiple
+#define AL0451_i_TargetMapY L0451_i_Multiple
+
+ /* If the party is not on the map specified in the event and the event type is not one of 32, 33, 37, 38 then the event is ignored */
+ if ((_vm->_dungeonMan->_currMapIndex != _vm->_dungeonMan->_partyMapIndex)
+ && (eventType != k37_TMEventTypeUpdateBehaviourGroup) && (eventType != k32_TMEventTypeUpdateAspectGroup)
+ && (eventType != k38_TMEventTypeUpdateBehaviour_0) && (eventType != k33_TMEventTypeUpdateAspectCreature_0))
+ return;
+
+ Thing groupThing = groupGetThing(eventMapX, eventMapY);
+ /* If there is no creature at the location specified in the event then the event is ignored */
+ if (groupThing == Thing::_endOfList)
+ return;
+
+ Group *curGroup = (Group *)_vm->_dungeonMan->getThingData(groupThing);
+ CreatureInfo creatureInfo = _vm->_dungeonMan->_creatureInfos[curGroup->_type];
+ /* Update the event */
+ TimelineEvent nextEvent;
+ setMapAndTime(nextEvent._mapTime, _vm->_dungeonMan->_currMapIndex, _vm->_gameTime);
+ nextEvent._priority = 255 - creatureInfo._movementTicks; /* The fastest creatures (with small MovementTicks value) get higher event priority */
+ nextEvent._B._location._mapX = eventMapX;
+ nextEvent._B._location._mapY = eventMapY;
+ /* If the creature is not on the party map then try and move the creature in a random direction and place a new event 37 in the timeline for the next creature movement */
+ if (_vm->_dungeonMan->_currMapIndex != _vm->_dungeonMan->_partyMapIndex) {
+ if (isMovementPossible(&creatureInfo, eventMapX, eventMapY, AL0446_i_Direction = _vm->getRandomNumber(4), false)) { /* BUG0_67 A group that is not on the party map may wrongly move or not move into a teleporter. Normally, a creature type with Wariness >= 10 (Vexirk, Materializer / Zytaz, Demon, Lord Chaos, Red Dragon / Dragon) would only move into a teleporter if the creature type is allowed on the destination map. However, the variable G0380_T_CurrentGroupThing identifying the group is not set before being used by F0139_DUNGEON_IsCreatureAllowedOnMap called by f202_isMovementPossible so the check to see if the creature type is allowed may operate on another creature type and thus return an incorrect result, causing the creature to teleport while it should not, or not to teleport while it should */
+ AL0450_i_DestinationMapX = eventMapX;
+ AL0451_i_DestinationMapY = eventMapY;
+ AL0450_i_DestinationMapX += _vm->_dirIntoStepCountEast[AL0446_i_Direction];
+ AL0451_i_DestinationMapY += _vm->_dirIntoStepCountNorth[AL0446_i_Direction];
+ if (_vm->_moveSens->getMoveResult(groupThing, eventMapX, eventMapY, AL0450_i_DestinationMapX, AL0451_i_DestinationMapY))
+ return;
+ nextEvent._B._location._mapX = _vm->_moveSens->_moveResultMapX;
+ nextEvent._B._location._mapY = _vm->_moveSens->_moveResultMapY;
+ }
+ nextEvent._type = k37_TMEventTypeUpdateBehaviourGroup;
+ AL0446_i_Ticks = MAX(ABS(_vm->_dungeonMan->_currMapIndex - _vm->_dungeonMan->_partyMapIndex) << 4, creatureInfo._movementTicks << 1);
+ /* BUG0_68 A group moves or acts with a wrong timing. Event is added below but L0465_s_NextEvent.C.Ticks has not been initialized. No consequence while the group is not on the party map. When the party enters the group map the first group event may have a wrong timing */
+T0209005_AddEventAndReturn:
+ nextEvent._mapTime += AL0446_i_Ticks;
+ _vm->_timeline->addEventGetEventIndex(&nextEvent);
+ return;
+ }
+ /* If the creature is Lord Chaos then ignore the event if the game is won. Initialize data to analyze Fluxcages */
+ bool isArchEnemy = getFlag(creatureInfo._attributes, k0x2000_MaskCreatureInfo_archenemy);
+ if (isArchEnemy) {
+ if (_vm->_gameWon)
+ return;
+
+ _fluxCageCount = 0;
+ _fluxCages[0] = 0;
+ }
+ ActiveGroup *activeGroup = &_activeGroups[curGroup->getActiveGroupIndex()];
+
+ // CHECKME: Terrible mix of types
+ int16 ticksSinceLastMove = (unsigned char)_vm->_gameTime - activeGroup->_lastMoveTime;
+ if (ticksSinceLastMove < 0)
+ ticksSinceLastMove += 256;
+
+ int16 movementTicks = creatureInfo._movementTicks;
+ if (movementTicks == k255_immobile)
+ movementTicks = 100;
+
+ if (_vm->_championMan->_party._freezeLifeTicks && !isArchEnemy) { /* If life is frozen and the creature is not Lord Chaos (Lord Chaos is immune to Freeze Life) then reschedule the event later (except for reactions which are ignored when life if frozen) */
+ if (eventType < 0)
+ return;
+ nextEvent._type = eventType;
+ nextEvent._C._ticks = ticks;
+ AL0446_i_Ticks = 4; /* Retry in 4 ticks */
+ goto T0209005_AddEventAndReturn;
+ }
+ /* If the specified event type is a 'reaction' instead of a real event from the timeline then create the corresponding reaction event with a delay:
+ For event kM1_TMEventTypeCreateReactionEvent31ParyIsAdjacent, the reaction time is 1 tick
+ For event kM2_TMEventTypeCreateReactionEvent30HitByProjectile and kM3_TMEventTypeCreateReactionEvent29DangerOnSquare, the reaction time may be 1 tick or slower: slow moving creatures react more slowly. The more recent is the last creature move, the slower the reaction */
+ if (eventType < 0) {
+ nextEvent._type = eventType + k32_TMEventTypeUpdateAspectGroup;
+ if (eventType == kM1_TMEventTypeCreateReactionEvent31ParyIsAdjacent) {
+ AL0446_i_Ticks = 1; /* Retry in 1 tick */
+ } else {
+ AL0446_i_Ticks = ((movementTicks + 2) >> 2) - ticksSinceLastMove;
+ if (AL0446_i_Ticks < 1) /* AL0446_i_Ticks is the reaction time */
+ AL0446_i_Ticks = 1; /* Retry in 1 tick */
+ }
+ goto T0209005_AddEventAndReturn; /* BUG0_68 A group moves or acts with a wrong timing. Event is added but L0465_s_NextEvent.C.Ticks has not been initialized */
+ }
+ AL0447_i_Behavior = curGroup->getBehaviour();
+ uint16 creatureCount = curGroup->getCount();
+ int16 creatureSize = getFlag(creatureInfo._attributes, k0x0003_MaskCreatureInfo_size);
+ AL0450_i_DistanceXToParty = ABS(eventMapX - _vm->_dungeonMan->_partyMapX);
+ AL0451_i_DistanceYToParty = ABS(eventMapY - _vm->_dungeonMan->_partyMapY);
+ _currentGroupMapX = eventMapX;
+ _currentGroupMapY = eventMapY;
+ _currGroupThing = groupThing;
+ _groupMovementTestedDirections[0] = 0;
+ _currGroupDistanceToParty = getDistanceBetweenSquares(eventMapX, eventMapY, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY);
+ _currGroupPrimaryDirToParty = getDirsWhereDestIsVisibleFromSource(eventMapX, eventMapY, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY);
+ _currGroupSecondaryDirToParty = _vm->_projexpl->_secondaryDirToOrFromParty;
+ int32 nextAspectUpdateTime = 0;
+ bool notUpdateBehaviorFl = true;
+ bool newGroupDirectionFound;
+ bool approachAfterReaction = false;
+ bool moveToPriorLocation = false;
+ int16 distanceToVisibleParty = 0;
+
+ if (eventType <= k31_TMEventTypeGroupReactionPartyIsAdjecent) { /* Process Reaction events 29 to 31 */
+ switch (eventType = eventType - k32_TMEventTypeUpdateAspectGroup) {
+ case kM1_TMEventTypeCreateReactionEvent31ParyIsAdjacent: /* This event is used when the party bumps into a group or attacks a group physically (not with a spell). It causes the creature behavior to change to attack if it is not already attacking the party or fleeing from target */
+ if ((AL0447_i_Behavior != k6_behavior_ATTACK) && (AL0447_i_Behavior != k5_behavior_FLEE)) {
+ groupDeleteEvents(eventMapX, eventMapY);
+ goto T0209044_SetBehavior6_Attack;
+ }
+ activeGroup->_targetMapX = _vm->_dungeonMan->_partyMapX;
+ activeGroup->_targetMapY = _vm->_dungeonMan->_partyMapY;
+ return;
+ case kM2_TMEventTypeCreateReactionEvent30HitByProjectile: /* This event is used for the reaction of a group after a projectile impacted with one creature in the group (some creatures may have been killed) */
+ if ((AL0447_i_Behavior == k6_behavior_ATTACK) || (AL0447_i_Behavior == k5_behavior_FLEE)) /* If the creature is attacking the party or fleeing from the target then there is no reaction */
+ return;
+ AL0446_i_Behavior2Or3 = ((AL0447_i_Behavior == k3_behavior_USELESS) || (AL0447_i_Behavior == k2_behavior_USELESS));
+ if (AL0446_i_Behavior2Or3 || (_vm->getRandomNumber(4))) { /* BUG0_00 Useless code. Behavior cannot be 2 nor 3 because these values are never used. The actual condition is thus: if 3/4 chances */
+ if (!groupGetDistanceToVisibleParty(curGroup, kM1_wholeCreatureGroup, eventMapX, eventMapY)) { /* If the group cannot see the party then look in a random direction to try and search for the party */
+ approachAfterReaction = newGroupDirectionFound = false;
+ goto T0209073_SetDirectionGroup;
+ }
+ if (AL0446_i_Behavior2Or3 || (_vm->getRandomNumber(4))) /* BUG0_00 Useless code. Behavior cannot be 2 nor 3 because these values are never used. The actual condition is thus: if 3/4 chances then no reaction */
+ return;
+ } /* No 'break': proceed to instruction after the next 'case' below. Reaction is to move in a random direction to try and avoid other projectiles */
+ case kM3_TMEventTypeCreateReactionEvent29DangerOnSquare: /* This event is used when some creatures in the group were killed by a Poison Cloud or by a closing door or if Lord Chaos is surrounded by 3 Fluxcages. It causes the creature to move in a random direction to avoid the danger */
+ approachAfterReaction = (AL0447_i_Behavior == k6_behavior_ATTACK); /* If the creature behavior is 'Attack' and it has to move to avoid danger then it will change its behavior to 'Approach' after the movement */
+ newGroupDirectionFound = false;
+ goto T0209058_MoveInRandomDirection;
+ }
+ }
+ if (eventType < k37_TMEventTypeUpdateBehaviourGroup) { /* Process Update Aspect events 32 to 36 */
+ nextEvent._type = eventType + 5;
+ if (groupGetDistanceToVisibleParty(curGroup, kM1_wholeCreatureGroup, eventMapX, eventMapY)) {
+ if ((AL0447_i_Behavior != k6_behavior_ATTACK) && (AL0447_i_Behavior != k5_behavior_FLEE)) {
+ if (getDistance(_vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, eventMapX, eventMapY) <= 1)
+ goto T0209044_SetBehavior6_Attack;
+ if (((AL0447_i_Behavior == k0_behavior_WANDER) || (AL0447_i_Behavior == k3_behavior_USELESS)) && (AL0447_i_Behavior != k7_behavior_APPROACH)) /* BUG0_00 Useless code. Behavior cannot be 3 because this value is never used. Moreover, the second condition in the && is redundant (if the value is 0 or 3, it cannot be 7). The actual condition is: if (AL0447_i_Behavior == k0_behavior_WANDER) */
+ goto T0209054_SetBehavior7_Approach;
+ }
+ activeGroup->_targetMapX = _vm->_dungeonMan->_partyMapX;
+ activeGroup->_targetMapY = _vm->_dungeonMan->_partyMapY;
+ }
+ if (AL0447_i_Behavior == k6_behavior_ATTACK) {
+ AL0446_i_CreatureAspectIndex = eventType - k33_TMEventTypeUpdateAspectCreature_0; /* Value -1 for event 32, meaning aspect will be updated for all creatures in the group */
+ nextAspectUpdateTime = getCreatureAspectUpdateTime(activeGroup, AL0446_i_CreatureAspectIndex, getFlag(activeGroup->_aspect[AL0446_i_CreatureAspectIndex], k0x0080_MaskActiveGroupIsAttacking));
+ goto T0209136;
+ }
+ if ((AL0450_i_DistanceXToParty > 3) || (AL0451_i_DistanceYToParty > 3)) {
+ nextAspectUpdateTime = _vm->_gameTime + ((creatureInfo._animationTicks >> 4) & 0xF);
+ goto T0209136;
+ }
+ } else { /* Process Update Behavior events 37 to 41 */
+ int16 primaryDirectionToOrFromParty;
+ notUpdateBehaviorFl = false;
+ if (ticks)
+ nextAspectUpdateTime = _vm->_gameTime;
+
+ if (eventType == k37_TMEventTypeUpdateBehaviourGroup) { /* Process event 37, Update Group Behavior */
+ bool allowMovementOverFakePitsAndFakeWalls;
+ if ((AL0447_i_Behavior == k0_behavior_WANDER) || (AL0447_i_Behavior == k2_behavior_USELESS) || (AL0447_i_Behavior == k3_behavior_USELESS)) { /* BUG0_00 Useless code. Behavior cannot be 2 nor 3 because these values are never used. The actual condition is: if (AL0447_i_Behavior == k0_behavior_WANDER) */
+ distanceToVisibleParty = groupGetDistanceToVisibleParty(curGroup, kM1_wholeCreatureGroup, eventMapX, eventMapY);
+ if (distanceToVisibleParty) {
+ if ((distanceToVisibleParty <= (creatureInfo.getAttackRange())) && ((!AL0450_i_DistanceXToParty) || (!AL0451_i_DistanceYToParty))) { /* If the creature is in range for attack and on the same row or column as the party on the map */
+T0209044_SetBehavior6_Attack:
+ if (eventType == kM2_TMEventTypeCreateReactionEvent30HitByProjectile) {
+ groupDeleteEvents(eventMapX, eventMapY);
+ }
+ activeGroup->_targetMapX = _vm->_dungeonMan->_partyMapX;
+ activeGroup->_targetMapY = _vm->_dungeonMan->_partyMapY;
+ curGroup->setBehaviour(k6_behavior_ATTACK);
+ AL0446_i_Direction = _currGroupPrimaryDirToParty;
+ for (AL0447_i_CreatureIndex = creatureCount; AL0447_i_CreatureIndex >= 0; AL0447_i_CreatureIndex--) {
+ if ((getCreatureValue(activeGroup->_directions, AL0447_i_CreatureIndex) != AL0446_i_Direction) &&
+ ((!AL0447_i_CreatureIndex) || (!_vm->getRandomNumber(2)))) {
+ setGroupDirection(activeGroup, AL0446_i_Direction, AL0447_i_CreatureIndex, creatureCount && (creatureSize == k1_MaskCreatureSizeHalf));
+ M32_setTime(nextEvent._mapTime, _vm->_gameTime + _vm->getRandomNumber(4) + 2); /* Random delay represents the time for the creature to turn */
+ } else {
+ M32_setTime(nextEvent._mapTime, _vm->_gameTime + 1);
+ }
+ if (notUpdateBehaviorFl) {
+ nextEvent._mapTime += MIN((uint16)((creatureInfo._attackTicks >> 1) + _vm->getRandomNumber(4)), ticks);
+ }
+ nextEvent._type = k38_TMEventTypeUpdateBehaviour_0 + AL0447_i_CreatureIndex;
+ addGroupEvent(&nextEvent, getCreatureAspectUpdateTime(activeGroup, AL0447_i_CreatureIndex, false));
+ }
+ return;
+ }
+ if (AL0447_i_Behavior != k2_behavior_USELESS) { /* BUG0_00 Useless code. Behavior cannot be 2 because this value is never used */
+T0209054_SetBehavior7_Approach:
+ curGroup->setBehaviour(k7_behavior_APPROACH);
+ activeGroup->_targetMapX = _vm->_dungeonMan->_partyMapX;
+ activeGroup->_targetMapY = _vm->_dungeonMan->_partyMapY;
+ nextEvent._mapTime += 1;
+ goto T0209134_SetEvent37;
+ }
+ } else {
+ if (AL0447_i_Behavior == k0_behavior_WANDER) {
+ primaryDirectionToOrFromParty = getSmelledPartyPrimaryDirOrdinal(&creatureInfo, eventMapX, eventMapY);
+ if (primaryDirectionToOrFromParty) {
+ primaryDirectionToOrFromParty--;
+ allowMovementOverFakePitsAndFakeWalls = false;
+ goto T0209085_SingleSquareMove;
+ }
+ newGroupDirectionFound = false;
+ if (_vm->getRandomNumber(2)) {
+T0209058_MoveInRandomDirection:
+ AL0446_i_Direction = _vm->getRandomNumber(4);
+ AL0447_i_ReferenceDirection = AL0446_i_Direction;
+ do {
+ AL0450_i_DestinationMapX = eventMapX;
+ AL0451_i_DestinationMapY = eventMapY;
+ AL0450_i_DestinationMapX += _vm->_dirIntoStepCountEast[AL0446_i_Direction], AL0451_i_DestinationMapY += _vm->_dirIntoStepCountNorth[AL0446_i_Direction];
+ if (((activeGroup->_priorMapX != AL0450_i_DestinationMapX) ||
+ (activeGroup->_priorMapY != AL0451_i_DestinationMapY) ||
+ (moveToPriorLocation = !_vm->getRandomNumber(4))) /* 1/4 chance of moving back to the square that the creature comes from */
+ && isMovementPossible(&creatureInfo, eventMapX, eventMapY, AL0446_i_Direction, false)) {
+T0209061_MoveGroup:
+ AL0447_i_Ticks = (movementTicks >> 1) - ticksSinceLastMove;
+ newGroupDirectionFound = (AL0447_i_Ticks <= 0);
+ if (newGroupDirectionFound) {
+ if (_vm->_moveSens->getMoveResult(groupThing, eventMapX, eventMapY, AL0450_i_DestinationMapX, AL0451_i_DestinationMapY))
+ return;
+ nextEvent._B._location._mapX = _vm->_moveSens->_moveResultMapX;
+ nextEvent._B._location._mapY = _vm->_moveSens->_moveResultMapY;;
+ activeGroup->_priorMapX = eventMapX;
+ activeGroup->_priorMapY = eventMapY;
+ activeGroup->_lastMoveTime = _vm->_gameTime;
+ } else {
+ movementTicks = AL0447_i_Ticks;
+ ticksSinceLastMove = -1;
+ }
+ break;
+ }
+ if (_groupMovementBlockedByParty) {
+ if ((eventType != kM3_TMEventTypeCreateReactionEvent29DangerOnSquare) &&
+ ((curGroup->getBehaviour() != k5_behavior_FLEE) ||
+ !getFirstPossibleMovementDirOrdinal(&creatureInfo, eventMapX, eventMapY, false) ||
+ _vm->getRandomNumber(2)))
+ goto T0209044_SetBehavior6_Attack;
+ activeGroup->_targetMapX = _vm->_dungeonMan->_partyMapX;
+ activeGroup->_targetMapY = _vm->_dungeonMan->_partyMapY;
+ }
+ AL0446_i_Direction = returnNextVal(AL0446_i_Direction);
+ } while (AL0446_i_Direction != AL0447_i_ReferenceDirection);
+ }
+ if (!newGroupDirectionFound &&
+ (ticksSinceLastMove != -1) &&
+ isArchEnemy &&
+ ((eventType == kM3_TMEventTypeCreateReactionEvent29DangerOnSquare) || !_vm->getRandomNumber(4))) { /* BUG0_15 The game hangs when you close a door on Lord Chaos. A condition is missing in the code to manage creatures and this may create an infinite loop between two parts in the code */
+ _vm->_projexpl->_secondaryDirToOrFromParty = returnNextVal(primaryDirectionToOrFromParty = _vm->getRandomNumber(4));
+ goto T0209089_DoubleSquareMove; /* BUG0_69 Memory corruption when you close a door on Lord Chaos. The local variable (L0454_i_PrimaryDirectionToOrFromParty) containing the direction where Lord Chaos tries to move may be used as an array index without being initialized and cause memory corruption */
+ }
+ if (newGroupDirectionFound || ((!_vm->getRandomNumber(4) || (distanceToVisibleParty <= creatureInfo.getSmellRange())) && (eventType != kM3_TMEventTypeCreateReactionEvent29DangerOnSquare))) {
+T0209073_SetDirectionGroup:
+ if (!newGroupDirectionFound && (ticksSinceLastMove >= 0)) { /* If direction is not found yet then look around in a random direction */
+ AL0446_i_Direction = _vm->getRandomNumber(4);
+ }
+ setDirGroup(activeGroup, AL0446_i_Direction, creatureCount, creatureSize);
+ }
+ /* If event is kM3_TMEventTypeCreateReactionEvent29DangerOnSquare or kM2_TMEventTypeCreateReactionEvent30HitByProjectile */
+ if (eventType < kM1_TMEventTypeCreateReactionEvent31ParyIsAdjacent) {
+ if (!newGroupDirectionFound)
+ return;
+ if (approachAfterReaction)
+ curGroup->setBehaviour(k7_behavior_APPROACH);
+
+ stopAttacking(activeGroup, eventMapX, eventMapY);
+ }
+ }
+ }
+ } else {
+ if (AL0447_i_Behavior == k7_behavior_APPROACH) {
+ distanceToVisibleParty = groupGetDistanceToVisibleParty(curGroup, kM1_wholeCreatureGroup, eventMapX, eventMapY);
+ if (distanceToVisibleParty) {
+ if ((distanceToVisibleParty <= creatureInfo.getAttackRange()) && ((!AL0450_i_DistanceXToParty) || (!AL0451_i_DistanceYToParty))) /* If the creature is in range for attack and on the same row or column as the party on the map */
+ goto T0209044_SetBehavior6_Attack;
+T0209081_RunTowardParty:
+ movementTicks++;
+ movementTicks = movementTicks >> 1; /* Running speed is half the movement ticks */
+ AL0450_i_TargetMapX = (activeGroup->_targetMapX = _vm->_dungeonMan->_partyMapX);
+ AL0451_i_TargetMapY = (activeGroup->_targetMapY = _vm->_dungeonMan->_partyMapY);
+ } else {
+T0209082_WalkTowardTarget:
+ AL0450_i_TargetMapX = activeGroup->_targetMapX;
+ AL0451_i_TargetMapY = activeGroup->_targetMapY;
+ /* If the creature reached its target but the party is not there anymore */
+ if ((eventMapX == AL0450_i_TargetMapX) && (eventMapY == AL0451_i_TargetMapY)) {
+ newGroupDirectionFound = false;
+ curGroup->setBehaviour(k0_behavior_WANDER);
+ goto T0209073_SetDirectionGroup;
+ }
+ }
+ allowMovementOverFakePitsAndFakeWalls = true;
+T0209084_SingleSquareMoveTowardParty:
+ primaryDirectionToOrFromParty = getDirsWhereDestIsVisibleFromSource(eventMapX, eventMapY, AL0450_i_TargetMapX, AL0451_i_TargetMapY);
+T0209085_SingleSquareMove:
+ if (isMovementPossible(&creatureInfo, eventMapX, eventMapY, AL0446_i_Direction = primaryDirectionToOrFromParty, allowMovementOverFakePitsAndFakeWalls) ||
+ isMovementPossible(&creatureInfo, eventMapX, eventMapY, AL0446_i_Direction = _vm->_projexpl->_secondaryDirToOrFromParty, allowMovementOverFakePitsAndFakeWalls && _vm->getRandomNumber(2)) ||
+ isMovementPossible(&creatureInfo, eventMapX, eventMapY, AL0446_i_Direction = returnOppositeDir((Direction)AL0446_i_Direction), false) ||
+ (!_vm->getRandomNumber(4) && isMovementPossible(&creatureInfo, eventMapX, eventMapY, AL0446_i_Direction = returnOppositeDir((Direction)primaryDirectionToOrFromParty), false))) {
+ AL0450_i_DestinationMapX = eventMapX;
+ AL0451_i_DestinationMapY = eventMapY;
+ AL0450_i_DestinationMapX += _vm->_dirIntoStepCountEast[AL0446_i_Direction], AL0451_i_DestinationMapY += _vm->_dirIntoStepCountNorth[AL0446_i_Direction];
+ goto T0209061_MoveGroup;
+ }
+ if (isArchEnemy) {
+T0209089_DoubleSquareMove:
+ getFirstPossibleMovementDirOrdinal(&creatureInfo, eventMapX, eventMapY, false); /* BUG0_00 Useless code. Returned value is ignored. When Lord Chaos teleports two squares away the ability to move to the first square is ignored which means Lord Chaos can teleport through walls or any other obstacle */
+ if (isArchenemyDoubleMovementPossible(&creatureInfo, eventMapX, eventMapY, AL0446_i_Direction = primaryDirectionToOrFromParty) ||
+ isArchenemyDoubleMovementPossible(&creatureInfo, eventMapX, eventMapY, AL0446_i_Direction = _vm->_projexpl->_secondaryDirToOrFromParty) ||
+ (_fluxCageCount && isArchenemyDoubleMovementPossible(&creatureInfo, eventMapX, eventMapY, AL0446_i_Direction = returnOppositeDir((Direction)AL0446_i_Direction))) ||
+ ((_fluxCageCount >= 2) && isArchenemyDoubleMovementPossible(&creatureInfo, eventMapX, eventMapY, AL0446_i_Direction = returnOppositeDir((Direction)primaryDirectionToOrFromParty)))) {
+ AL0450_i_DestinationMapX = eventMapX;
+ AL0451_i_DestinationMapY = eventMapY;
+ AL0450_i_DestinationMapX += _vm->_dirIntoStepCountEast[AL0446_i_Direction] * 2, AL0451_i_DestinationMapY += _vm->_dirIntoStepCountNorth[AL0446_i_Direction] * 2;
+ _vm->_sound->requestPlay(k17_soundBUZZ, AL0450_i_DestinationMapX, AL0451_i_DestinationMapY, k1_soundModePlayIfPrioritized);
+ goto T0209061_MoveGroup;
+ }
+ }
+ setDirGroup(activeGroup, primaryDirectionToOrFromParty, creatureCount, creatureSize);
+ } else {
+ if (AL0447_i_Behavior == k5_behavior_FLEE) {
+T0209094_FleeFromTarget:
+ allowMovementOverFakePitsAndFakeWalls = true;
+ /* If the creature can see the party then update target coordinates */
+ distanceToVisibleParty = groupGetDistanceToVisibleParty(curGroup, kM1_wholeCreatureGroup, eventMapX, eventMapY);
+ if (distanceToVisibleParty) {
+ AL0450_i_TargetMapX = (activeGroup->_targetMapX = _vm->_dungeonMan->_partyMapX);
+ AL0451_i_TargetMapY = (activeGroup->_targetMapY = _vm->_dungeonMan->_partyMapY);
+ } else {
+ if (!(--(activeGroup->_delayFleeingFromTarget))) { /* If the creature is not afraid anymore then stop fleeing from target */
+T0209096_SetBehavior0_Wander:
+ newGroupDirectionFound = false;
+ curGroup->setBehaviour(k0_behavior_WANDER);
+ goto T0209073_SetDirectionGroup;
+ }
+ if (_vm->getRandomNumber(2)) {
+ /* If the creature cannot move and the party is adjacent then stop fleeing */
+ if (!getFirstPossibleMovementDirOrdinal(&creatureInfo, eventMapX, eventMapY, false)) {
+ if (getDistance(eventMapX, eventMapY, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY) <= 1)
+ goto T0209096_SetBehavior0_Wander;
+ }
+ /* Set creature target to the home square where the creature was located when the party entered the map */
+ AL0450_i_TargetMapX = activeGroup->_homeMapX;
+ AL0451_i_TargetMapY = activeGroup->_homeMapY;
+ goto T0209084_SingleSquareMoveTowardParty;
+ }
+ AL0450_i_TargetMapX = activeGroup->_targetMapX;
+ AL0451_i_TargetMapY = activeGroup->_targetMapY;
+ }
+ /* Try and flee from the party (opposite direction) */
+ primaryDirectionToOrFromParty = returnOppositeDir((Direction)getDirsWhereDestIsVisibleFromSource(eventMapX, eventMapY, AL0450_i_TargetMapX, AL0451_i_TargetMapY));
+ _vm->_projexpl->_secondaryDirToOrFromParty = returnOppositeDir((Direction)_vm->_projexpl->_secondaryDirToOrFromParty);
+ movementTicks -= (movementTicks >> 2);
+ goto T0209085_SingleSquareMove;
+ }
+ }
+ }
+ } else { /* Process events 38 to 41, Update Creature Behavior */
+ if (AL0447_i_Behavior == k5_behavior_FLEE) {
+ if (creatureCount) {
+ stopAttacking(activeGroup, eventMapX, eventMapY);
+ }
+ goto T0209094_FleeFromTarget;
+ }
+ /* If the creature is attacking, then compute the next aspect update time and the next attack time */
+ if (getFlag(activeGroup->_aspect[AL0447_i_CreatureIndex = eventType - k38_TMEventTypeUpdateBehaviour_0], k0x0080_MaskActiveGroupIsAttacking)) {
+ nextAspectUpdateTime = getCreatureAspectUpdateTime(activeGroup, AL0447_i_CreatureIndex, false);
+ nextEvent._mapTime += ((AL0447_i_Ticks = creatureInfo._attackTicks) + _vm->getRandomNumber(4) - 1);
+ if (AL0447_i_Ticks > 15)
+ nextEvent._mapTime += _vm->getRandomNumber(8) - 2;
+ } else { /* If the creature is not attacking, then try attacking if possible */
+ if (AL0447_i_CreatureIndex > creatureCount) /* Ignore event if it is for a creature that is not in the group */
+ return;
+
+ primaryDirectionToOrFromParty = _currGroupPrimaryDirToParty;
+ distanceToVisibleParty = groupGetDistanceToVisibleParty(curGroup, AL0447_i_CreatureIndex, eventMapX, eventMapY);
+ /* If the party is visible, update the target coordinates */
+ if (distanceToVisibleParty) {
+ activeGroup->_targetMapX = _vm->_dungeonMan->_partyMapX;
+ activeGroup->_targetMapY = _vm->_dungeonMan->_partyMapY;
+ }
+ /* If there is a single creature in the group that is not full square sized and 1/4 chance */
+ if (!creatureCount && (creatureSize != k2_MaskCreatureSizeFull) && !((AL0446_i_GroupCellsCriteria = _vm->getRandomNumber(65536)) & 0x00C0)) {
+ if (activeGroup->_cells != k255_CreatureTypeSingleCenteredCreature) {
+ /* If the creature is not already on the center of the square then change its cell */
+ if (AL0446_i_GroupCellsCriteria & 0x0038) { /* 7/8 chances of changing cell to the center of the square */
+ activeGroup->_cells = k255_CreatureTypeSingleCenteredCreature;
+ } else { /* 1/8 chance of changing cell to the next or previous cell on the square */
+ AL0446_i_GroupCellsCriteria = normalizeModulo4(normalizeModulo4(activeGroup->_cells) + ((AL0446_i_GroupCellsCriteria & 0x0001) ? 1 : -1));
+ }
+ }
+ /* If 1/8 chance and the creature is not adjacent to the party and is a quarter square sized creature then process projectile impacts and update the creature cell if still alive. When the creature is not in front of the party, it has 7/8 chances of dodging a projectile by moving to another cell or staying in the center of the square */
+ if (!(AL0446_i_GroupCellsCriteria & 0x0038) && (distanceToVisibleParty != 1) && (creatureSize == k0_MaskCreatureSizeQuarter)) {
+ if (_vm->_projexpl->projectileGetImpactCount(kM1_CreatureElemType, eventMapX, eventMapY, activeGroup->_cells) && (_vm->_projexpl->_creatureDamageOutcome == k2_outcomeKilledAllCreaturesInGroup)) /* This call to F0218_PROJECTILE_GetImpactCount works fine because there is a single creature in the group so L0445_ps_ActiveGroup->Cells contains only one cell index */
+ return;
+ activeGroup->_cells = normalizeModulo4(AL0446_i_GroupCellsCriteria);
+ }
+ }
+ /* If the creature can see the party and is looking in the party direction or can attack in all direction */
+ if (distanceToVisibleParty &&
+ (getFlag(creatureInfo._attributes, k0x0004_MaskCreatureInfo_sideAttack) ||
+ getCreatureValue(activeGroup->_directions, AL0447_i_CreatureIndex) == primaryDirectionToOrFromParty)) {
+ /* If the creature is in range to attack the party and random test succeeds */
+ if ((distanceToVisibleParty <= (AL0446_i_Range = creatureInfo.getAttackRange())) &&
+ (!AL0450_i_DistanceXToParty || !AL0451_i_DistanceYToParty) &&
+ (AL0446_i_Range <= (_vm->getRandomNumber(16) + 1))) {
+ if ((AL0446_i_Range == 1) &&
+ (!getFlag(AL0446_i_CreatureAttributes = creatureInfo._attributes, k0x0008_MaskCreatureInfo_preferBackRow) || !_vm->getRandomNumber(4) || !getFlag(AL0446_i_CreatureAttributes, k0x0010_MaskCreatureInfo_attackAnyChamp)) &&
+ (creatureSize == k0_MaskCreatureSizeQuarter) &&
+ (activeGroup->_cells != k255_CreatureTypeSingleCenteredCreature) &&
+ ((AL0446_i_Cell = getCreatureValue(activeGroup->_cells, AL0447_i_CreatureIndex)) != primaryDirectionToOrFromParty) &&
+ (AL0446_i_Cell != returnNextVal(primaryDirectionToOrFromParty))) { /* If the creature cannot cast spells (range = 1) and is not on a cell where it can attack the party directly and is a quarter square sized creature not in the center of the square then the creature moves to another cell and attack does not occur immediately */
+ if (!creatureCount && _vm->getRandomNumber(2)) {
+ activeGroup->_cells = k255_CreatureTypeSingleCenteredCreature;
+ } else {
+ if ((primaryDirectionToOrFromParty & 0x0001) == (AL0446_i_Cell & 0x0001)) {
+ AL0446_i_Cell--;
+ } else {
+ AL0446_i_Cell++;
+ }
+ if (!getCreatureOrdinalInCell(curGroup, AL0446_i_Cell = normalizeModulo4(AL0446_i_Cell)) ||
+ (_vm->getRandomNumber(2) && !getCreatureOrdinalInCell(curGroup, AL0446_i_Cell = returnOppositeDir((Direction)AL0446_i_Cell)))) { /* If the selected cell (or the opposite cell) is not already occupied by a creature */
+ if (_vm->_projexpl->projectileGetImpactCount(kM1_CreatureElemType, eventMapX, eventMapY, activeGroup->_cells) && (_vm->_projexpl->_creatureDamageOutcome == k2_outcomeKilledAllCreaturesInGroup)) /* BUG0_70 A projectile impact on a creature may be ignored. The function F0218_PROJECTILE_GetImpactCount to detect projectile impacts when a quarter square sized creature moves inside a group (to another cell on the same square) may fail if there are several creatures in the group because the function expects a single cell index for its last parameter. The function should be called once for each cell where there is a creature */
+ return;
+ if (_vm->_projexpl->_creatureDamageOutcome != k1_outcomeKilledSomeCreaturesInGroup) {
+ activeGroup->_cells = getGroupValueUpdatedWithCreatureValue(activeGroup->_cells, AL0447_i_CreatureIndex, AL0446_i_Cell);
+ }
+ }
+ }
+ nextEvent._mapTime += MAX(1, (creatureInfo._movementTicks >> 1) + _vm->getRandomNumber(2)); /* Time for the creature to change cell */
+ nextEvent._type = eventType;
+ goto T0209135;
+ }
+ nextAspectUpdateTime = getCreatureAspectUpdateTime(activeGroup, AL0447_i_CreatureIndex, isCreatureAttacking(curGroup, eventMapX, eventMapY, AL0447_i_CreatureIndex));
+ nextEvent._mapTime += (creatureInfo._animationTicks & 0xF) + _vm->getRandomNumber(2);
+ } else {
+ curGroup->setBehaviour(k7_behavior_APPROACH);
+ if (creatureCount) {
+ stopAttacking(activeGroup, eventMapX, eventMapY);
+ }
+ goto T0209081_RunTowardParty;
+ }
+ } else {
+ /* If the party is visible, update target coordinates */
+ if (groupGetDistanceToVisibleParty(curGroup, kM1_wholeCreatureGroup, eventMapX, eventMapY)) {
+ activeGroup->_targetMapX = _vm->_dungeonMan->_partyMapX;
+ activeGroup->_targetMapY = _vm->_dungeonMan->_partyMapY;
+ setGroupDirection(activeGroup, primaryDirectionToOrFromParty, AL0447_i_CreatureIndex, creatureCount && (creatureSize == k1_MaskCreatureSizeHalf));
+ nextEvent._mapTime += 2;
+ nextAspectUpdateTime = filterTime(nextEvent._mapTime);
+ } else { /* If the party is not visible, move to the target (last known party location) */
+ curGroup->setBehaviour(k7_behavior_APPROACH);
+ if (creatureCount) {
+ stopAttacking(activeGroup, eventMapX, eventMapY);
+ }
+ goto T0209082_WalkTowardTarget;
+ }
+ }
+ }
+ nextEvent._type = eventType;
+ goto T0209136;
+ }
+ nextEvent._mapTime += MAX(1, _vm->getRandomNumber(4) + movementTicks - 1);
+T0209134_SetEvent37:
+ nextEvent._type = k37_TMEventTypeUpdateBehaviourGroup;
+ }
+T0209135:
+ if (!nextAspectUpdateTime) {
+ nextAspectUpdateTime = getCreatureAspectUpdateTime(activeGroup, kM1_wholeCreatureGroup, false);
+ }
+T0209136:
+ if (notUpdateBehaviorFl) {
+ nextEvent._mapTime += ticks;
+ } else {
+ nextAspectUpdateTime += ticks;
+ }
+ addGroupEvent(&nextEvent, nextAspectUpdateTime);
+}
+
+bool GroupMan::isMovementPossible(CreatureInfo *creatureInfo, int16 mapX, int16 mapY, uint16 dir, bool allowMovementOverImaginaryPitsAndFakeWalls) {
+ _groupMovementTestedDirections[dir] = true;
+ _groupMovementBlockedByGroupThing = Thing::_endOfList;
+ _groupMovementBlockedByDoor = false;
+ _groupMovementBlockedByParty = false;
+ if (creatureInfo->_movementTicks == k255_immobile)
+ return false;
+
+ _vm->_dungeonMan->mapCoordsAfterRelMovement((Direction)dir, 1, 0, mapX, mapY);
+ uint16 curSquare = _vm->_dungeonMan->_currMapData[mapX][mapY];
+ int16 curSquareType = Square(curSquare).getType();
+ _groupMovBlockedByWallStairsPitFakeWalFluxCageTeleporter =
+ !(((mapX >= 0) && (mapX < _vm->_dungeonMan->_currMapWidth)) &&
+ ((mapY >= 0) && (mapY < _vm->_dungeonMan->_currMapHeight)) &&
+ (curSquareType != k0_ElementTypeWall) &&
+ (curSquareType != k3_ElementTypeStairs) &&
+ ((curSquareType != k2_ElementTypePit) || (getFlag(curSquare, k0x0001_PitImaginary) && allowMovementOverImaginaryPitsAndFakeWalls) || !getFlag(curSquare, k0x0008_PitOpen) || getFlag(creatureInfo->_attributes, k0x0020_MaskCreatureInfo_levitation)) &&
+ ((curSquareType != k6_ElementTypeFakeWall) || getFlag(curSquare, k0x0004_FakeWallOpen) || (getFlag(curSquare, k0x0001_FakeWallImaginary) && allowMovementOverImaginaryPitsAndFakeWalls)));
+
+ if (_groupMovBlockedByWallStairsPitFakeWalFluxCageTeleporter)
+ return false;
+
+ if (getFlag(creatureInfo->_attributes, k0x2000_MaskCreatureInfo_archenemy)) {
+ Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
+ while (curThing != Thing::_endOfList) {
+ if ((curThing).getType() == k15_ExplosionThingType) {
+ Teleporter *curTeleporter = (Teleporter *)_vm->_dungeonMan->getThingData(curThing);
+ if (((Explosion *)curTeleporter)->setType(k50_ExplosionType_Fluxcage)) {
+ _fluxCages[dir] = true;
+ _fluxCageCount++;
+ _groupMovBlockedByWallStairsPitFakeWalFluxCageTeleporter = true;
+ return false;
+ }
+ }
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+ }
+ if ((curSquareType == k5_ElementTypeTeleporter) && getFlag(curSquare, k0x0008_TeleporterOpen) && (creatureInfo->getWariness() >= 10)) {
+ Teleporter *curTeleporter = (Teleporter *)_vm->_dungeonMan->getSquareFirstThingData(mapX, mapY);
+ if (getFlag(curTeleporter->getScope(), k0x0001_TelepScopeCreatures) && !_vm->_dungeonMan->isCreatureAllowedOnMap(_currGroupThing, curTeleporter->getTargetMapIndex())) {
+ _groupMovBlockedByWallStairsPitFakeWalFluxCageTeleporter = true;
+ return false;
+ }
+ }
+
+ _groupMovementBlockedByParty = (_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (mapX == _vm->_dungeonMan->_partyMapX) && (mapY == _vm->_dungeonMan->_partyMapY);
+ if (_groupMovementBlockedByParty)
+ return false;
+
+ if (curSquareType == k4_DoorElemType) {
+ Teleporter *curTeleporter = (Teleporter *)_vm->_dungeonMan->getSquareFirstThingData(mapX, mapY);
+ if (((Square(curSquare).getDoorState()) > (((Door *)curTeleporter)->opensVertically() ? CreatureInfo::getHeight(creatureInfo->_attributes) : 1)) && ((Square(curSquare).getDoorState()) != k5_doorState_DESTROYED) && !getFlag(creatureInfo->_attributes, k0x0040_MaskCreatureInfo_nonMaterial)) {
+ _groupMovementBlockedByDoor = true;
+ return false;
+ }
+ }
+
+ _groupMovementBlockedByGroupThing = groupGetThing(mapX, mapY);
+ return (_groupMovementBlockedByGroupThing == Thing::_endOfList);
+}
+
+int16 GroupMan::getDistanceBetweenSquares(int16 srcMapX, int16 srcMapY, int16 destMapX, int16 destMapY) {
+ return ABS(srcMapX - destMapX) + ABS(srcMapY - destMapY);
+}
+
+int16 GroupMan::groupGetDistanceToVisibleParty(Group *group, int16 creatureIndex, int16 mapX, int16 mapY) {
+ uint16 groupDirections;
+ CreatureInfo *groupCreatureInfo = &_vm->_dungeonMan->_creatureInfos[group->_type];
+ if (_vm->_championMan->_party._event71Count_Invisibility && !getFlag(groupCreatureInfo->_attributes, k0x0800_MaskCreatureInfo_seeInvisible))
+ return 0;
+
+ bool alwaysSee = false;
+ int16 checkDirectionsCount; /* Count of directions to test in L0425_ai_CreatureViewDirections */
+ int16 creatureViewDirections[4]; /* List of directions to test */
+ if (getFlag(groupCreatureInfo->_attributes, k0x0004_MaskCreatureInfo_sideAttack)) { /* If creature can see in all directions */
+ alwaysSee = true;
+ checkDirectionsCount = 1;
+ creatureViewDirections[0] = kDirNorth;
+ } else {
+ groupDirections = _activeGroups[group->getActiveGroupIndex()]._directions;
+ if (creatureIndex < 0) { /* Negative index means test if each creature in the group can see the party in their respective direction */
+ checkDirectionsCount = 0;
+ for (creatureIndex = group->getCount(); creatureIndex >= 0; creatureIndex--) {
+ int16 creatureDirection = normalizeModulo4(groupDirections >> (creatureIndex << 1));
+ int16 counter = checkDirectionsCount;
+ bool skipSet = false;
+ while (counter--) {
+ if (creatureViewDirections[counter] == creatureDirection) { /* If the creature looks in the same direction as another one in the group */
+ skipSet = true;
+ break;
+ }
+ }
+ if (!skipSet)
+ creatureViewDirections[checkDirectionsCount++] = creatureDirection;
+ }
+ } else { /* Positive index means test only if the specified creature in the group can see the party in its direction */
+ creatureViewDirections[0] = getCreatureValue(groupDirections, creatureIndex);
+ checkDirectionsCount = 1;
+ }
+ }
+
+ while (checkDirectionsCount--) {
+ if (alwaysSee || isDestVisibleFromSource(creatureViewDirections[checkDirectionsCount], mapX, mapY, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY)) {
+ int16 sightRange = groupCreatureInfo->getSightRange();
+ if (!getFlag(groupCreatureInfo->_attributes, k0x1000_MaskCreatureInfo_nightVision))
+ sightRange -= _vm->_displayMan->_dungeonViewPaletteIndex >> 1;
+
+ if (_currGroupDistanceToParty > MAX<int16>(1, sightRange))
+ return 0;
+
+ return getDistanceBetweenUnblockedSquares(mapX, mapY, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, &GroupMan::isViewPartyBlocked);
+ }
+ }
+ return 0;
+}
+
+int16 GroupMan::getDistanceBetweenUnblockedSquares(int16 srcMapX, int16 srcMapY,
+ int16 destMapX, int16 destMapY, bool (GroupMan::*isBlocked)(uint16, uint16)) {
+
+ if (getDistance(srcMapX, srcMapY, destMapX, destMapY) <= 1) {
+ return 1;
+ }
+
+ int16 distanceX = ABS(destMapX - srcMapX);
+ int16 distanceY = ABS(destMapY - srcMapY);
+ bool isDistanceXSmallerThanDistanceY = (distanceX < distanceY);
+ bool isDistanceXEqualsDistanceY = (distanceX == distanceY);
+ int16 pathMapX = destMapX;
+ int16 pathMapY = destMapY;
+ int16 axisStepX = ((pathMapX - srcMapX) > 0) ? -1 : 1;
+ int16 axisStepY = ((pathMapY - srcMapY) > 0) ? -1 : 1;
+ int16 largestAxisDistance;
+
+ int16 valueA;
+ int16 valueB;
+ int16 valueC;
+
+ if (isDistanceXSmallerThanDistanceY) {
+ largestAxisDistance = pathMapY - srcMapY;
+ valueC = (largestAxisDistance ? ((pathMapX - srcMapX) << 6) / largestAxisDistance : 128);
+ } else {
+ largestAxisDistance = pathMapX - srcMapX;
+ valueC = (largestAxisDistance ? ((pathMapY - srcMapY) << 6) / largestAxisDistance : 128);
+ }
+
+ /* 128 when the creature is on the same row or column as the party */
+ do {
+ if (isDistanceXEqualsDistanceY) {
+ if (( (CALL_MEMBER_FN(*_vm->_groupMan, isBlocked))(pathMapX + axisStepX, pathMapY)
+ && (CALL_MEMBER_FN(*_vm->_groupMan, isBlocked))(pathMapX, pathMapY + axisStepY))
+ || (CALL_MEMBER_FN(*_vm->_groupMan, isBlocked))(pathMapX = pathMapX + axisStepX, pathMapY = pathMapY + axisStepY))
+ return 0;
+ } else {
+ if (isDistanceXSmallerThanDistanceY) {
+ valueA = ABS(((pathMapY - srcMapY) ? ((pathMapX + axisStepX - srcMapX) << 6) / largestAxisDistance : 128) - valueC);
+ valueB = ABS(((pathMapY + axisStepY - srcMapY) ? ((pathMapX - srcMapX) << 6) / largestAxisDistance : 128) - valueC);
+ } else {
+ valueA = ABS(((pathMapX + axisStepX - srcMapX) ? ((pathMapY - srcMapY) << 6) / largestAxisDistance : 128) - valueC);
+ valueB = ABS(((pathMapX - srcMapX) ? ((pathMapY + axisStepY - srcMapY) << 6) / largestAxisDistance : 128) - valueC);
+ }
+
+ if (valueA < valueB)
+ pathMapX += axisStepX;
+ else
+ pathMapY += axisStepY;
+
+ if ((CALL_MEMBER_FN(*_vm->_groupMan, isBlocked))(pathMapX, pathMapY)) {
+ pathMapX += axisStepX;
+ pathMapY -= axisStepY;
+ if (((valueA != valueB) || (CALL_MEMBER_FN(*_vm->_groupMan, isBlocked))(pathMapX, pathMapY)))
+ return 0;
+ }
+ }
+ } while (getDistance(pathMapX, pathMapY, srcMapX, srcMapY) > 1);
+ return getDistanceBetweenSquares(srcMapX, srcMapY, destMapX, destMapY);
+}
+
+bool GroupMan::isViewPartyBlocked(uint16 mapX, uint16 mapY) {
+ uint16 curSquare = _vm->_dungeonMan->_currMapData[mapX][mapY];
+ int16 curSquareType = Square(curSquare).getType();
+ if (curSquareType == k4_DoorElemType) {
+ Door *curDoor = (Door *)_vm->_dungeonMan->getSquareFirstThingData(mapX, mapY);
+ int16 curDoorState = Square(curSquare).getDoorState();
+ return ((curDoorState == k3_doorState_FOURTH) || (curDoorState == k4_doorState_CLOSED)) && !getFlag(_vm->_dungeonMan->_currMapDoorInfo[curDoor->getType()]._attributes, k0x0001_MaskDoorInfo_CraturesCanSeeThrough);
+ }
+ return (curSquareType == k0_ElementTypeWall) || ((curSquareType == k6_ElementTypeFakeWall) && !getFlag(curSquare, k0x0004_FakeWallOpen));
+}
+
+int32 GroupMan::getCreatureAspectUpdateTime(ActiveGroup *activeGroup, int16 creatureIndex, bool isAttacking) {
+ Group *group = &(((Group *)_vm->_dungeonMan->_thingData[k4_GroupThingType])[activeGroup->_groupThingIndex]);
+ uint16 creatureType = group->_type;
+ uint16 creatureGraphicInfo = _vm->_dungeonMan->_creatureInfos[creatureType]._graphicInfo;
+ bool processGroup = (creatureIndex < 0);
+ if (processGroup) /* If the creature index is negative then all creatures in the group are processed */
+ creatureIndex = group->getCount();
+
+ do {
+ uint16 aspect = activeGroup->_aspect[creatureIndex];
+ aspect &= k0x0080_MaskActiveGroupIsAttacking | k0x0040_MaskActiveGroupFlipBitmap;
+ int16 offset = ((creatureGraphicInfo >> 12) & 0x3);
+ if (offset) {
+ offset = _vm->getRandomNumber(offset);
+ if (_vm->getRandomNumber(2))
+ offset = (-offset) & 0x0007;
+
+ aspect |= offset;
+ }
+
+ offset = ((creatureGraphicInfo >> 14) & 0x3);
+ if (offset) {
+ offset = _vm->getRandomNumber(offset);
+ if (_vm->getRandomNumber(2))
+ offset = (-offset) & 0x0007;
+
+ aspect |= (offset << 3);
+ }
+ if (isAttacking) {
+ if (getFlag(creatureGraphicInfo, k0x0200_CreatureInfoGraphicMaskFlipAttack)) {
+ if (getFlag(aspect, k0x0080_MaskActiveGroupIsAttacking) && (creatureType == k18_CreatureTypeAnimatedArmourDethKnight)) {
+ if (_vm->getRandomNumber(2)) {
+ toggleFlag(aspect, k0x0040_MaskActiveGroupFlipBitmap);
+ _vm->_sound->requestPlay(k16_soundCOMBAT_ATTACK_SKELETON_ANIMATED_ARMOUR_DETH_KNIGHT, _currentGroupMapX, _currentGroupMapY, k1_soundModePlayIfPrioritized);
+ }
+ } else if (!getFlag(aspect, k0x0080_MaskActiveGroupIsAttacking) || !getFlag(creatureGraphicInfo, k0x0400_CreatureInfoGraphicMaskFlipDuringAttack)) {
+ if (_vm->getRandomNumber(2))
+ setFlag(aspect, k0x0040_MaskActiveGroupFlipBitmap);
+ else
+ clearFlag(aspect, k0x0040_MaskActiveGroupFlipBitmap);
+ }
+ } else
+ clearFlag(aspect, k0x0040_MaskActiveGroupFlipBitmap);
+
+ setFlag(aspect, k0x0080_MaskActiveGroupIsAttacking);
+ } else {
+ if (getFlag(creatureGraphicInfo, k0x0004_CreatureInfoGraphicMaskFlipNonAttack)) {
+ if (creatureType == k13_CreatureTypeCouatl) {
+ if (_vm->getRandomNumber(2)) {
+ toggleFlag(aspect, k0x0040_MaskActiveGroupFlipBitmap);
+ uint16 soundIndex = _vm->_moveSens->getSound(k13_CreatureTypeCouatl);
+ if (soundIndex <= k34_D13_soundCount)
+ _vm->_sound->requestPlay(soundIndex, _currentGroupMapX, _currentGroupMapY, k1_soundModePlayIfPrioritized);
+ }
+ } else if (_vm->getRandomNumber(2))
+ setFlag(aspect, k0x0040_MaskActiveGroupFlipBitmap);
+ else
+ clearFlag(aspect, k0x0040_MaskActiveGroupFlipBitmap);
+ } else
+ clearFlag(aspect, k0x0040_MaskActiveGroupFlipBitmap);
+
+ clearFlag(aspect, k0x0080_MaskActiveGroupIsAttacking);
+ }
+ activeGroup->_aspect[creatureIndex] = aspect;
+ } while (processGroup && (creatureIndex--));
+ uint16 animationTicks = _vm->_dungeonMan->_creatureInfos[group->_type]._animationTicks;
+ return _vm->_gameTime + (isAttacking ? ((animationTicks >> 8) & 0xF) : ((animationTicks >> 4) & 0xF)) + _vm->getRandomNumber(2);
+}
+
+void GroupMan::setGroupDirection(ActiveGroup *activeGroup, int16 dir, int16 creatureIndex, bool twoHalfSquareSizedCreatures) {
+ static ActiveGroup *G0396_ps_TwoHalfSquareSizedCreaturesGroupLastDirectionSetActiveGroup;
+
+ if (twoHalfSquareSizedCreatures
+ && (_vm->_gameTime == twoHalfSquareSizedCreaturesGroupLastDirectionSetTime)
+ && (activeGroup == G0396_ps_TwoHalfSquareSizedCreaturesGroupLastDirectionSetActiveGroup))
+ return;
+
+ uint16 groupDirections = activeGroup->_directions;
+ if (normalizeModulo4(getCreatureValue(groupDirections, creatureIndex) - dir) == 2) { /* If current and new direction are opposites then change direction only one step at a time */
+ dir = returnNextVal((_vm->getRandomNumber(65536) & 0x0002) + dir);
+ groupDirections = getGroupValueUpdatedWithCreatureValue(groupDirections, creatureIndex, dir);
+ } else
+ groupDirections = getGroupValueUpdatedWithCreatureValue(groupDirections, creatureIndex, dir);
+
+ if (twoHalfSquareSizedCreatures) {
+ groupDirections = getGroupValueUpdatedWithCreatureValue(groupDirections, creatureIndex ^ 1, dir); /* Set direction of the second half square sized creature */
+ twoHalfSquareSizedCreaturesGroupLastDirectionSetTime = _vm->_gameTime;
+ G0396_ps_TwoHalfSquareSizedCreaturesGroupLastDirectionSetActiveGroup = activeGroup;
+ }
+
+ activeGroup->_directions = (Direction)groupDirections;
+}
+
+void GroupMan::addGroupEvent(TimelineEvent *event, uint32 time) {
+ warning("potentially dangerous cast to uint32 below");
+ if (time < (uint32)filterTime(event->_mapTime)) {
+ event->_type -= 5;
+ event->_C._ticks = filterTime(event->_mapTime) - time;
+ M32_setTime(event->_mapTime, time);
+ } else {
+ event->_C._ticks = time - filterTime(event->_mapTime);
+ }
+ _vm->_timeline->addEventGetEventIndex(event);
+}
+
+int16 GroupMan::getSmelledPartyPrimaryDirOrdinal(CreatureInfo *creatureInfo, int16 mapY, int16 mapX) {
+ uint16 smellRange = creatureInfo->getSmellRange();
+ if (!smellRange)
+ return 0;
+
+ if ((((smellRange + 1) >> 1) >= _currGroupDistanceToParty) && getDistanceBetweenUnblockedSquares(mapY, mapX, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, &GroupMan::isSmellPartyBlocked)) {
+ _vm->_projexpl->_secondaryDirToOrFromParty = _currGroupSecondaryDirToParty;
+ return _vm->indexToOrdinal(_currGroupPrimaryDirToParty);
+ }
+
+ int16 scentOrdinal = _vm->_championMan->getScentOrdinal(mapY, mapX);
+ if (scentOrdinal && ((_vm->_championMan->_party._scentStrengths[_vm->ordinalToIndex(scentOrdinal)] + _vm->getRandomNumber(4)) > (30 - (smellRange << 1)))) { /* If there is a fresh enough party scent on the group square */
+ return _vm->indexToOrdinal(getDirsWhereDestIsVisibleFromSource(mapY, mapX, _vm->_championMan->_party._scents[scentOrdinal].getMapX(), _vm->_championMan->_party._scents[scentOrdinal].getMapY()));
+ }
+ return 0;
+}
+
+bool GroupMan::isSmellPartyBlocked(uint16 mapX, uint16 mapY) {
+ uint16 square = _vm->_dungeonMan->_currMapData[mapX][mapY];
+ int16 squareType = Square(square).getType();
+
+ return ( (squareType) == k0_ElementTypeWall) || ((squareType == k6_ElementTypeFakeWall)
+ && !getFlag(square, k0x0004_FakeWallOpen));
+}
+
+int16 GroupMan::getFirstPossibleMovementDirOrdinal(CreatureInfo *info, int16 mapX, int16 mapY, bool allowMovementOverImaginaryPitsAndFakeWalls) {
+ for (int16 direction = kDirNorth; direction <= kDirWest; direction++) {
+ if ((!_groupMovementTestedDirections[direction]) && isMovementPossible(info, mapX, mapY, direction, allowMovementOverImaginaryPitsAndFakeWalls)) {
+ return _vm->indexToOrdinal(direction);
+ }
+ }
+ return 0;
+}
+
+void GroupMan::setDirGroup(ActiveGroup *activeGroup, int16 dir, int16 creatureIndex, int16 creatureSize) {
+ bool twoHalfSquareSizedCreatures = creatureIndex && (creatureSize == k1_MaskCreatureSizeHalf);
+
+ if (twoHalfSquareSizedCreatures)
+ creatureIndex--;
+
+ do {
+ if (!creatureIndex || _vm->getRandomNumber(2))
+ setGroupDirection(activeGroup, dir, creatureIndex, twoHalfSquareSizedCreatures);
+ } while (creatureIndex--);
+}
+
+void GroupMan::stopAttacking(ActiveGroup *group, int16 mapX, int16 mapY) {
+ for (int16 creatureIndex = 0; creatureIndex < 4; creatureIndex++)
+ clearFlag(group->_aspect[creatureIndex++], k0x0080_MaskActiveGroupIsAttacking);
+
+ groupDeleteEvents(mapX, mapY);
+}
+
+bool GroupMan::isArchenemyDoubleMovementPossible(CreatureInfo *info, int16 mapX, int16 mapY, uint16 dir) {
+ if (_fluxCages[dir])
+ return false;
+
+ mapX += _vm->_dirIntoStepCountEast[dir], mapY += _vm->_dirIntoStepCountNorth[dir];
+ return isMovementPossible(info, mapX, mapY, dir, false);
+}
+
+bool GroupMan::isCreatureAttacking(Group *group, int16 mapX, int16 mapY, uint16 creatureIndex) {
+ static const uint8 creatureAttackSounds[11] = { 3, 7, 14, 15, 19, 21, 29, 30, 31, 4, 16 }; /* Atari ST: { 3, 7, 14, 15, 19, 21, 4, 16 } */
+
+ _vm->_projexpl->_lastCreatureAttackTime = _vm->_gameTime;
+ ActiveGroup activeGroup = _activeGroups[group->getActiveGroupIndex()];
+ uint16 creatureType = group->_type;
+ CreatureInfo *creatureInfo = &_vm->_dungeonMan->_creatureInfos[creatureType];
+ uint16 primaryDirectionToParty = _currGroupPrimaryDirToParty;
+
+ int16 targetCell;
+ byte groupCells = activeGroup._cells;
+ if (groupCells == k255_CreatureTypeSingleCenteredCreature)
+ targetCell = _vm->getRandomNumber(2);
+ else
+ targetCell = ((getCreatureValue(groupCells, creatureIndex) + 5 - primaryDirectionToParty) & 0x0002) >> 1;
+
+ targetCell += primaryDirectionToParty;
+ targetCell &= 0x0003;
+ if ((creatureInfo->getAttackRange() > 1) && ((_currGroupDistanceToParty > 1) || _vm->getRandomNumber(2))) {
+ Thing projectileThing = Thing::_none;
+
+ switch (creatureType) {
+ case k14_CreatureTypeVexirk:
+ case k23_CreatureTypeLordChaos:
+ if (_vm->getRandomNumber(2)) {
+ projectileThing = Thing::_explFireBall;
+ } else {
+ switch (_vm->getRandomNumber(4)) {
+ case 0:
+ projectileThing = Thing::_explHarmNonMaterial;
+ break;
+ case 1:
+ projectileThing = Thing::_explLightningBolt;
+ break;
+ case 2:
+ projectileThing = Thing::_explPoisonCloud;
+ break;
+ case 3:
+ projectileThing = Thing::_explOpenDoor;
+ }
+ }
+ break;
+ case k1_CreatureTypeSwampSlimeSlime:
+ projectileThing = Thing::_explSlime;
+ break;
+ case k3_CreatureTypeWizardEyeFlyingEye:
+ if (_vm->getRandomNumber(8)) {
+ projectileThing = Thing::_explLightningBolt;
+ } else {
+ projectileThing = Thing::_explOpenDoor;
+ }
+ break;
+ case k19_CreatureTypeMaterializerZytaz:
+ if (_vm->getRandomNumber(2)) {
+ projectileThing = Thing::_explPoisonCloud;
+ break;
+ }
+ case k22_CreatureTypeDemon:
+ case k24_CreatureTypeRedDragon:
+ projectileThing = Thing::_explFireBall;
+ } /* BUG0_13 The game may crash when 'Lord Order' or 'Grey Lord' cast spells. This cannot happen with the original dungeons as they do not contain any groups of these types. 'Lord Order' and 'Grey Lord' creatures can cast spells (attack range > 1) but no projectile type is defined for them in the code. If these creatures are present in a dungeon they will cast projectiles containing undefined things because the variable is not initialized */
+ int16 kineticEnergy = (creatureInfo->_attack >> 2) + 1;
+ kineticEnergy += _vm->getRandomNumber(kineticEnergy);
+ kineticEnergy += _vm->getRandomNumber(kineticEnergy);
+ _vm->_sound->requestPlay(k13_soundSPELL, mapX, mapY, k0_soundModePlayImmediately);
+ _vm->_projexpl->createProjectile(projectileThing, mapX, mapY, targetCell, (Direction)_currGroupPrimaryDirToParty, getBoundedValue((int16)20, kineticEnergy, (int16)255), creatureInfo->_dexterity, 8);
+ } else {
+ int16 championIndex;
+ if (getFlag(creatureInfo->_attributes, k0x0010_MaskCreatureInfo_attackAnyChamp)) {
+ championIndex = _vm->getRandomNumber(4);
+ int cpt;
+ for (cpt = 0; (cpt < 4) && !_vm->_championMan->_champions[championIndex]._currHealth; cpt++)
+ championIndex = returnNextVal(championIndex);
+
+ if (cpt == 4)
+ return false;
+ } else {
+ championIndex = _vm->_championMan->getTargetChampionIndex(mapX, mapY, targetCell);
+ if (championIndex < 0)
+ return false;
+ }
+
+ if (creatureType == k2_CreatureTypeGiggler)
+ stealFromChampion(group, championIndex);
+ else {
+ int16 damage = getChampionDamage(group, championIndex) + 1;
+ Champion *damagedChampion = &_vm->_championMan->_champions[championIndex];
+ if (damage > damagedChampion->_maximumDamageReceived) {
+ damagedChampion->_maximumDamageReceived = damage;
+ damagedChampion->_directionMaximumDamageReceived = returnOppositeDir((Direction)primaryDirectionToParty);
+ }
+ }
+ }
+ int16 attackSoundOrdinal = creatureInfo->_attackSoundOrdinal;
+ if (attackSoundOrdinal)
+ _vm->_sound->requestPlay(creatureAttackSounds[--attackSoundOrdinal], mapX, mapY, k1_soundModePlayIfPrioritized);
+
+ return true;
+}
+
+void GroupMan::setOrderedCellsToAttack(signed char *orderedCellsToAttack, int16 targetMapX, int16 targetMapY, int16 attackerMapX, int16 attackerMapY, uint16 cellSource) {
+ static signed char attackOrder[8][4] = { // @ G0023_aac_Graphic562_OrderedCellsToAttack
+ {0, 1, 3, 2}, /* Attack South from position Northwest or Southwest */
+ {1, 0, 2, 3}, /* Attack South from position Northeast or Southeast */
+ {1, 2, 0, 3}, /* Attack West from position Northwest or Northeast */
+ {2, 1, 3, 0}, /* Attack West from position Southeast or Southwest */
+ {3, 2, 0, 1}, /* Attack North from position Northwest or Southwest */
+ {2, 3, 1, 0}, /* Attack North from position Southeast or Northeast */
+ {0, 3, 1, 2}, /* Attack East from position Northwest or Northeast */
+ {3, 0, 2, 1} /* Attack East from position Southeast or Southwest */
+ };
+
+ uint16 orderedCellsToAttackIndex = getDirsWhereDestIsVisibleFromSource(targetMapX, targetMapY, attackerMapX, attackerMapY) << 1;
+ if (!(orderedCellsToAttackIndex & 0x0002))
+ cellSource++;
+
+ orderedCellsToAttackIndex += (cellSource >> 1) & 0x0001;
+ for (uint16 i = 0; i < 4; ++i)
+ orderedCellsToAttack[i] = attackOrder[orderedCellsToAttackIndex][i];
+}
+
+void GroupMan::stealFromChampion(Group *group, uint16 championIndex) {
+ static unsigned char G0394_auc_StealFromSlotIndices[8]; /* Initialized with 0 bytes by C loader */
+
+ bool objectStolen = false;
+ Champion *champion = &_vm->_championMan->_champions[championIndex];
+ int16 percentage = 100 - _vm->_championMan->getDexterity(champion);
+ uint16 slotIdx = _vm->getRandomNumber(8);
+ while ((percentage > 0) && !_vm->_championMan->isLucky(champion, percentage)) {
+ uint16 stealFromSlotIndex = G0394_auc_StealFromSlotIndices[slotIdx];
+ if (stealFromSlotIndex == kDMSlotBackpackLine1_1)
+ stealFromSlotIndex += _vm->getRandomNumber(17); /* Select a random slot in the backpack */
+
+ Thing slotThing = champion->_slots[stealFromSlotIndex];
+ if ((slotThing != Thing::_none)) {
+ objectStolen = true;
+ slotThing = _vm->_championMan->getObjectRemovedFromSlot(championIndex, stealFromSlotIndex);
+ if (group->_slot == Thing::_endOfList) {
+ group->_slot = slotThing;
+ /* BUG0_12 An object is cloned and appears at two different locations in the dungeon and/or inventory. The game may crash when interacting with this object. If a Giggler with no possessions steals an object that was previously in a chest and was not the last object in the chest then the objects that followed it are cloned. In the chest, the object is part of a linked list of objects that is not reset when the object is removed from the chest and placed in the inventory (but not in the dungeon), nor when it is stolen and added as the first Giggler possession. If the Giggler already has a possession before stealing the object then this does not create a cloned object.
+ The following statement is missing: L0394_T_Thing->Next = Thing::_endOfList;
+ This creates cloned things if L0394_T_Thing->Next is not Thing::_endOfList which is the case when the object comes from a chest in which it was not the last object */
+ } else {
+ _vm->_dungeonMan->linkThingToList(slotThing, group->_slot, kM1_MapXNotOnASquare, 0);
+ }
+ _vm->_championMan->drawChampionState((ChampionIndex)championIndex);
+ }
+ ++slotIdx;
+ slotIdx &= 0x0007;
+ percentage -= 20;
+ }
+ if (!_vm->getRandomNumber(8) || (objectStolen && _vm->getRandomNumber(2))) {
+ _activeGroups[group->getActiveGroupIndex()]._delayFleeingFromTarget = _vm->getRandomNumber(64) + 20;
+ group->setBehaviour(k5_behavior_FLEE);
+ }
+}
+
+int16 GroupMan::getChampionDamage(Group *group, uint16 champIndex) {
+ unsigned char allowedWoundMasks[4] = {32, 16, 8, 4}; // @ G0024_auc_Graphic562_WoundProbabilityIndexToWoundMask
+
+ Champion *curChampion = &_vm->_championMan->_champions[champIndex];
+ if (champIndex >= _vm->_championMan->_partyChampionCount)
+ return 0;
+
+ if (!curChampion->_currHealth)
+ return 0;
+
+ if (_vm->_championMan->_partyIsSleeping)
+ _vm->_championMan->wakeUp();
+
+ int16 doubledMapDifficulty = _vm->_dungeonMan->_currMap->_difficulty << 1;
+ CreatureInfo creatureInfo = _vm->_dungeonMan->_creatureInfos[group->_type];
+ _vm->_championMan->addSkillExperience(champIndex, kDMSkillParry, creatureInfo.getExperience());
+ if (_vm->_championMan->_partyIsSleeping || (((_vm->_championMan->getDexterity(curChampion) < (_vm->getRandomNumber(32) + creatureInfo._dexterity + doubledMapDifficulty - 16)) || !_vm->getRandomNumber(4)) && !_vm->_championMan->isLucky(curChampion, 60))) {
+ uint16 allowedWound;
+ uint16 woundTest = _vm->getRandomNumber(65536);
+ if (woundTest & 0x0070) {
+ woundTest &= 0x000F;
+ uint16 woundProbabilities = creatureInfo._woundProbabilities;
+ uint16 woundProbabilityIndex;
+ for (woundProbabilityIndex = 0; woundTest > (woundProbabilities & 0x000F); woundProbabilityIndex++) {
+ woundProbabilities >>= 4;
+ }
+ allowedWound = allowedWoundMasks[woundProbabilityIndex];
+ } else
+ allowedWound = woundTest & 0x0001; /* 0 (Ready hand) or 1 (action hand) */
+
+ int16 attack = (_vm->getRandomNumber(16) + creatureInfo._attack + doubledMapDifficulty) - (_vm->_championMan->getSkillLevel(champIndex, kDMSkillParry) << 1);
+ if (attack <= 1) {
+ if (_vm->getRandomNumber(2))
+ return 0;
+
+ attack = _vm->getRandomNumber(4) + 2;
+ }
+ attack >>= 1;
+ attack += _vm->getRandomNumber(attack) + _vm->getRandomNumber(4);
+ attack += _vm->getRandomNumber(attack);
+ attack >>= 2;
+ attack += _vm->getRandomNumber(4) + 1;
+ if (_vm->getRandomNumber(2))
+ attack -= _vm->getRandomNumber((attack >> 1) + 1) - 1;
+
+ int16 damage = _vm->_championMan->addPendingDamageAndWounds_getDamage(champIndex, attack, allowedWound, creatureInfo._attackType);
+ if (damage) {
+ _vm->_sound->requestPlay(k09_soundCHAMPION_0_DAMAGED + champIndex, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, k2_soundModePlayOneTickLater);
+
+ uint16 poisonAttack = creatureInfo._poisonAttack;
+ if (poisonAttack && _vm->getRandomNumber(2)) {
+ poisonAttack = _vm->_championMan->getStatisticAdjustedAttack(curChampion, kDMStatVitality, poisonAttack);
+ if (poisonAttack >= 0)
+ _vm->_championMan->championPoison(champIndex, poisonAttack);
+ }
+ return damage;
+ }
+ }
+
+ return 0;
+}
+
+void GroupMan::dropMovingCreatureFixedPossession(Thing thing, int16 mapX, int16 mapY) {
+ if (_dropMovingCreatureFixedPossCellCount) {
+ Group *group = (Group *)_vm->_dungeonMan->getThingData(thing);
+ int16 creatureType = group->_type;
+ while (_dropMovingCreatureFixedPossCellCount) {
+ dropCreatureFixedPossessions(creatureType, mapX, mapY, _dropMovingCreatureFixedPossessionsCell[--_dropMovingCreatureFixedPossCellCount], k2_soundModePlayOneTickLater);
+ }
+ }
+}
+
+void GroupMan::startWandering(int16 mapX, int16 mapY) {
+ Group *L0332_ps_Group = (Group *)_vm->_dungeonMan->getThingData(groupGetThing(mapX, mapY));
+ if (L0332_ps_Group->getBehaviour() >= k4_behavior_USELESS) {
+ L0332_ps_Group->setBehaviour(k0_behavior_WANDER);
+ }
+ TimelineEvent nextEvent;
+ setMapAndTime(nextEvent._mapTime, _vm->_dungeonMan->_currMapIndex, (_vm->_gameTime + 1));
+ nextEvent._type = k37_TMEventTypeUpdateBehaviourGroup;
+ nextEvent._priority = 255 - _vm->_dungeonMan->_creatureInfos[L0332_ps_Group->_type]._movementTicks; /* The fastest creatures (with small MovementTicks value) get higher event priority */
+ nextEvent._C._ticks = 0;
+ nextEvent._B._location._mapX = mapX;
+ nextEvent._B._location._mapY = mapY;
+ _vm->_timeline->addEventGetEventIndex(&nextEvent);
+}
+
+void GroupMan::addActiveGroup(Thing thing, int16 mapX, int16 mapY) {
+ ActiveGroup *activeGroup = _activeGroups;
+ int16 activeGroupIndex = 0;
+ while (activeGroup->_groupThingIndex >= 0) {
+ if (++activeGroupIndex >= _maxActiveGroupCount)
+ return;
+
+ activeGroup++;
+ }
+ _currActiveGroupCount++;
+
+ activeGroup->_groupThingIndex = (thing).getIndex();
+ Group *curGroup = (Group *)(_vm->_dungeonMan->_thingData[k4_GroupThingType] +
+ _vm->_dungeonMan->_thingDataWordCount[k4_GroupThingType] * activeGroup->_groupThingIndex);
+
+ activeGroup->_cells = curGroup->_cells;
+ curGroup->getActiveGroupIndex() = activeGroupIndex;
+ activeGroup->_priorMapX = activeGroup->_homeMapX = mapX;
+ activeGroup->_priorMapY = activeGroup->_homeMapY = mapY;
+ activeGroup->_lastMoveTime = _vm->_gameTime - 127;
+ uint16 creatureIndex = curGroup->getCount();
+ do {
+ activeGroup->_directions = (Direction)getGroupValueUpdatedWithCreatureValue(activeGroup->_directions, creatureIndex, curGroup->getDir());
+ activeGroup->_aspect[creatureIndex] = 0;
+ } while (creatureIndex--);
+ getCreatureAspectUpdateTime(activeGroup, kM1_wholeCreatureGroup, false);
+}
+
+void GroupMan::removeActiveGroup(uint16 activeGroupIndex) {
+ if ((activeGroupIndex > _maxActiveGroupCount) || (_activeGroups[activeGroupIndex]._groupThingIndex < 0))
+ return;
+
+ ActiveGroup *activeGroup = &_activeGroups[activeGroupIndex];
+ Group *group = &((Group *)_vm->_dungeonMan->_thingData[k4_GroupThingType])[activeGroup->_groupThingIndex];
+ _currActiveGroupCount--;
+ group->_cells = activeGroup->_cells;
+ group->setDir(normalizeModulo4(activeGroup->_directions));
+ if (group->getBehaviour() >= k4_behavior_USELESS) {
+ group->setBehaviour(k0_behavior_WANDER);
+ }
+ activeGroup->_groupThingIndex = -1;
+}
+
+void GroupMan::removeAllActiveGroups() {
+ for (int16 idx = 0; _currActiveGroupCount > 0; idx++) {
+ if (_activeGroups[idx]._groupThingIndex >= 0) {
+ removeActiveGroup(idx);
+ }
+ }
+}
+
+void GroupMan::addAllActiveGroups() {
+ byte *curSquare = _vm->_dungeonMan->_currMapData[0];
+ Thing *squareCurThing = &_vm->_dungeonMan->_squareFirstThings[_vm->_dungeonMan->_currMapColCumulativeSquareFirstThingCount[0]];
+ for (uint16 mapX = 0; mapX < _vm->_dungeonMan->_currMapWidth; mapX++) {
+ for (uint16 mapY = 0; mapY < _vm->_dungeonMan->_currMapHeight; mapY++) {
+ if (getFlag(*curSquare++, k0x0010_ThingListPresent)) {
+ Thing curThing = *squareCurThing++;
+ do {
+ if (curThing.getType() == k4_GroupThingType) {
+ groupDeleteEvents(mapX, mapY);
+ addActiveGroup(curThing, mapX, mapY);
+ startWandering(mapX, mapY);
+ break;
+ }
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ } while (curThing != Thing::_endOfList);
+ }
+ }
+ }
+}
+
+Thing GroupMan::groupGetGenerated(int16 creatureType, int16 healthMultiplier, uint16 creatureCount, Direction dir, int16 mapX, int16 mapY) {
+ Thing groupThing = _vm->_dungeonMan->getUnusedThing(k4_GroupThingType);
+ if (((_currActiveGroupCount >= (_maxActiveGroupCount - 5)) && (_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex))
+ || (groupThing == Thing::_none)) {
+ return Thing::_none;
+ }
+ Group *group = (Group *)_vm->_dungeonMan->getThingData(groupThing);
+ group->_slot = Thing::_endOfList;
+ group->setDoNotDiscard(false);
+ group->setDir(dir);
+ group->setCount(creatureCount);
+ bool severalCreaturesInGroup = creatureCount;
+ uint16 cell = 0;
+ uint16 groupCells = 0;
+ if (severalCreaturesInGroup)
+ cell = _vm->getRandomNumber(4);
+ else
+ groupCells = k255_CreatureTypeSingleCenteredCreature;
+
+ CreatureInfo *creatureInfo = &_vm->_dungeonMan->_creatureInfos[group->_type = creatureType];
+ uint16 baseHealth = creatureInfo->_baseHealth;
+ do {
+ group->_health[creatureCount] = (baseHealth * healthMultiplier) + _vm->getRandomNumber((baseHealth >> 2) + 1);
+ if (severalCreaturesInGroup) {
+ groupCells = getGroupValueUpdatedWithCreatureValue(groupCells, creatureCount, cell++);
+ if (getFlag(creatureInfo->_attributes, k0x0003_MaskCreatureInfo_size) == k1_MaskCreatureSizeHalf)
+ cell++;
+
+ cell &= 0x0003;
+ }
+ } while (creatureCount--);
+ group->_cells = groupCells;
+ if (_vm->_moveSens->getMoveResult(groupThing, kM1_MapXNotOnASquare, 0, mapX, mapY)) {
+ /* If F0267_MOVE_GetMoveResult_CPSCE returns true then the group was either killed by a projectile
+ impact (in which case the thing data was marked as unused) or the party is on the destination
+ square and an event is created to move the creature into the dungeon later
+ (in which case the thing is referenced in the event) */
+ return Thing::_none;
+ }
+ _vm->_sound->requestPlay(k17_soundBUZZ, mapX, mapY, k1_soundModePlayIfPrioritized);
+ return groupThing;
+}
+
+bool GroupMan::isSquareACorridorTeleporterPitOrDoor(int16 mapX, int16 mapY) {
+ int16 squareType = Square(_vm->_dungeonMan->getSquare(mapX, mapY)).getType();
+
+ return ((squareType == k1_CorridorElemType) || (squareType == k5_ElementTypeTeleporter)
+ || (squareType == k2_ElementTypePit) || (squareType == k4_DoorElemType));
+}
+
+int16 GroupMan::getMeleeTargetCreatureOrdinal(int16 groupX, int16 groupY, int16 partyX, int16 partyY, uint16 champCell) {
+ Thing groupThing = groupGetThing(groupX, groupY);
+ if (groupThing == Thing::_endOfList)
+ return 0;
+
+ Group *group = (Group *)_vm->_dungeonMan->getThingData(groupThing);
+ signed char orderedCellsToAttack[4];
+ setOrderedCellsToAttack(orderedCellsToAttack, groupX, groupY, partyX, partyY, champCell);
+ uint16 counter = 0;
+ for (;;) { /*_Infinite loop_*/
+ int16 creatureOrdinal = getCreatureOrdinalInCell(group, orderedCellsToAttack[counter]);
+ if (creatureOrdinal)
+ return creatureOrdinal;
+
+ counter++;
+ }
+}
+
+int16 GroupMan::getMeleeActionDamage(Champion *champ, int16 champIndex, Group *group, int16 creatureIndex, int16 mapX, int16 mapY, uint16 actionHitProbability, uint16 actionDamageFactor, int16 skillIndex) {
+ int16 L0565_i_Damage = 0;
+ int16 L0566_i_Damage = 0;
+ int16 L0568_i_Defense;
+ int16 L0569_i_Outcome;
+
+ if (champIndex >= _vm->_championMan->_partyChampionCount)
+ return 0;
+
+ if (!champ->_currHealth)
+ return 0;
+
+ int16 doubledMapDifficulty = _vm->_dungeonMan->_currMap->_difficulty << 1;
+ CreatureInfo *creatureInfo = &_vm->_dungeonMan->_creatureInfos[group->_type];
+ int16 actionHandObjectIconIndex = _vm->_objectMan->getIconIndex(champ->_slots[kDMSlotActionHand]);
+ bool actionHitsNonMaterialCreatures = getFlag(actionHitProbability, k0x8000_hitNonMaterialCreatures);
+ if (actionHitsNonMaterialCreatures)
+ clearFlag(actionHitProbability, k0x8000_hitNonMaterialCreatures);
+
+ if ((!getFlag(creatureInfo->_attributes, k0x0040_MaskCreatureInfo_nonMaterial) || actionHitsNonMaterialCreatures) &&
+ ((_vm->_championMan->getDexterity(champ) > (_vm->getRandomNumber(32) + creatureInfo->_dexterity + doubledMapDifficulty - 16)) ||
+ (!_vm->getRandomNumber(4)) ||
+ (_vm->_championMan->isLucky(champ, 75 - actionHitProbability)))) {
+
+ L0565_i_Damage = _vm->_championMan->getStrength(champIndex, kDMSlotActionHand);
+ if (!(L0565_i_Damage))
+ goto T0231009;
+
+ L0565_i_Damage += _vm->getRandomNumber((L0565_i_Damage >> 1) + 1);
+ L0565_i_Damage = ((long)L0565_i_Damage * (long)actionDamageFactor) >> 5;
+ L0568_i_Defense = _vm->getRandomNumber(32) + creatureInfo->_defense + doubledMapDifficulty;
+ if (actionHandObjectIconIndex == kDMIconIndiceWeaponDiamondEdge)
+ L0568_i_Defense -= L0568_i_Defense >> 2;
+ else if (actionHandObjectIconIndex == kDMIconIndiceWeaponHardcleaveExecutioner)
+ L0568_i_Defense -= L0568_i_Defense >> 3;
+
+ L0565_i_Damage += _vm->getRandomNumber(32) - L0568_i_Defense;
+ L0566_i_Damage = L0565_i_Damage;
+ if (L0566_i_Damage <= 1) {
+T0231009:
+ L0565_i_Damage = _vm->getRandomNumber(4);
+ if (!L0565_i_Damage)
+ goto T0231015;
+
+ L0565_i_Damage++;
+ L0566_i_Damage += _vm->getRandomNumber(16);
+
+ if ((L0566_i_Damage > 0) || (_vm->getRandomNumber(2))) {
+ L0565_i_Damage += _vm->getRandomNumber(4);
+ if (!_vm->getRandomNumber(4))
+ L0565_i_Damage += MAX(0, L0566_i_Damage + _vm->getRandomNumber(16));
+ }
+ }
+ L0565_i_Damage >>= 1;
+ L0565_i_Damage += _vm->getRandomNumber(L0565_i_Damage) + _vm->getRandomNumber(4);
+ L0565_i_Damage += _vm->getRandomNumber(L0565_i_Damage);
+ L0565_i_Damage >>= 2;
+ L0565_i_Damage += _vm->getRandomNumber(4) + 1;
+ if ((actionHandObjectIconIndex == kDMIconIndiceWeaponVorpalBlade)
+ && !getFlag(creatureInfo->_attributes, k0x0040_MaskCreatureInfo_nonMaterial)
+ && !(L0565_i_Damage >>= 1))
+ goto T0231015;
+
+ if (_vm->getRandomNumber(64) < _vm->_championMan->getSkillLevel(champIndex, skillIndex))
+ L0565_i_Damage += L0565_i_Damage + 10;
+
+ L0569_i_Outcome = groupGetDamageCreatureOutcome(group, creatureIndex, mapX, mapY, L0565_i_Damage, true);
+ _vm->_championMan->addSkillExperience(champIndex, skillIndex, (L0565_i_Damage * creatureInfo->getExperience() >> 4) + 3);
+ _vm->_championMan->decrementStamina(champIndex, _vm->getRandomNumber(4) + 4);
+ goto T0231016;
+ }
+T0231015:
+ L0565_i_Damage = 0;
+ L0569_i_Outcome = k0_outcomeKilledNoCreaturesInGroup;
+ _vm->_championMan->decrementStamina(champIndex, _vm->getRandomNumber(2) + 2);
+T0231016:
+ _vm->_championMan->drawChampionState((ChampionIndex)champIndex);
+ if (L0569_i_Outcome != k2_outcomeKilledAllCreaturesInGroup) {
+ processEvents29to41(mapX, mapY, kM1_TMEventTypeCreateReactionEvent31ParyIsAdjacent, 0);
+ }
+ return L0565_i_Damage;
+}
+
+void GroupMan::fluxCageAction(int16 mapX, int16 mapY) {
+ SquareType squareType = _vm->_dungeonMan->getSquare(mapX, mapY).getType();
+ if ((squareType == k0_WallElemType) || (squareType == k3_StairsElemType))
+ return;
+
+ Thing unusedThing = _vm->_dungeonMan->getUnusedThing(k15_ExplosionThingType);
+ if (unusedThing == Thing::_none)
+ return;
+
+ _vm->_dungeonMan->linkThingToList(unusedThing, Thing(0), mapX, mapY);
+ (((Explosion *)_vm->_dungeonMan->_thingData[k15_ExplosionThingType])[unusedThing.getIndex()]).setType(k50_ExplosionType_Fluxcage);
+ TimelineEvent newEvent;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_currMapIndex, _vm->_gameTime + 100);
+ newEvent._type = k24_TMEventTypeRemoveFluxcage;
+ newEvent._priority = 0;
+ newEvent._C._slot = unusedThing.toUint16();
+ newEvent._B._location._mapX = mapX;
+ newEvent._B._location._mapY = mapY;
+ newEvent._B._location._mapY = mapY;
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ int16 fluxcageCount;
+ if (isLordChaosOnSquare(mapX, mapY - 1)) {
+ mapY--;
+ fluxcageCount = isFluxcageOnSquare(mapX + 1, mapY);
+ fluxcageCount += isFluxcageOnSquare(mapX, mapY - 1) + isFluxcageOnSquare(mapX - 1, mapY);
+ } else if (isLordChaosOnSquare(mapX - 1, mapY)) {
+ mapX--;
+ fluxcageCount = isFluxcageOnSquare(mapX, mapY + 1);
+ fluxcageCount += isFluxcageOnSquare(mapX, mapY - 1) + isFluxcageOnSquare(mapX - 1, mapY);
+ } else if (isLordChaosOnSquare(mapX + 1, mapY)) {
+ mapX++;
+ fluxcageCount = isFluxcageOnSquare(mapX, mapY - 1);
+ fluxcageCount += isFluxcageOnSquare(mapX, mapY + 1) + isFluxcageOnSquare(mapX + 1, mapY);
+ } else if (isLordChaosOnSquare(mapX, mapY + 1)) {
+ mapY++;
+ fluxcageCount = isFluxcageOnSquare(mapX - 1, mapY);
+ fluxcageCount += isFluxcageOnSquare(mapX, mapY + 1) + isFluxcageOnSquare(mapX + 1, mapY);
+ } else
+ fluxcageCount = 0;
+
+ if (fluxcageCount == 2)
+ processEvents29to41(mapX, mapY, kM3_TMEventTypeCreateReactionEvent29DangerOnSquare, 0);
+}
+
+uint16 GroupMan::isLordChaosOnSquare(int16 mapX, int16 mapY) {
+ Thing thing = groupGetThing(mapX, mapY);
+ if (thing == Thing::_endOfList)
+ return 0;
+
+ Group *group = (Group *)_vm->_dungeonMan->getThingData(thing);
+ if (group->_type == k23_CreatureTypeLordChaos)
+ return thing.toUint16();
+
+ return 0;
+}
+
+bool GroupMan::isFluxcageOnSquare(int16 mapX, int16 mapY) {
+ SquareType squareType = _vm->_dungeonMan->getSquare(mapX, mapY).getType();
+ if ((squareType == k0_WallElemType) || (squareType == k3_StairsElemType))
+ return false;
+
+ Thing thing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
+ while (thing != Thing::_endOfList) {
+ if ((thing.getType() == k15_ExplosionThingType) && (((Explosion *)_vm->_dungeonMan->_thingData[k15_ExplosionThingType])[thing.getIndex()].getType() == k50_ExplosionType_Fluxcage))
+ return true;
+
+ thing = _vm->_dungeonMan->getNextThing(thing);
+ }
+ return false;
+}
+
+void GroupMan::fuseAction(uint16 mapX, uint16 mapY) {
+ if ((mapX < 0) || (mapX >= _vm->_dungeonMan->_currMapWidth) || (mapY < 0) || (mapY >= _vm->_dungeonMan->_currMapHeight))
+ return;
+
+ _vm->_projexpl->createExplosion(Thing::_explHarmNonMaterial, 255, mapX, mapY, k255_CreatureTypeSingleCenteredCreature); /* BUG0_17 The game crashes after the Fuse action is performed while looking at a wall on a map boundary. An explosion thing is created on the square in front of the party but there is no check to ensure the square coordinates are in the map bounds. This corrupts a memory location and leads to a game crash */
+ Thing lordChaosThing = Thing(isLordChaosOnSquare(mapX, mapY));
+ if (lordChaosThing.toUint16()) {
+ bool isFluxcages[4];
+ isFluxcages[0] = isFluxcageOnSquare(mapX - 1, mapY);
+ isFluxcages[1] = isFluxcageOnSquare(mapX + 1, mapY);
+ isFluxcages[2] = isFluxcageOnSquare(mapX, mapY - 1);
+ isFluxcages[3] = isFluxcageOnSquare(mapX, mapY + 1);
+
+ uint16 fluxcageCount = 0;
+ for (int i = 0; i < 4; i++) {
+ if (isFluxcages[i])
+ fluxcageCount++;
+ }
+
+ while (fluxcageCount++ < 4) {
+ int16 destMapX = mapX;
+ int16 destMapY = mapY;
+ uint16 fluxcageIndex = _vm->getRandomNumber(4);
+ for (uint16 i = 5; --i; fluxcageIndex = returnNextVal(fluxcageIndex)) {
+ if (!isFluxcages[fluxcageIndex]) {
+ isFluxcages[fluxcageIndex] = true;
+ switch (fluxcageIndex) {
+ case 0:
+ destMapX--;
+ break;
+ case 1:
+ destMapX++;
+ break;
+ case 2:
+ destMapY--;
+ break;
+ case 3:
+ destMapY++;
+ }
+ break;
+ }
+ }
+ if (isSquareACorridorTeleporterPitOrDoor(destMapX, destMapY)) {
+ if (!_vm->_moveSens->getMoveResult(lordChaosThing, mapX, mapY, destMapX, destMapY))
+ startWandering(destMapX, destMapY);
+
+ return;
+ }
+ }
+ _vm->fuseSequence();
+ }
+}
+
+void GroupMan::saveActiveGroupPart(Common::OutSaveFile *file) {
+ for (uint16 i = 0; i < _maxActiveGroupCount; ++i) {
+ ActiveGroup *group = &_activeGroups[i];
+ file->writeUint16BE(group->_groupThingIndex);
+ file->writeUint16BE(group->_directions);
+ file->writeByte(group->_cells);
+ file->writeByte(group->_lastMoveTime);
+ file->writeByte(group->_delayFleeingFromTarget);
+ file->writeByte(group->_targetMapX);
+ file->writeByte(group->_targetMapY);
+ file->writeByte(group->_priorMapX);
+ file->writeByte(group->_priorMapY);
+ file->writeByte(group->_homeMapX);
+ file->writeByte(group->_homeMapY);
+ for (uint16 j = 0; j < 4; ++j)
+ file->writeByte(group->_aspect[j]);
+ }
+}
+
+void GroupMan::loadActiveGroupPart(Common::InSaveFile *file) {
+ for (uint16 i = 0; i < _maxActiveGroupCount; ++i) {
+ ActiveGroup *group = &_activeGroups[i];
+ group->_groupThingIndex = file->readUint16BE();
+ group->_directions = (Direction)file->readUint16BE();
+ group->_cells = file->readByte();
+ group->_lastMoveTime = file->readByte();
+ group->_delayFleeingFromTarget = file->readByte();
+ group->_targetMapX = file->readByte();
+ group->_targetMapY = file->readByte();
+ group->_priorMapX = file->readByte();
+ group->_priorMapY = file->readByte();
+ group->_homeMapX = file->readByte();
+ group->_homeMapY = file->readByte();
+ for (uint16 j = 0; j < 4; ++j)
+ group->_aspect[j] = file->readByte();
+ }
+}
+}
diff --git a/engines/dm/group.h b/engines/dm/group.h
new file mode 100644
index 0000000000..53d80c6e4d
--- /dev/null
+++ b/engines/dm/group.h
@@ -0,0 +1,252 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_GROUP_H
+#define DM_GROUP_H
+
+#include "dm/dm.h"
+
+namespace DM {
+ class Champion;
+ class TimelineEvent;
+ class CreatureInfo;
+
+ // this doesn't seem to be used anywhere at all
+/* Creature types */
+enum CreatureType {
+ k0_CreatureTypeGiantScorpionScorpion = 0, // @ C00_CREATURE_GIANT_SCORPION_SCORPION
+ k1_CreatureTypeSwampSlimeSlime = 1, // @ C01_CREATURE_SWAMP_SLIME_SLIME_DEVIL
+ k2_CreatureTypeGiggler = 2, // @ C02_CREATURE_GIGGLER
+ k3_CreatureTypeWizardEyeFlyingEye = 3, // @ C03_CREATURE_WIZARD_EYE_FLYING_EYE
+ k4_CreatureTypePainRatHellHound = 4, // @ C04_CREATURE_PAIN_RAT_HELLHOUND
+ k5_CreatureTypeRuster = 5, // @ C05_CREATURE_RUSTER
+ k6_CreatureTypeScreamer = 6, // @ C06_CREATURE_SCREAMER
+ k7_CreatureTypeRockpile = 7, // @ C07_CREATURE_ROCK_ROCKPILE
+ k8_CreatureTypeGhostRive = 8, // @ C08_CREATURE_GHOST_RIVE
+ k9_CreatureTypeStoneGolem = 9, // @ C09_CREATURE_STONE_GOLEM
+ k10_CreatureTypeMummy = 10, // @ C10_CREATURE_MUMMY
+ k11_CreatureTypeBlackFlame = 11, // @ C11_CREATURE_BLACK_FLAME
+ k12_CreatureTypeSkeleton = 12, // @ C12_CREATURE_SKELETON
+ k13_CreatureTypeCouatl = 13, // @ C13_CREATURE_COUATL
+ k14_CreatureTypeVexirk = 14, // @ C14_CREATURE_VEXIRK
+ k15_CreatureTypeMagnetaWormWorm = 15, // @ C15_CREATURE_MAGENTA_WORM_WORM
+ k16_CreatureTypeTrolinAntman = 16, // @ C16_CREATURE_TROLIN_ANTMAN
+ k17_CreatureTypeGiantWaspMuncher = 17, // @ C17_CREATURE_GIANT_WASP_MUNCHER
+ k18_CreatureTypeAnimatedArmourDethKnight = 18, // @ C18_CREATURE_ANIMATED_ARMOUR_DETH_KNIGHT
+ k19_CreatureTypeMaterializerZytaz = 19, // @ C19_CREATURE_MATERIALIZER_ZYTAZ
+ k20_CreatureTypeWaterElemental = 20, // @ C20_CREATURE_WATER_ELEMENTAL
+ k21_CreatureTypeOitu = 21, // @ C21_CREATURE_OITU
+ k22_CreatureTypeDemon = 22, // @ C22_CREATURE_DEMON
+ k23_CreatureTypeLordChaos = 23, // @ C23_CREATURE_LORD_CHAOS
+ k24_CreatureTypeRedDragon = 24, // @ C24_CREATURE_RED_DRAGON
+ k25_CreatureTypeLordOrder = 25, // @ C25_CREATURE_LORD_ORDER
+ k26_CreatureTypeGreyLord = 26, // @ C26_CREATURE_GREY_LORD
+ k255_CreatureTypeSingleCenteredCreature = 255 // @ C255_SINGLE_CENTERED_CREATURE
+};
+
+#define k0_MaskCreatureSizeQuarter 0 // @ C0_SIZE_QUARTER_SQUARE
+#define k1_MaskCreatureSizeHalf 1 // @ C1_SIZE_HALF_SQUARE
+#define k2_MaskCreatureSizeFull 2 // @ C2_SIZE_FULL_SQUARE
+
+#define k0x0003_MaskCreatureInfo_size 0x0003 // @ MASK0x0003_SIZE
+#define k0x0004_MaskCreatureInfo_sideAttack 0x0004 // @ MASK0x0004_SIDE_ATTACK
+#define k0x0008_MaskCreatureInfo_preferBackRow 0x0008 // @ MASK0x0008_PREFER_BACK_ROW
+#define k0x0010_MaskCreatureInfo_attackAnyChamp 0x0010 // @ MASK0x0010_ATTACK_ANY_CHAMPION
+#define k0x0020_MaskCreatureInfo_levitation 0x0020 // @ MASK0x0020_LEVITATION
+#define k0x0040_MaskCreatureInfo_nonMaterial 0x0040 // @ MASK0x0040_NON_MATERIAL
+#define k0x0200_MaskCreatureInfo_dropFixedPoss 0x0200 // @ MASK0x0200_DROP_FIXED_POSSESSIONS
+#define k0x0400_MaskCreatureInfo_keepThrownSharpWeapon 0x0400 // @ MASK0x0400_KEEP_THROWN_SHARP_WEAPONS
+#define k0x0800_MaskCreatureInfo_seeInvisible 0x0800 // @ MASK0x0800_SEE_INVISIBLE
+#define k0x1000_MaskCreatureInfo_nightVision 0x1000 // @ MASK0x1000_NIGHT_VISION
+#define k0x2000_MaskCreatureInfo_archenemy 0x2000 // @ MASK0x2000_ARCHENEMY
+#define k0x4000_MaskCreatureInfo_magicmap 0x4000 // @ MASK0x4000_MAGICMAP
+
+
+#define k0x0040_MaskActiveGroupFlipBitmap 0x0040 // @ MASK0x0040_FLIP_BITMAP
+#define k0x0080_MaskActiveGroupIsAttacking 0x0080 // @ MASK0x0080_IS_ATTACKING
+
+class ActiveGroup {
+public:
+ int16 _groupThingIndex;
+ Direction _directions;
+ byte _cells;
+ byte _lastMoveTime;
+ byte _delayFleeingFromTarget;
+ byte _targetMapX;
+ byte _targetMapY;
+ byte _priorMapX;
+ byte _priorMapY;
+ byte _homeMapX;
+ byte _homeMapY;
+ byte _aspect[4];
+}; // @ ACTIVE_GROUP
+
+
+class Group {
+public:
+ Thing _nextThing;
+ Thing _slot;
+ uint16 _type;
+ uint16 _cells;
+ uint16 _health[4];
+ uint16 _flags;
+public:
+ explicit Group(uint16 *rawDat) : _nextThing(rawDat[0]), _slot(rawDat[1]), _type(rawDat[2]),
+ _cells(rawDat[3]), _flags(rawDat[8]) {
+ _health[0] = rawDat[4];
+ _health[1] = rawDat[5];
+ _health[2] = rawDat[6];
+ _health[3] = rawDat[7];
+ }
+
+ uint16 &getActiveGroupIndex() { return _cells; }
+
+ uint16 getBehaviour() { return _flags & 0xF; }
+ uint16 setBehaviour(uint16 val) { _flags = (_flags & ~0xF) | (val & 0xF); return (val & 0xF); }
+ uint16 getCount() { return (_flags >> 5) & 0x3; }
+ void setCount(uint16 val) { _flags = (_flags & ~(0x3 << 5)) | ((val & 0x3) << 5); }
+ Direction getDir() { return (Direction)((_flags >> 8) & 0x3); }
+ void setDir(uint16 val) { _flags = (_flags & ~(0x3 << 8)) | ((val & 0x3) << 8); }
+ uint16 getDoNotDiscard() { return (_flags >> 10) & 0x1; }
+ void setDoNotDiscard(bool val) { _flags = (_flags & ~(1 << 10)) | ((val & 1) << 10); }
+}; // @ GROUP
+
+#define k0_behavior_WANDER 0 // @ C0_BEHAVIOR_WANDER
+#define k2_behavior_USELESS 2 // @ C2_BEHAVIOR_USELESS
+#define k3_behavior_USELESS 3 // @ C3_BEHAVIOR_USELESS
+#define k4_behavior_USELESS 4 // @ C4_BEHAVIOR_USELESS
+#define k5_behavior_FLEE 5 // @ C5_BEHAVIOR_FLEE
+#define k6_behavior_ATTACK 6 // @ C6_BEHAVIOR_ATTACK
+#define k7_behavior_APPROACH 7 // @ C7_BEHAVIOR_APPROACH
+
+#define k15_immuneToFear 15 // @ C15_IMMUNE_TO_FEAR
+
+#define k255_immobile 255 // @ C255_IMMOBILE
+#define kM1_wholeCreatureGroup -1 // @ CM1_WHOLE_CREATURE_GROUP
+
+
+int32 M32_setTime(int32 &map_time, int32 time); // @ M32_SET_TIME
+
+
+class GroupMan {
+ DMEngine *_vm;
+ byte _dropMovingCreatureFixedPossessionsCell[4]; // @ G0392_auc_DropMovingCreatureFixedPossessionsCells
+ uint16 _dropMovingCreatureFixedPossCellCount; // @ G0391_ui_DropMovingCreatureFixedPossessionsCellCount
+ uint16 _fluxCageCount; // @ G0386_ui_FluxCageCount
+ int16 _fluxCages[4]; // @ G0385_ac_FluxCages
+ int16 _currentGroupMapX; // @ G0378_i_CurrentGroupMapX
+ int16 _currentGroupMapY; // @ G0379_i_CurrentGroupMapY
+ Thing _currGroupThing; // @ G0380_T_CurrentGroupThing
+ int16 _groupMovementTestedDirections[4]; // @ G0384_auc_GroupMovementTestedDirections
+ uint16 _currGroupDistanceToParty; // @ G0381_ui_CurrentGroupDistanceToParty
+ int16 _currGroupPrimaryDirToParty; // @ G0382_i_CurrentGroupPrimaryDirectionToParty
+ int16 _currGroupSecondaryDirToParty; // @ G0383_i_CurrentGroupSecondaryDirectionToParty
+
+ Thing _groupMovementBlockedByGroupThing; // @ G0388_T_GroupMovementBlockedByGroupThing
+ bool _groupMovementBlockedByDoor; // @ G0389_B_GroupMovementBlockedByDoor
+ bool _groupMovementBlockedByParty; // @ G0390_B_GroupMovementBlockedByParty
+ bool _groupMovBlockedByWallStairsPitFakeWalFluxCageTeleporter; // @ G0387_B_GroupMovementBlockedByWallStairsPitFakeWallFluxcageTeleporter
+ int32 twoHalfSquareSizedCreaturesGroupLastDirectionSetTime; // @ G0395_l_TwoHalfSquareSizedCreaturesGroupLastDirectionSetTime
+public:
+ uint16 _maxActiveGroupCount; // @ G0376_ui_MaximumActiveGroupCount
+ ActiveGroup *_activeGroups; // @ G0375_ps_ActiveGroups
+ uint16 _currActiveGroupCount; // @ G0377_ui_CurrentActiveGroupCount
+ explicit GroupMan(DMEngine *vm);
+ ~GroupMan();
+
+ void initActiveGroups(); // @ F0196_GROUP_InitializeActiveGroups
+ uint16 getGroupCells(Group *group, int16 mapIndex); // @ F0145_DUNGEON_GetGroupCells
+ uint16 getGroupDirections(Group *group, int16 mapIndex); // @ F0147_DUNGEON_GetGroupDirections
+ int16 getCreatureOrdinalInCell(Group *group, uint16 cell); // @ F0176_GROUP_GetCreatureOrdinalInCell
+ uint16 getCreatureValue(uint16 groupVal, uint16 creatureIndex); // @ M50_CREATURE_VALUE
+ void dropGroupPossessions(int16 mapX, int16 mapY, Thing groupThing, int16 mode); // @ F0188_GROUP_DropGroupPossessions
+ void dropCreatureFixedPossessions(uint16 creatureType, int16 mapX, int16 mapY, uint16 cell,
+ int16 mode); // @ F0186_GROUP_DropCreatureFixedPossessions
+ int16 getDirsWhereDestIsVisibleFromSource(int16 srcMapX, int16 srcMapY,
+ int16 destMapX, int16 destMapY); // @ F0228_GROUP_GetDirectionsWhereDestinationIsVisibleFromSource
+ bool isDestVisibleFromSource(uint16 dir, int16 srcMapX, int16 srcMapY, int16 destMapX,
+ int16 destMapY); // @ F0227_GROUP_IsDestinationVisibleFromSource
+ bool groupIsDoorDestoryedByAttack(uint16 mapX, uint16 mapY, int16 attack,
+ bool magicAttack, int16 ticks); // @ F0232_GROUP_IsDoorDestroyedByAttack
+ Thing groupGetThing(int16 mapX, int16 mapY); // @ F0175_GROUP_GetThing
+ int16 groupGetDamageCreatureOutcome(Group *group, uint16 creatureIndex,
+ int16 mapX, int16 mapY, int16 damage, bool notMoving); // @ F0190_GROUP_GetDamageCreatureOutcome
+ void groupDelete(int16 mapX, int16 mapY); // @ F0189_GROUP_Delete
+ void groupDeleteEvents(int16 mapX, int16 mapY); // @ F0181_GROUP_DeleteEvents
+ uint16 getGroupValueUpdatedWithCreatureValue(uint16 groupVal, uint16 creatureIndex, uint16 creatureVal); // @ F0178_GROUP_GetGroupValueUpdatedWithCreatureValue
+ int16 getDamageAllCreaturesOutcome(Group *group, int16 mapX, int16 mapY, int16 attack, bool notMoving); // @ F0191_GROUP_GetDamageAllCreaturesOutcome
+ int16 groupGetResistanceAdjustedPoisonAttack(uint16 creatreType, int16 poisonAttack); // @ F0192_GROUP_GetResistanceAdjustedPoisonAttack
+ void processEvents29to41(int16 eventMapX, int16 eventMapY, int16 eventType, uint16 ticks); // @ F0209_GROUP_ProcessEvents29to41
+ bool isMovementPossible(CreatureInfo *creatureInfo, int16 mapX, int16 mapY,
+ uint16 dir, bool allowMovementOverImaginaryPitsAndFakeWalls); // @ F0202_GROUP_IsMovementPossible
+ int16 getDistanceBetweenSquares(int16 srcMapX, int16 srcMapY, int16 destMapX,
+ int16 destMapY); // @ F0226_GROUP_GetDistanceBetweenSquares
+
+ int16 groupGetDistanceToVisibleParty(Group *group, int16 creatureIndex, int16 mapX, int16 mapY); // @ F0200_GROUP_GetDistanceToVisibleParty
+ int16 getDistanceBetweenUnblockedSquares(int16 srcMapX, int16 srcMapY,
+ int16 destMapX, int16 destMapY, bool (GroupMan::*isBlocked)(uint16, uint16)); // @ F0199_GROUP_GetDistanceBetweenUnblockedSquares
+ bool isViewPartyBlocked(uint16 mapX, uint16 mapY); // @ F0197_GROUP_IsViewPartyBlocked
+ int32 getCreatureAspectUpdateTime(ActiveGroup *activeGroup, int16 creatureIndex,
+ bool isAttacking); // @ F0179_GROUP_GetCreatureAspectUpdateTime
+ void setGroupDirection(ActiveGroup *activeGroup, int16 dir, int16 creatureIndex, bool twoHalfSquareSizedCreatures); // @ F0205_GROUP_SetDirection
+ void addGroupEvent(TimelineEvent *event, uint32 time); // @ F0208_GROUP_AddEvent
+ int16 getSmelledPartyPrimaryDirOrdinal(CreatureInfo *creatureInfo, int16 mapY, int16 mapX); // @ F0201_GROUP_GetSmelledPartyPrimaryDirectionOrdinal
+ bool isSmellPartyBlocked(uint16 mapX, uint16 mapY); // @ F0198_GROUP_IsSmellPartyBlocked
+ int16 getFirstPossibleMovementDirOrdinal(CreatureInfo *info, int16 mapX, int16 mapY,
+ bool allowMovementOverImaginaryPitsAndFakeWalls); // @ F0203_GROUP_GetFirstPossibleMovementDirectionOrdinal
+ void setDirGroup(ActiveGroup *activeGroup, int16 dir, int16 creatureIndex,
+ int16 creatureSize); // @ F0206_GROUP_SetDirectionGroup
+ void stopAttacking(ActiveGroup *group, int16 mapX, int16 mapY);// @ F0182_GROUP_StopAttacking
+ bool isArchenemyDoubleMovementPossible(CreatureInfo *info, int16 mapX, int16 mapY, uint16 dir); // @ F0204_GROUP_IsArchenemyDoubleMovementPossible
+ bool isCreatureAttacking(Group *group, int16 mapX, int16 mapY, uint16 creatureIndex); // @ F0207_GROUP_IsCreatureAttacking
+ void setOrderedCellsToAttack(signed char *orderedCellsToAttack, int16 targetMapX,
+ int16 targetMapY, int16 attackerMapX, int16 attackerMapY, uint16 cellSource); // @ F0229_GROUP_SetOrderedCellsToAttack
+ void stealFromChampion(Group *group, uint16 championIndex); // @ F0193_GROUP_StealFromChampion
+ int16 getChampionDamage(Group *group, uint16 champIndex); // @ F0230_GROUP_GetChampionDamage
+ void dropMovingCreatureFixedPossession(Thing thing, int16 mapX, int16 mapY); // @ F0187_GROUP_DropMovingCreatureFixedPossessions
+ void startWandering(int16 mapX, int16 mapY); // @ F0180_GROUP_StartWandering
+ void addActiveGroup(Thing thing, int16 mapX, int16 mapY); // @ F0183_GROUP_AddActiveGroup
+ void removeActiveGroup(uint16 activeGroupIndex); // @ F0184_GROUP_RemoveActiveGroup
+ void removeAllActiveGroups(); // @ F0194_GROUP_RemoveAllActiveGroups
+ void addAllActiveGroups(); // @ F0195_GROUP_AddAllActiveGroups
+ Thing groupGetGenerated(int16 creatureType, int16 healthMultiplier, uint16 creatureCount, Direction dir, int16 mapX, int16 mapY); // @ F0185_GROUP_GetGenerated
+ bool isSquareACorridorTeleporterPitOrDoor(int16 mapX, int16 mapY); // @ F0223_GROUP_IsSquareACorridorTeleporterPitOrDoor
+ int16 getMeleeTargetCreatureOrdinal(int16 groupX, int16 groupY, int16 partyX, int16 paryY,
+ uint16 champCell); // @ F0177_GROUP_GetMeleeTargetCreatureOrdinal
+ int16 getMeleeActionDamage(Champion *champ, int16 champIndex, Group *group, int16 creatureIndex,
+ int16 mapX, int16 mapY, uint16 actionHitProbability, uint16 actionDamageFactor, int16 skillIndex); // @ F0231_GROUP_GetMeleeActionDamage
+ void fluxCageAction(int16 mapX, int16 mapY); // @ F0224_GROUP_FluxCageAction
+ uint16 isLordChaosOnSquare(int16 mapX, int16 mapY); // @ F0222_GROUP_IsLordChaosOnSquare
+ bool isFluxcageOnSquare(int16 mapX, int16 mapY); // @ F0221_GROUP_IsFluxcageOnSquare
+ void fuseAction(uint16 mapX, uint16 mapY); // @ F0225_GROUP_FuseAction
+ void saveActiveGroupPart(Common::OutSaveFile *file);
+ void loadActiveGroupPart(Common::InSaveFile *file);
+};
+
+}
+
+#endif
diff --git a/engines/dm/inventory.cpp b/engines/dm/inventory.cpp
new file mode 100644
index 0000000000..7355708bf5
--- /dev/null
+++ b/engines/dm/inventory.cpp
@@ -0,0 +1,1072 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "graphics/surface.h"
+#include "graphics/thumbnail.h"
+
+#include "dm/inventory.h"
+#include "dm/dungeonman.h"
+#include "dm/eventman.h"
+#include "dm/menus.h"
+#include "dm/gfx.h"
+#include "dm/text.h"
+#include "dm/objectman.h"
+#include "dm/timeline.h"
+#include "dm/projexpl.h"
+#include "dm/sounds.h"
+
+
+namespace DM {
+
+void InventoryMan::initConstants() {
+ static const char* skillLevelNamesEN[15] = {"NEOPHYTE", "NOVICE", "APPRENTICE", "JOURNEYMAN", "CRAFTSMAN",
+ "ARTISAN", "ADEPT", "EXPERT", "` MASTER", "a MASTER","b MASTER", "c MASTER", "d MASTER", "e MASTER", "ARCHMASTER"};
+ static const char* skillLevelNamesDE[15] = {"ANFAENGER", "NEULING", "LEHRLING", "ARBEITER", "GESELLE", "HANDWERKR", "FACHMANN",
+ "EXPERTE", "` MEISTER", "a MEISTER", "b MEISTER", "c MEISTER", "d MEISTER", "e MEISTER", "ERZMEISTR"};
+ static const char* skillLevelNamesFR[15] = {"NEOPHYTE", "NOVICE", "APPRENTI", "COMPAGNON", "ARTISAN", "PATRON",
+ "ADEPTE", "EXPERT", "MAITRE '", "MAITRE a", "MAITRE b", "MAITRE c", "MAITRE d", "MAITRE e", "SUR-MAITRE"};
+ const char **translatedSkillLevel;
+ switch (_vm->getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY:
+ translatedSkillLevel = skillLevelNamesEN;
+ break;
+ case Common::DE_DEU:
+ translatedSkillLevel = skillLevelNamesDE;
+ break;
+ case Common::FR_FRA:
+ translatedSkillLevel = skillLevelNamesFR;
+ break;
+ }
+ for (int i = 0; i < 15; ++i)
+ _skillLevelNames[i] = translatedSkillLevel[i];
+
+ _boxPanel = Box(80, 223, 52, 124); // @ G0032_s_Graphic562_Box_Panel
+}
+
+InventoryMan::InventoryMan(DMEngine *vm) : _vm(vm) {
+ _inventoryChampionOrdinal = 0;
+ _panelContent = k0_PanelContentFoodWaterPoisoned;
+ for (uint16 i = 0; i < 8; ++i)
+ _chestSlots[i] = Thing(0);
+ _openChest = Thing::_none;
+ _objDescTextXpos = 0;
+ _objDescTextYpos = 0;
+
+ for (int i = 0; i < 15; i++)
+ _skillLevelNames[i] = nullptr;
+
+ initConstants();
+}
+
+void InventoryMan::toggleInventory(ChampionIndex championIndex) {
+ static Box boxFloppyZzzCross(174, 218, 2, 12); // @ G0041_s_Graphic562_Box_ViewportFloppyZzzCross
+
+ if ((championIndex != kDMChampionCloseInventory) && !_vm->_championMan->_champions[championIndex]._currHealth)
+ return;
+
+ if (_vm->_pressingMouth || _vm->_pressingEye)
+ return;
+
+ _vm->_stopWaitingForPlayerInput = true;
+ uint16 inventoryChampionOrdinal = _inventoryChampionOrdinal;
+ if (_vm->indexToOrdinal(championIndex) == inventoryChampionOrdinal)
+ championIndex = kDMChampionCloseInventory;
+
+ _vm->_eventMan->showMouse();
+ if (inventoryChampionOrdinal) {
+ _inventoryChampionOrdinal = _vm->indexToOrdinal(kDMChampionNone);
+ closeChest();
+ Champion *champion = &_vm->_championMan->_champions[_vm->ordinalToIndex(inventoryChampionOrdinal)];
+ if (champion->_currHealth && !_vm->_championMan->_candidateChampionOrdinal) {
+ setFlag(champion->_attributes, kDMAttributeStatusBox);
+ _vm->_championMan->drawChampionState((ChampionIndex)_vm->ordinalToIndex(inventoryChampionOrdinal));
+ }
+ if (_vm->_championMan->_partyIsSleeping) {
+ _vm->_eventMan->hideMouse();
+ return;
+ }
+ if (championIndex == kDMChampionCloseInventory) {
+ _vm->_eventMan->_refreshMousePointerInMainLoop = true;
+ _vm->_menuMan->drawMovementArrows();
+ _vm->_eventMan->hideMouse();
+ _vm->_eventMan->_secondaryMouseInput = _vm->_eventMan->_secondaryMouseInputMovement;
+ _vm->_eventMan->_secondaryKeyboardInput = _vm->_eventMan->_secondaryKeyboardInputMovement;
+ _vm->_eventMan->discardAllInput();
+ _vm->_displayMan->drawFloorAndCeiling();
+ return;
+ }
+ }
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ _inventoryChampionOrdinal = _vm->indexToOrdinal(championIndex);
+ if (!inventoryChampionOrdinal)
+ _vm->_displayMan->shadeScreenBox(&_vm->_displayMan->_boxMovementArrows, k0_ColorBlack);
+
+ Champion *champion = &_vm->_championMan->_champions[championIndex];
+ _vm->_displayMan->loadIntoBitmap(k17_InventoryGraphicIndice, _vm->_displayMan->_bitmapViewport);
+ if (_vm->_championMan->_candidateChampionOrdinal)
+ _vm->_displayMan->fillBoxBitmap(_vm->_displayMan->_bitmapViewport, boxFloppyZzzCross, k12_ColorDarkestGray, k112_byteWidthViewport, k136_heightViewport);
+
+ switch (_vm->getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY:
+ _vm->_textMan->printToViewport(5, 116, k13_ColorLightestGray, "HEALTH");
+ _vm->_textMan->printToViewport(5, 124, k13_ColorLightestGray, "STAMINA");
+ break;
+ case Common::DE_DEU:
+ _vm->_textMan->printToViewport(5, 116, k13_ColorLightestGray, "GESUND");
+ _vm->_textMan->printToViewport(5, 124, k13_ColorLightestGray, "KRAFT");
+ break;
+ case Common::FR_FRA:
+ _vm->_textMan->printToViewport(5, 116, k13_ColorLightestGray, "SANTE");
+ _vm->_textMan->printToViewport(5, 124, k13_ColorLightestGray, "VIGUEUR");
+ break;
+ }
+
+ _vm->_textMan->printToViewport(5, 132, k13_ColorLightestGray, "MANA");
+
+ for (uint16 i = kDMSlotReadyHand; i < kDMSlotChest1; i++)
+ _vm->_championMan->drawSlot(championIndex, i);
+
+ setFlag(champion->_attributes, kDMAttributeViewport | kDMAttributeStatusBox | kDMAttributePanel | kDMAttributeLoad | kDMAttributeStatistics | kDMAttributeNameTitle);
+ _vm->_championMan->drawChampionState(championIndex);
+ _vm->_eventMan->_mousePointerBitmapUpdated = true;
+ _vm->_eventMan->hideMouse();
+ _vm->_eventMan->_secondaryMouseInput = _vm->_eventMan->_secondaryMouseInputChampionInventory;
+ _vm->_eventMan->_secondaryKeyboardInput = nullptr;
+ _vm->_eventMan->discardAllInput();
+}
+
+void InventoryMan::drawStatusBoxPortrait(ChampionIndex championIndex) {
+ DisplayMan &dispMan = *_vm->_displayMan;
+ dispMan._useByteBoxCoordinates = false;
+ Box box;
+ box._y1 = 0;
+ box._y2 = 28;
+ box._x1 = championIndex * k69_ChampionStatusBoxSpacing + 7;
+ box._x2 = box._x1 + 31;
+ dispMan.blitToScreen(_vm->_championMan->_champions[championIndex]._portrait, &box, k16_byteWidth, kM1_ColorNoTransparency, 29);
+}
+
+void InventoryMan::drawPanelHorizontalBar(int16 x, int16 y, int16 pixelWidth, Color color) {
+ Box box;
+ box._x1 = x;
+ box._x2 = box._x1 + pixelWidth;
+ box._y1 = y;
+ box._y2 = box._y1 + 6;
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ _vm->_displayMan->fillBoxBitmap(_vm->_displayMan->_bitmapViewport, box, color, k112_byteWidthViewport, k136_heightViewport);
+}
+
+void InventoryMan::drawPanelFoodOrWaterBar(int16 amount, int16 y, Color color) {
+ if (amount < -512)
+ color = k8_ColorRed;
+ else if (amount < 0)
+ color = k11_ColorYellow;
+
+ int16 pixelWidth = amount + 1024;
+ if (pixelWidth == 3072)
+ pixelWidth = 3071;
+
+ pixelWidth /= 32;
+ drawPanelHorizontalBar(115, y + 2, pixelWidth, k0_ColorBlack);
+ drawPanelHorizontalBar(113, y, pixelWidth, color);
+}
+
+void InventoryMan::drawPanelFoodWaterPoisoned() {
+ static Box boxFood(112, 159, 60, 68); // @ G0035_s_Graphic562_Box_Food
+ static Box boxWater(112, 159, 83, 91); // @ G0036_s_Graphic562_Box_Water
+ static Box boxPoisoned(112, 207, 105, 119); // @ G0037_s_Graphic562_Box_Poisoned
+
+ Champion &champ = _vm->_championMan->_champions[_inventoryChampionOrdinal];
+ closeChest();
+ DisplayMan &dispMan = *_vm->_displayMan;
+ dispMan.blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k20_PanelEmptyIndice), _boxPanel, k72_byteWidth, k8_ColorRed, 73);
+
+ switch (_vm->getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY:
+ dispMan.blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k30_FoodLabelIndice), boxFood, k24_byteWidth, k12_ColorDarkestGray, 9);
+ dispMan.blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k31_WaterLabelIndice), boxWater, k24_byteWidth, k12_ColorDarkestGray, 9);
+ break;
+ case Common::DE_DEU:
+ dispMan.blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k30_FoodLabelIndice), boxFood, k32_byteWidth, k12_ColorDarkestGray, 9);
+ dispMan.blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k31_WaterLabelIndice), boxWater, k32_byteWidth, k12_ColorDarkestGray, 9);
+ break;
+ case Common::FR_FRA:
+ dispMan.blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k30_FoodLabelIndice), boxFood, k48_byteWidth, k12_ColorDarkestGray, 9);
+ dispMan.blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k31_WaterLabelIndice), boxWater, k24_byteWidth, k12_ColorDarkestGray, 9);
+ break;
+ }
+
+ if (champ._poisonEventCount)
+ dispMan.blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k32_PoisionedLabelIndice),
+ boxPoisoned, k48_byteWidth, k12_ColorDarkestGray, 15);
+
+ drawPanelFoodOrWaterBar(champ._food, 69, k5_ColorLightBrown);
+ drawPanelFoodOrWaterBar(champ._water, 92, k14_ColorBlue);
+}
+
+void InventoryMan::drawPanelResurrectReincarnate() {
+ _panelContent = k5_PanelContentResurrectReincarnate;
+ _vm->_displayMan->blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k40_PanelResurectReincaranteIndice),
+ _boxPanel, k72_byteWidth, k6_ColorDarkGreen, 73);
+}
+
+void InventoryMan::drawPanel() {
+ closeChest();
+
+ ChampionMan &cm = *_vm->_championMan;
+ if (cm._candidateChampionOrdinal) {
+ drawPanelResurrectReincarnate();
+ return;
+ }
+
+ Thing thing = cm._champions[_vm->ordinalToIndex(_inventoryChampionOrdinal)].getSlot(kDMSlotActionHand);
+
+ _panelContent = k0_PanelContentFoodWaterPoisoned;
+ switch (thing.getType()) {
+ case k9_ContainerThingType:
+ _panelContent = k4_PanelContentChest;
+ break;
+ case k7_ScrollThingType:
+ _panelContent = k2_PanelContentScroll;
+ break;
+ default:
+ thing = Thing::_none;
+ break;
+ }
+
+ if (thing == Thing::_none)
+ drawPanelFoodWaterPoisoned();
+ else
+ drawPanelObject(thing, false);
+}
+
+void InventoryMan::closeChest() {
+ DungeonMan &dunMan = *_vm->_dungeonMan;
+
+ bool processFirstChestSlot = true;
+ if (_openChest == Thing::_none)
+ return;
+ Container *container = (Container *)dunMan.getThingData(_openChest);
+ _openChest = Thing::_none;
+ container->getSlot() = Thing::_endOfList;
+ Thing prevThing;
+ for (int16 chestSlotIndex = 0; chestSlotIndex < 8; ++chestSlotIndex) {
+ Thing thing = _chestSlots[chestSlotIndex];
+ if (thing != Thing::_none) {
+ _chestSlots[chestSlotIndex] = Thing::_none; // CHANGE8_09_FIX
+
+ if (processFirstChestSlot) {
+ processFirstChestSlot = false;
+ *dunMan.getThingData(thing) = Thing::_endOfList.toUint16();
+ container->getSlot() = prevThing = thing;
+ } else {
+ dunMan.linkThingToList(thing, prevThing, kM1_MapXNotOnASquare, 0);
+ prevThing = thing;
+ }
+ }
+ }
+}
+
+void InventoryMan::drawPanelScrollTextLine(int16 yPos, char *text) {
+ for (char *iter = text; *iter != '\0'; ++iter) {
+ if ((*iter >= 'A') && (*iter <= 'Z'))
+ *iter -= 64;
+ else if (*iter >= '{') // this branch is CHANGE5_03_IMPROVEMENT
+ *iter -= 96;
+ }
+ _vm->_textMan->printToViewport(162 - (6 * strlen(text) / 2), yPos, k0_ColorBlack, text, k15_ColorWhite);
+}
+
+void InventoryMan::drawPanelScroll(Scroll *scroll) {
+ DisplayMan &dispMan = *_vm->_displayMan;
+
+ char stringFirstLine[300];
+ _vm->_dungeonMan->decodeText(stringFirstLine, Thing(scroll->getTextStringThingIndex()), (TextType)(k2_TextTypeScroll | k0x8000_DecodeEvenIfInvisible));
+ char *charRed = stringFirstLine;
+ while (*charRed && (*charRed != '\n'))
+ charRed++;
+
+ *charRed = '\0';
+ dispMan.blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k23_PanelOpenScrollIndice),
+ _boxPanel, k72_byteWidth, k8_ColorRed, 73);
+ int16 lineCount = 1;
+ charRed++;
+ char *charGreen = charRed; // first char of the second line
+ while (*charGreen) {
+ /* BUG0_47 Graphical glitch when you open a scroll. If there is a single line of text in a scroll
+ (with no carriage return) then charGreen points to undefined data. This may result in a graphical
+ glitch and also corrupt other memory. This is not an issue in the original dungeons where all
+ scrolls contain at least one carriage return character */
+ if (*charGreen == '\n')
+ lineCount++;
+
+ charGreen++;
+ }
+
+ if (*(charGreen - 1) != '\n')
+ lineCount++;
+ else if (*(charGreen - 2) == '\n')
+ lineCount--;
+
+ int16 yPos = 92 - (7 * lineCount) / 2; // center the text vertically
+ drawPanelScrollTextLine(yPos, stringFirstLine);
+ charGreen = charRed;
+ while (*charGreen) {
+ yPos += 7;
+ while (*charRed && (*charRed != '\n'))
+ charRed++;
+
+ if (!(*charRed))
+ charRed[1] = '\0';
+
+ *charRed++ = '\0';
+ drawPanelScrollTextLine(yPos, charGreen);
+ charGreen = charRed;
+ }
+}
+
+void InventoryMan::openAndDrawChest(Thing thingToOpen, Container *chest, bool isPressingEye) {
+ DisplayMan &dispMan = *_vm->_displayMan;
+ ObjectMan &objMan = *_vm->_objectMan;
+
+ if (_openChest == thingToOpen)
+ return;
+
+ if (_openChest != Thing::_none)
+ closeChest(); // CHANGE8_09_FIX
+
+ _openChest = thingToOpen;
+ if (!isPressingEye) {
+ objMan.drawIconInSlotBox(k9_SlotBoxInventoryActionHand, kDMIconIndiceContainerChestOpen);
+ }
+ dispMan.blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k25_PanelOpenChestIndice),
+ _boxPanel, k72_byteWidth, k8_ColorRed, 73);
+ int16 chestSlotIndex = 0;
+ Thing thing = chest->getSlot();
+ int16 thingCount = 0;
+ while (thing != Thing::_endOfList) {
+ if (++thingCount > 8)
+ break; // CHANGE8_08_FIX, make sure that no more than the first 8 objects in a chest are drawn
+
+ objMan.drawIconInSlotBox(chestSlotIndex + k38_SlotBoxChestFirstSlot, objMan.getIconIndex(thing));
+ _chestSlots[chestSlotIndex++] = thing;
+ thing = _vm->_dungeonMan->getNextThing(thing);
+ }
+ while (chestSlotIndex < 8) {
+ objMan.drawIconInSlotBox(chestSlotIndex + k38_SlotBoxChestFirstSlot, kDMIconIndiceNone);
+ _chestSlots[chestSlotIndex++] = Thing::_none;
+ }
+}
+
+void InventoryMan::drawIconToViewport(IconIndice iconIndex, int16 xPos, int16 yPos) {
+ static byte iconBitmap[16 * 16];
+ Box boxIcon(xPos, xPos + 15, yPos, yPos + 15);
+
+ _vm->_objectMan->extractIconFromBitmap(iconIndex, iconBitmap);
+ _vm->_displayMan->blitToViewport(iconBitmap, boxIcon, k8_byteWidth, kM1_ColorNoTransparency, 16);
+}
+
+void InventoryMan::buildObjectAttributeString(int16 potentialAttribMask, int16 actualAttribMask, const char **attribStrings, char *destString, const char *prefixString, const char *suffixString) {
+ uint16 identicalBitCount = 0;
+ int16 attribMask = 1;
+ for (uint16 stringIndex = 0; stringIndex < 16; stringIndex++, attribMask <<= 1) {
+ if (attribMask & potentialAttribMask & actualAttribMask)
+ identicalBitCount++;
+ }
+
+ if (identicalBitCount == 0) {
+ *destString = '\0';
+ return;
+ }
+
+ strcpy(destString, prefixString);
+
+ attribMask = 1;
+ for (uint16 stringIndex = 0; stringIndex < 16; stringIndex++, attribMask <<= 1) {
+ if (attribMask & potentialAttribMask & actualAttribMask) {
+ strcat(destString, attribStrings[stringIndex]);
+ if (identicalBitCount-- > 2) {
+ strcat(destString, ", ");
+ } else if (identicalBitCount == 1) {
+
+ switch (_vm->getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY: strcat(destString, " AND "); break;
+ case Common::DE_DEU: strcat(destString, " UND "); break;
+ case Common::FR_FRA: strcat(destString, " ET "); break;
+ }
+ }
+ }
+ }
+
+ strcat(destString, suffixString);
+}
+
+void InventoryMan::drawPanelObjectDescriptionString(const char *descString) {
+ if (descString[0] == '\f') { // form feed
+ descString++;
+ _objDescTextXpos = 108;
+ _objDescTextYpos = 59;
+ }
+
+ if (descString[0]) {
+ char stringTmpBuff[128];
+ strcpy(stringTmpBuff, descString);
+
+ char *stringLine = stringTmpBuff;
+ bool severalLines = false;
+ char *string = nullptr;
+ while (*stringLine) {
+ if (strlen(stringLine) > 18) { // if string is too long to fit on one line
+ string = &stringLine[17];
+ while (*string != ' ') // go back to the last space character
+ string--;
+
+ *string = '\0'; // and split the string there
+ severalLines = true;
+ }
+
+ _vm->_textMan->printToViewport(_objDescTextXpos, _objDescTextYpos, k13_ColorLightestGray, stringLine);
+ _objDescTextYpos += 7;
+ if (severalLines) {
+ severalLines = false;
+ stringLine = ++string;
+ } else {
+ *stringLine = '\0';
+ }
+ }
+ }
+}
+
+void InventoryMan::drawPanelArrowOrEye(bool pressingEye) {
+ static Box boxArrowOrEye(83, 98, 57, 65); // @ G0033_s_Graphic562_Box_ArrowOrEye
+
+ DisplayMan &dispMan = *_vm->_displayMan;
+ dispMan.blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(pressingEye ? k19_EyeForObjectDescriptionIndice : k18_ArrowForChestContentIndice),
+ boxArrowOrEye, k8_byteWidth, k8_ColorRed, 9);
+}
+
+void InventoryMan::drawPanelObject(Thing thingToDraw, bool pressingEye) {
+ static Box boxObjectDescCircle(105, 136, 53, 79); // @ G0034_s_Graphic562_Box_ObjectDescriptionCircle
+
+ DungeonMan &dunMan = *_vm->_dungeonMan;
+ ObjectMan &objMan = *_vm->_objectMan;
+ DisplayMan &dispMan = *_vm->_displayMan;
+ ChampionMan &champMan = *_vm->_championMan;
+ TextMan &textMan = *_vm->_textMan;
+
+ if (_vm->_pressingEye || _vm->_pressingMouth)
+ closeChest();
+
+ uint16 *rawThingPtr = dunMan.getThingData(thingToDraw);
+ drawPanelObjectDescriptionString("\f"); // form feed
+ ThingType thingType = thingToDraw.getType();
+ if (thingType == k7_ScrollThingType)
+ drawPanelScroll((Scroll *)rawThingPtr);
+ else if (thingType == k9_ContainerThingType)
+ openAndDrawChest(thingToDraw, (Container *)rawThingPtr, pressingEye);
+ else {
+ IconIndice iconIndex = objMan.getIconIndex(thingToDraw);
+ dispMan.blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k20_PanelEmptyIndice),
+ _boxPanel, k72_byteWidth, k8_ColorRed, 73);
+ dispMan.blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k29_ObjectDescCircleIndice),
+ boxObjectDescCircle, k16_byteWidth, k12_ColorDarkestGray, 27);
+
+ Common::String descString;
+ Common::String str;
+ if (iconIndex == kDMIconIndiceJunkChampionBones) {
+ switch (_vm->getGameLanguage()) { // localized
+ case Common::FR_FRA:
+ // Fix original bug dur to a cut&paste error: string was concatenated then overwritten by the name
+ str = Common::String::format("%s %s", objMan._objectNames[iconIndex], champMan._champions[((Junk *)rawThingPtr)->getChargeCount()]._name);
+ break;
+ default: // German and English versions are the same
+ str = Common::String::format("%s %s", champMan._champions[((Junk *)rawThingPtr)->getChargeCount()]._name, objMan._objectNames[iconIndex]);
+ break;
+ }
+
+ descString = str;
+ } else if ((thingType == k8_PotionThingType)
+ && (iconIndex != kDMIconIndicePotionWaterFlask)
+ && (champMan.getSkillLevel((ChampionIndex)_vm->ordinalToIndex(_inventoryChampionOrdinal), kDMSkillPriest) > 1)) {
+ str = ('_' + ((Potion *)rawThingPtr)->getPower() / 40);
+ str += " ";
+ str += objMan._objectNames[iconIndex];
+ descString = str;
+ } else {
+ descString = objMan._objectNames[iconIndex];
+ }
+
+ textMan.printToViewport(134, 68, k13_ColorLightestGray, descString.c_str());
+ drawIconToViewport(iconIndex, 111, 59);
+
+
+ _objDescTextYpos = 87;
+
+ uint16 potentialAttribMask = 0;
+ uint16 actualAttribMask = 0;
+ switch (thingType) {
+ case k5_WeaponThingType: {
+ potentialAttribMask = k0x0008_DescriptionMaskCursed | k0x0002_DescriptionMaskPoisoned | k0x0004_DescriptionMaskBroken;
+ Weapon *weapon = (Weapon *)rawThingPtr;
+ actualAttribMask = (weapon->getCursed() << 3) | (weapon->getPoisoned() << 1) | (weapon->getBroken() << 2);
+ if ((iconIndex >= kDMIconIndiceWeaponTorchUnlit)
+ && (iconIndex <= kDMIconIndiceWeaponTorchLit)
+ && (weapon->getChargeCount() == 0)) {
+
+ switch (_vm->getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY:
+ drawPanelObjectDescriptionString("(BURNT OUT)");
+ break;
+ case Common::DE_DEU:
+ drawPanelObjectDescriptionString("(AUSGEBRANNT)");
+ break;
+ case Common::FR_FRA:
+ drawPanelObjectDescriptionString("(CONSUME)");
+ break;
+ }
+ }
+ break;
+ }
+ case k6_ArmourThingType: {
+ potentialAttribMask = k0x0008_DescriptionMaskCursed | k0x0004_DescriptionMaskBroken;
+ Armour *armour = (Armour *)rawThingPtr;
+ actualAttribMask = (armour->getCursed() << 3) | (armour->getBroken() << 2);
+ break;
+ }
+ case k8_PotionThingType: {
+ potentialAttribMask = k0x0001_DescriptionMaskConsumable;
+ Potion *potion = (Potion *)rawThingPtr;
+ actualAttribMask = _vm->_dungeonMan->_objectInfos[k2_ObjectInfoIndexFirstPotion + potion->getType()].getAllowedSlots();
+ break;
+ }
+ case k10_JunkThingType: {
+ if ((iconIndex >= kDMIconIndiceJunkWater) && (iconIndex <= kDMIconIndiceJunkWaterSkin)) {
+ potentialAttribMask = 0;
+ const char *descStringEN[4] = {"(EMPTY)", "(ALMOST EMPTY)", "(ALMOST FULL)", "(FULL)"};
+ const char *descStringDE[4] = {"(LEER)", "(FAST LEER)", "(FAST VOLL)", "(VOLL)"};
+ const char *descStringFR[4] = {"(VIDE)", "(PRESQUE VIDE)", "(PRESQUE PLEINE)", "(PLEINE)"};
+
+ Junk *junk = (Junk *)rawThingPtr;
+ switch (_vm->getGameLanguage()) { // localized
+ case Common::DE_DEU:
+ descString = descStringDE[junk->getChargeCount()];
+ break;
+ case Common::FR_FRA:
+ descString = descStringFR[junk->getChargeCount()];
+ break;
+ default:
+ descString = descStringEN[junk->getChargeCount()];
+ break;
+ }
+
+ drawPanelObjectDescriptionString(descString.c_str());
+ } else if ((iconIndex >= kDMIconIndiceJunkCompassNorth) && (iconIndex <= kDMIconIndiceJunkCompassWest)) {
+ const static char *directionNameEN[4] = {"NORTH", "EAST", "SOUTH", "WEST"};
+ const static char *directionNameDE[4] = {"NORDEN", "OSTEN", "SUEDEN", "WESTEN"};
+ const static char *directionNameFR[4] = {"AU NORD", "A L'EST", "AU SUD", "A L'OUEST"};
+
+ potentialAttribMask = 0;
+
+ switch (_vm->getGameLanguage()) { // localized
+ case Common::DE_DEU:
+ str = "GRUPPE BLICKT NACH ";
+ str += directionNameDE[iconIndex];
+ break;
+ case Common::FR_FRA:
+ str = "GROUPE FACE ";
+ str += directionNameFR[iconIndex];
+ break;
+ default:
+ str = "PARTY FACING ";
+ str += directionNameEN[iconIndex];
+ break;
+ }
+
+ drawPanelObjectDescriptionString(str.c_str());
+ } else {
+ Junk *junk = (Junk *)rawThingPtr;
+ potentialAttribMask = k0x0001_DescriptionMaskConsumable;
+ actualAttribMask = _vm->_dungeonMan->_objectInfos[k127_ObjectInfoIndexFirstJunk + junk->getType()].getAllowedSlots();
+ }
+ break;
+ }
+ default:
+ break;
+ } // end of switch
+
+ if (potentialAttribMask) {
+ static const char *attribStringEN[4] = {"CONSUMABLE", "POISONED", "BROKEN", "CURSED"};
+ static const char *attribStringDE[4] = {"ESSBAR", "VERGIFTET", "DEFEKT", "VERFLUCHT"};
+ static const char *attribStringFR[4] = {"COMESTIBLE", "EMPOISONNE", "BRISE", "MAUDIT"};
+ const char **attribString = nullptr;
+
+ switch (_vm->getGameLanguage()) { // localized
+ case Common::DE_DEU:
+ attribString = attribStringDE;
+ break;
+ case Common::FR_FRA:
+ attribString = attribStringFR;
+ break;
+ default:
+ attribString = attribStringEN;
+ break;
+ }
+
+ char destString[40];
+ buildObjectAttributeString(potentialAttribMask, actualAttribMask, attribString, destString, "(", ")");
+ drawPanelObjectDescriptionString(destString);
+ }
+
+ uint16 weight = dunMan.getObjectWeight(thingToDraw);
+ switch (_vm->getGameLanguage()) { // localized
+ case Common::DE_DEU:
+ str = "WIEGT " + champMan.getStringFromInteger(weight / 10, false, 3) + ",";
+ break;
+ case Common::FR_FRA:
+ str = "PESE " + champMan.getStringFromInteger(weight / 10, false, 3) + "KG,";
+ break;
+ default:
+ str = "WEIGHS " + champMan.getStringFromInteger(weight / 10, false, 3) + ".";
+ break;
+ }
+
+ weight -= (weight / 10) * 10;
+ str += champMan.getStringFromInteger(weight, false, 1);
+
+ switch (_vm->getGameLanguage()) { // localized
+ case Common::FR_FRA:
+ str += ".";
+ break;
+ default:
+ str += " KG.";
+ break;
+ }
+
+ drawPanelObjectDescriptionString(str.c_str());
+ }
+ drawPanelArrowOrEye(pressingEye);
+}
+
+void InventoryMan::setDungeonViewPalette() {
+ static const int16 palIndexToLightAmmount[6] = {99, 75, 50, 25, 1, 0}; // @ G0040_ai_Graphic562_PaletteIndexToLightAmount
+
+ if (_vm->_dungeonMan->_currMap->_difficulty == 0) {
+ _vm->_displayMan->_dungeonViewPaletteIndex = 0; /* Brightest color palette index */
+ } else {
+ /* Get torch light power from both hands of each champion in the party */
+ int16 counter = 4; /* BUG0_01 Coding error without consequence. The hands of four champions are inspected even if there are less champions in the party. No consequence as the data in unused champions is set to 0 and _vm->_objectMan->f32_getObjectType then returns -1 */
+ Champion *curChampion = _vm->_championMan->_champions;
+ int16 torchesLightPower[8];
+ int16 *curTorchLightPower = torchesLightPower;
+ while (counter--) {
+ uint16 slotIndex = kDMSlotActionHand + 1;
+ while (slotIndex--) {
+ Thing slotThing = curChampion->_slots[slotIndex];
+ if ((_vm->_objectMan->getObjectType(slotThing) >= kDMIconIndiceWeaponTorchUnlit) &&
+ (_vm->_objectMan->getObjectType(slotThing) <= kDMIconIndiceWeaponTorchLit)) {
+ Weapon *curWeapon = (Weapon *)_vm->_dungeonMan->getThingData(slotThing);
+ *curTorchLightPower = curWeapon->getChargeCount();
+ } else {
+ *curTorchLightPower = 0;
+ }
+ curTorchLightPower++;
+ }
+ curChampion++;
+ }
+ /* Sort torch light power values so that the four highest values are in the first four entries in the array L1045_ai_TorchesLightPower in decreasing order. The last four entries contain the smallest values but they are not sorted */
+ curTorchLightPower = torchesLightPower;
+ int16 torchIndex = 0;
+ while (torchIndex != 4) {
+ counter = 7 - torchIndex;
+ int16 *L1041_pi_TorchLightPower = &torchesLightPower[torchIndex + 1];
+ while (counter--) {
+ if (*L1041_pi_TorchLightPower > *curTorchLightPower) {
+ int16 AL1044_ui_TorchLightPower = *L1041_pi_TorchLightPower;
+ *L1041_pi_TorchLightPower = *curTorchLightPower;
+ *curTorchLightPower = AL1044_ui_TorchLightPower;
+ }
+ L1041_pi_TorchLightPower++;
+ }
+ curTorchLightPower++;
+ torchIndex++;
+ }
+ /* Get total light amount provided by the four torches with the highest light power values and by the fifth torch in the array which may be any one of the four torches with the smallest ligh power values */
+ uint16 torchLightAmountMultiplier = 6;
+ torchIndex = 5;
+ int16 totalLightAmount = 0;
+ curTorchLightPower = torchesLightPower;
+ while (torchIndex--) {
+ if (*curTorchLightPower) {
+ totalLightAmount += (_vm->_championMan->_lightPowerToLightAmount[*curTorchLightPower] << torchLightAmountMultiplier) >> 6;
+ torchLightAmountMultiplier = MAX(0, torchLightAmountMultiplier - 1);
+ }
+ curTorchLightPower++;
+ }
+ totalLightAmount += _vm->_championMan->_party._magicalLightAmount;
+ /* Select palette corresponding to the total light amount */
+ const int16 *curLightAmount = palIndexToLightAmmount;
+ int16 paletteIndex;
+ if (totalLightAmount > 0) {
+ paletteIndex = 0; /* Brightest color palette index */
+ while (*curLightAmount++ > totalLightAmount)
+ paletteIndex++;
+ } else {
+ paletteIndex = 5; /* Darkest color palette index */
+ }
+ _vm->_displayMan->_dungeonViewPaletteIndex = paletteIndex;
+ }
+
+ _vm->_displayMan->_refreshDungeonViewPaleteRequested = true;
+}
+
+void InventoryMan::decreaseTorchesLightPower() {
+ bool torchChargeCountChanged = false;
+ int16 championCount = _vm->_championMan->_partyChampionCount;
+ if (_vm->_championMan->_candidateChampionOrdinal)
+ championCount--;
+
+ Champion *curChampion = _vm->_championMan->_champions;
+ while (championCount--) {
+ int16 slotIndex = kDMSlotActionHand + 1;
+ while (slotIndex--) {
+ int16 iconIndex = _vm->_objectMan->getIconIndex(curChampion->_slots[slotIndex]);
+ if ((iconIndex >= kDMIconIndiceWeaponTorchUnlit) && (iconIndex <= kDMIconIndiceWeaponTorchLit)) {
+ Weapon *curWeapon = (Weapon *)_vm->_dungeonMan->getThingData(curChampion->_slots[slotIndex]);
+ if (curWeapon->getChargeCount()) {
+ if (curWeapon->setChargeCount(curWeapon->getChargeCount() - 1) == 0) {
+ curWeapon->setDoNotDiscard(false);
+ }
+ torchChargeCountChanged = true;
+ }
+ }
+ }
+ curChampion++;
+ }
+
+ if (torchChargeCountChanged) {
+ setDungeonViewPalette();
+ _vm->_championMan->drawChangedObjectIcons();
+ }
+}
+
+void InventoryMan::drawChampionSkillsAndStatistics() {
+ static const char *statisticNamesEN[7] = {"L", "STRENGTH", "DEXTERITY", "WISDOM", "VITALITY", "ANTI-MAGIC", "ANTI-FIRE"};
+ static const char *statisticNamesDE[7] = {"L", "STAERKE", "FLINKHEIT", "WEISHEIT", "VITALITAET", "ANTI-MAGIE", "ANTI-FEUER"};
+ static const char *statisticNamesFR[7] = {"L", "FORCE", "DEXTERITE", "SAGESSE", "VITALITE", "ANTI-MAGIE", "ANTI-FEU"};
+
+ const char **statisticNames;
+
+ switch (_vm->getGameLanguage()) { // localized
+ case Common::DE_DEU:
+ statisticNames = statisticNamesDE;
+ break;
+ case Common::FR_FRA:
+ statisticNames = statisticNamesFR;
+ break;
+ default:
+ statisticNames = statisticNamesEN;
+ break;
+ }
+
+ closeChest();
+ uint16 championIndex = _vm->ordinalToIndex(_inventoryChampionOrdinal);
+ Champion *curChampion = &_vm->_championMan->_champions[championIndex];
+ _vm->_displayMan->blitToViewport(_vm->_displayMan->getNativeBitmapOrGraphic(k20_PanelEmptyIndice), _boxPanel, k72_byteWidth, k8_ColorRed, 73);
+ int16 textPosY = 58;
+ for (uint16 idx = kDMSkillFighter; idx <= kDMSkillWizard; idx++) {
+ int16 skillLevel = MIN((uint16)16, _vm->_championMan->getSkillLevel(championIndex, idx | kDMIgnoreTemporaryExperience));
+ if (skillLevel == 1)
+ continue;
+
+ Common::String displayString;
+
+ switch (_vm->getGameLanguage()) { // localized
+ case Common::FR_FRA:
+ // Fix original bug: Due to a copy&paste error, the string was concatenate then overwritten be the last part
+ displayString = Common::String::format("%s %s", _vm->_championMan->_baseSkillName[idx], _skillLevelNames[skillLevel - 2]);
+ break;
+ default: // English and German versions are built the same way
+ displayString = Common::String::format("%s %s", _skillLevelNames[skillLevel - 2], _vm->_championMan->_baseSkillName[idx]);
+ break;
+ }
+ _vm->_textMan->printToViewport(108, textPosY, k13_ColorLightestGray, displayString.c_str());
+ textPosY += 7;
+ }
+ textPosY = 86;
+ for (uint16 idx = kDMStatStrength; idx <= kDMStatAntifire; idx++) {
+ _vm->_textMan->printToViewport(108, textPosY, k13_ColorLightestGray, statisticNames[idx]);
+ int16 statisticCurrentValue = curChampion->_statistics[idx][kDMStatCurrent];
+ uint16 statisticMaximumValue = curChampion->_statistics[idx][kDMStatMaximum];
+ int16 statisticColor;
+ if (statisticCurrentValue < statisticMaximumValue)
+ statisticColor = k8_ColorRed;
+ else if (statisticCurrentValue > statisticMaximumValue)
+ statisticColor = k7_ColorLightGreen;
+ else
+ statisticColor = k13_ColorLightestGray;
+
+ _vm->_textMan->printToViewport(174, textPosY, (Color)statisticColor, _vm->_championMan->getStringFromInteger(statisticCurrentValue, true, 3).c_str());
+ Common::String displayString = "/" + _vm->_championMan->getStringFromInteger(statisticMaximumValue, true, 3);
+ _vm->_textMan->printToViewport(192, textPosY, k13_ColorLightestGray, displayString.c_str());
+ textPosY += 7;
+ }
+}
+
+void InventoryMan::drawStopPressingMouth() {
+ drawPanel();
+ _vm->_displayMan->drawViewport(k0_viewportNotDungeonView);
+ _vm->_eventMan->_hideMousePointerRequestCount = 1;
+ _vm->_eventMan->showMouse();
+ _vm->_eventMan->showMouse();
+ _vm->_eventMan->showMouse();
+}
+
+void InventoryMan::drawStopPressingEye() {
+ drawIconToViewport(kDMIconIndiceEyeNotLooking, 12, 13);
+ drawPanel();
+ _vm->_displayMan->drawViewport(k0_viewportNotDungeonView);
+ Thing leaderHandObject = _vm->_championMan->_leaderHandObject;
+ if (leaderHandObject != Thing::_none)
+ _vm->_objectMan->drawLeaderObjectName(leaderHandObject);
+
+ _vm->_eventMan->showMouse();
+ _vm->_eventMan->showMouse();
+ _vm->_eventMan->showMouse();
+}
+
+void InventoryMan::clickOnMouth() {
+ static int16 foodAmounts[8] = {
+ 500, /* Apple */
+ 600, /* Corn */
+ 650, /* Bread */
+ 820, /* Cheese */
+ 550, /* Screamer Slice */
+ 350, /* Worm round */
+ 990, /* Drumstick / Shank */
+ 1400 /* Dragon steak */
+ };
+
+ if (_vm->_championMan->_leaderEmptyHanded) {
+ if (_panelContent == k0_PanelContentFoodWaterPoisoned)
+ return;
+
+ _vm->_eventMan->_ignoreMouseMovements = true;
+ _vm->_pressingMouth = true;
+ if (!_vm->_eventMan->isMouseButtonDown(k1_LeftMouseButton)) {
+ _vm->_eventMan->_ignoreMouseMovements = false;
+ _vm->_pressingMouth = false;
+ _vm->_stopPressingMouth = false;
+ } else {
+ _vm->_eventMan->showMouse();
+ _vm->_eventMan->_hideMousePointerRequestCount = 1;
+ drawPanelFoodWaterPoisoned();
+ _vm->_displayMan->drawViewport(k0_viewportNotDungeonView);
+ }
+ return;
+ }
+
+ if (_vm->_championMan->_candidateChampionOrdinal)
+ return;
+
+ Thing handThing = _vm->_championMan->_leaderHandObject;
+ if (!getFlag(_vm->_dungeonMan->_objectInfos[_vm->_dungeonMan->getObjectInfoIndex(handThing)]._allowedSlots, k0x0001_ObjectAllowedSlotMouth))
+ return;
+
+ uint16 iconIndex = _vm->_objectMan->getIconIndex(handThing);
+ uint16 handThingType = handThing.getType();
+ uint16 handThingWeight = _vm->_dungeonMan->getObjectWeight(handThing);
+ uint16 championIndex = _vm->ordinalToIndex(_inventoryChampionOrdinal);
+ Champion *curChampion = &_vm->_championMan->_champions[championIndex];
+ Junk *junkData = (Junk *)_vm->_dungeonMan->getThingData(handThing);
+ bool removeObjectFromLeaderHand;
+ if ((iconIndex >= kDMIconIndiceJunkWater) && (iconIndex <= kDMIconIndiceJunkWaterSkin)) {
+ if (!(junkData->getChargeCount()))
+ return;
+
+ curChampion->_water = MIN(curChampion->_water + 800, 2048);
+ junkData->setChargeCount(junkData->getChargeCount() - 1);
+ removeObjectFromLeaderHand = false;
+ } else if (handThingType == k8_PotionThingType)
+ removeObjectFromLeaderHand = false;
+ else {
+ junkData->setNextThing(Thing::_none);
+ removeObjectFromLeaderHand = true;
+ }
+ _vm->_eventMan->showMouse();
+ if (removeObjectFromLeaderHand)
+ _vm->_championMan->getObjectRemovedFromLeaderHand();
+
+ if (handThingType == k8_PotionThingType) {
+ uint16 potionPower = ((Potion *)junkData)->getPower();
+ uint16 counter = ((511 - potionPower) / (32 + (potionPower + 1) / 8)) >> 1;
+ uint16 adjustedPotionPower = (potionPower / 25) + 8; /* Value between 8 and 18 */
+
+ switch (((Potion *)junkData)->getType()) {
+ case k6_PotionTypeRos:
+ adjustStatisticCurrentValue(curChampion, kDMStatDexterity, adjustedPotionPower);
+ break;
+ case k7_PotionTypeKu:
+ adjustStatisticCurrentValue(curChampion, kDMStatStrength, (((Potion *)junkData)->getPower() / 35) + 5); /* Value between 5 and 12 */
+ break;
+ case k8_PotionTypeDane:
+ adjustStatisticCurrentValue(curChampion, kDMStatWisdom, adjustedPotionPower);
+ break;
+ case k9_PotionTypeNeta:
+ adjustStatisticCurrentValue(curChampion, kDMStatVitality, adjustedPotionPower);
+ break;
+ case k10_PotionTypeAntivenin:
+ _vm->_championMan->unpoison(championIndex);
+ break;
+ case k11_PotionTypeMon:
+ curChampion->_currStamina += MIN(curChampion->_maxStamina - curChampion->_currStamina, curChampion->_maxStamina / counter);
+ break;
+ case k12_PotionTypeYa: {
+ adjustedPotionPower += adjustedPotionPower >> 1;
+ if (curChampion->_shieldDefense > 50)
+ adjustedPotionPower >>= 2;
+
+ curChampion->_shieldDefense += adjustedPotionPower;
+ TimelineEvent newEvent;
+ newEvent._type = k72_TMEventTypeChampionShield;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + (adjustedPotionPower * adjustedPotionPower));
+ newEvent._priority = championIndex;
+ newEvent._B._defense = adjustedPotionPower;
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ setFlag(curChampion->_attributes, kDMAttributeStatusBox);
+ }
+ break;
+ case k13_PotionTypeEe: {
+ uint16 mana = MIN(900, (curChampion->_currMana + adjustedPotionPower) + (adjustedPotionPower - 8));
+ if (mana > curChampion->_maxMana)
+ mana -= (mana - MAX(curChampion->_currMana, curChampion->_maxMana)) >> 1;
+
+ curChampion->_currMana = mana;
+ }
+ break;
+ case k14_PotionTypeVi: {
+ uint16 healWoundIterationCount = MAX(1, (((Potion *)junkData)->getPower() / 42));
+ curChampion->_currHealth += curChampion->_maxHealth / counter;
+ int16 wounds = curChampion->_wounds;
+ if (wounds) { /* If the champion is wounded */
+ counter = 10;
+ do {
+ for (uint16 i = 0; i < healWoundIterationCount; i++)
+ curChampion->_wounds &= _vm->getRandomNumber(65536);
+
+ healWoundIterationCount = 1;
+ } while ((wounds == curChampion->_wounds) && --counter); /* Loop until at least one wound is healed or there are no more heal iterations */
+ }
+ setFlag(curChampion->_attributes, kDMAttributeLoad | kDMAttributeWounds);
+ }
+ break;
+ case k15_PotionTypeWaterFlask:
+ curChampion->_water = MIN(curChampion->_water + 1600, 2048);
+ break;
+ default:
+ break;
+ }
+ ((Potion *)junkData)->setType(k20_PotionTypeEmptyFlask);
+ } else if ((iconIndex >= kDMIconIndiceJunkApple) && (iconIndex < kDMIconIndiceJunkIronKey))
+ curChampion->_food = MIN(curChampion->_food + foodAmounts[iconIndex - kDMIconIndiceJunkApple], 2048);
+
+ if (curChampion->_currStamina > curChampion->_maxStamina)
+ curChampion->_currStamina = curChampion->_maxStamina;
+
+ if (curChampion->_currHealth > curChampion->_maxHealth)
+ curChampion->_currHealth = curChampion->_maxHealth;
+
+ if (removeObjectFromLeaderHand) {
+ for (uint16 i = 5; --i; _vm->delay(8)) { /* Animate mouth icon */
+ _vm->_objectMan->drawIconToScreen(kDMIconIndiceMouthOpen + !(i & 0x0001), 56, 46);
+ _vm->_eventMan->discardAllInput();
+ if (_vm->_engineShouldQuit)
+ return;
+ _vm->_displayMan->updateScreen();
+ }
+ } else {
+ _vm->_championMan->drawChangedObjectIcons();
+ _vm->_championMan->_champions[_vm->_championMan->_leaderIndex]._load += _vm->_dungeonMan->getObjectWeight(handThing) - handThingWeight;
+ setFlag(_vm->_championMan->_champions[_vm->_championMan->_leaderIndex]._attributes, kDMAttributeLoad);
+ }
+ _vm->_sound->requestPlay(k08_soundSWALLOW, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, k0_soundModePlayImmediately);
+ setFlag(curChampion->_attributes, kDMAttributeStatistics);
+
+ if (_panelContent == k0_PanelContentFoodWaterPoisoned)
+ setFlag(curChampion->_attributes, kDMAttributePanel);
+
+ _vm->_championMan->drawChampionState((ChampionIndex)championIndex);
+ _vm->_eventMan->hideMouse();
+}
+
+void InventoryMan::adjustStatisticCurrentValue(Champion *champ, uint16 statIndex, int16 valueDelta) {
+ int16 delta;
+ if (valueDelta >= 0) {
+ int16 currentValue = champ->_statistics[statIndex][kDMStatCurrent];
+ if (currentValue > 120) {
+ valueDelta >>= 1;
+ if (currentValue > 150) {
+ valueDelta >>= 1;
+ }
+ valueDelta++;
+ }
+ delta = MIN(valueDelta, (int16)(170 - currentValue));
+ } else { /* BUG0_00 Useless code. The function is always called with valueDelta having a positive value */
+ delta = MAX(valueDelta, int16(champ->_statistics[statIndex][kDMStatMinimum] - champ->_statistics[statIndex][kDMStatCurrent]));
+ }
+ champ->_statistics[statIndex][kDMStatCurrent] += delta;
+}
+
+void InventoryMan::clickOnEye() {
+ _vm->_eventMan->_ignoreMouseMovements = true;
+ _vm->_pressingEye = true;
+ if (!_vm->_eventMan->isMouseButtonDown(k1_LeftMouseButton)) {
+ _vm->_eventMan->_ignoreMouseMovements = false;
+ _vm->_pressingEye = false;
+ _vm->_stopPressingEye = false;
+ return;
+ }
+ _vm->_eventMan->discardAllInput();
+ _vm->_eventMan->hideMouse();
+ _vm->_eventMan->hideMouse();
+ _vm->_eventMan->hideMouse();
+ _vm->delay(8);
+ drawIconToViewport(kDMIconIndiceEyeLooking, 12, 13);
+ if (_vm->_championMan->_leaderEmptyHanded)
+ drawChampionSkillsAndStatistics();
+ else {
+ _vm->_objectMan->clearLeaderObjectName();
+ drawPanelObject(_vm->_championMan->_leaderHandObject, true);
+ }
+ _vm->_displayMan->drawViewport(k0_viewportNotDungeonView);
+}
+
+}
diff --git a/engines/dm/inventory.h b/engines/dm/inventory.h
new file mode 100644
index 0000000000..c4685575c2
--- /dev/null
+++ b/engines/dm/inventory.h
@@ -0,0 +1,99 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_INVENTORY_H
+#define DM_INVENTORY_H
+
+#include "dm/dm.h"
+#include "dm/gfx.h"
+#include "dm/champion.h"
+#include "dm/dungeonman.h"
+
+namespace DM {
+
+#define k0x0001_DescriptionMaskConsumable 0x0001 // @ MASK0x0001_DESCRIPTION_CONSUMABLE
+#define k0x0002_DescriptionMaskPoisoned 0x0002 // @ MASK0x0002_DESCRIPTION_POISONED
+#define k0x0004_DescriptionMaskBroken 0x0004 // @ MASK0x0004_DESCRIPTION_BROKEN
+#define k0x0008_DescriptionMaskCursed 0x0008 // @ MASK0x0008_DESCRIPTION_CURSED
+
+#define k69_ChampionStatusBoxSpacing 69 // @ C69_CHAMPION_STATUS_BOX_SPACING
+#define k38_SlotBoxChestFirstSlot 38 // @ C38_SLOT_BOX_CHEST_FIRST_SLOT
+
+enum PanelContent {
+ k0_PanelContentFoodWaterPoisoned = 0, // @ C00_PANEL_FOOD_WATER_POISONED
+ k2_PanelContentScroll = 2, // @ C02_PANEL_SCROLL
+ k4_PanelContentChest = 4, // @ C04_PANEL_CHEST
+ k5_PanelContentResurrectReincarnate = 5 // @ C05_PANEL_RESURRECT_REINCARNATE
+};
+
+class InventoryMan {
+ DMEngine *_vm;
+
+ void initConstants();
+
+public:
+ explicit InventoryMan(DMEngine *vm);
+
+ int16 _inventoryChampionOrdinal; // @ G0423_i_InventoryChampionOrdinal
+ PanelContent _panelContent; // @ G0424_i_PanelContent
+ Thing _chestSlots[8]; // @ G0425_aT_ChestSlots
+ Thing _openChest; // @ G0426_T_OpenChest
+ int16 _objDescTextXpos; // @ G0421_i_ObjectDescriptionTextX
+ int16 _objDescTextYpos; // @ G0422_i_ObjectDescriptionTextY
+ Box _boxPanel;
+ const char *_skillLevelNames[15];
+
+ void toggleInventory(ChampionIndex championIndex); // @ F0355_INVENTORY_Toggle_CPSE
+ void drawStatusBoxPortrait(ChampionIndex championIndex); // @ F0354_INVENTORY_DrawStatusBoxPortrait
+ void drawPanelHorizontalBar(int16 x, int16 y, int16 pixelWidth, Color color); // @ F0343_INVENTORY_DrawPanel_HorizontalBar
+ void drawPanelFoodOrWaterBar(int16 amount, int16 y, Color color); // @ F0344_INVENTORY_DrawPanel_FoodOrWaterBar
+ void drawPanelFoodWaterPoisoned(); // @ F0345_INVENTORY_DrawPanel_FoodWaterPoisoned
+ void drawPanelResurrectReincarnate(); // @ F0346_INVENTORY_DrawPanel_ResurrectReincarnate
+ void drawPanel(); // @ F0347_INVENTORY_DrawPanel
+ void closeChest(); // @ F0334_INVENTORY_CloseChest
+ void drawPanelScrollTextLine(int16 yPos, char *text); // @ F0340_INVENTORY_DrawPanel_ScrollTextLine
+ void drawPanelScroll(Scroll *scoll); // @ F0341_INVENTORY_DrawPanel_Scroll
+ void openAndDrawChest(Thing thingToOpen, Container *chest, bool isPressingEye); // @ F0333_INVENTORY_OpenAndDrawChest
+ void drawIconToViewport(IconIndice iconIndex, int16 xPos, int16 yPos); // @ F0332_INVENTORY_DrawIconToViewport
+ void buildObjectAttributeString(int16 potentialAttribMask, int16 actualAttribMask, const char ** attribStrings,
+ char *destString, const char *prefixString, const char *suffixString); // @ F0336_INVENTORY_DrawPanel_BuildObjectAttributesString
+ void drawPanelObjectDescriptionString(const char *descString); // @ F0335_INVENTORY_DrawPanel_ObjectDescriptionString
+ void drawPanelArrowOrEye(bool pressingEye); // @ F0339_INVENTORY_DrawPanel_ArrowOrEye
+ void drawPanelObject(Thing thingToDraw, bool pressingEye); // @ F0342_INVENTORY_DrawPanel_Object
+ void setDungeonViewPalette(); // @ F0337_INVENTORY_SetDungeonViewPalette
+ void decreaseTorchesLightPower(); // @ F0338_INVENTORY_DecreaseTorchesLightPower_CPSE
+ void drawChampionSkillsAndStatistics(); // @ F0351_INVENTORY_DrawChampionSkillsAndStatistics
+ void drawStopPressingMouth(); // @ F0350_INVENTORY_DrawStopPressingMouth
+ void drawStopPressingEye();// @ F0353_INVENTORY_DrawStopPressingEye
+ void clickOnMouth(); // @ F0349_INVENTORY_ProcessCommand70_ClickOnMouth
+ void adjustStatisticCurrentValue(Champion *champ, uint16 statIndex, int16 valueDelta); // @ F0348_INVENTORY_AdjustStatisticCurrentValue
+ void clickOnEye(); // @ F0352_INVENTORY_ProcessCommand71_ClickOnEye
+};
+
+}
+
+#endif
diff --git a/engines/dm/loadsave.cpp b/engines/dm/loadsave.cpp
new file mode 100644
index 0000000000..5feb484523
--- /dev/null
+++ b/engines/dm/loadsave.cpp
@@ -0,0 +1,448 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+#include "common/system.h"
+#include "common/savefile.h"
+#include "common/translation.h"
+#include "graphics/thumbnail.h"
+#include "gui/saveload.h"
+
+#include "dm/dm.h"
+#include "dm/dungeonman.h"
+#include "dm/timeline.h"
+#include "dm/group.h"
+#include "dm/champion.h"
+#include "dm/menus.h"
+#include "dm/eventman.h"
+#include "dm/projexpl.h"
+#include "dm/dialog.h"
+
+namespace DM {
+
+LoadgameResponse DMEngine::loadgame(int16 slot) {
+ if (slot == -1 && _newGameFl == k0_modeLoadSavedGame)
+ return kM1_LoadgameFailure;
+
+ bool fadePalette = true;
+ Common::String fileName;
+ Common::SaveFileManager *saveFileManager = nullptr;
+ Common::InSaveFile *file = nullptr;
+
+ struct {
+ SaveTarget _saveTarget;
+ int32 _saveVersion;
+ OriginalSaveFormat _saveFormat;
+ OriginalSavePlatform _savePlatform;
+
+ int32 _gameId;
+ uint16 _dungeonId;
+ } dmSaveHeader;
+
+ if (!_newGameFl) {
+ fileName = getSavefileName(slot);
+ saveFileManager = _system->getSavefileManager();
+ file = saveFileManager->openForLoading(fileName);
+ }
+
+ if (_newGameFl) {
+ //L1366_B_FadePalette = !F0428_DIALOG_RequireGameDiskInDrive_NoDialogDrawn(C0_DO_NOT_FORCE_DIALOG_DM_CSB, true);
+ _restartGameAllowed = false;
+ _championMan->_partyChampionCount = 0;
+ _championMan->_leaderHandObject = Thing::_none;
+ _gameId = ((int32)getRandomNumber(65536)) * getRandomNumber(65536);
+ } else {
+ SaveGameHeader header;
+ readSaveGameHeader(file, &header);
+
+ warning("MISSING CODE: missing check for matching format and platform in save in f435_loadgame");
+
+ dmSaveHeader._saveTarget = (SaveTarget)file->readSint32BE();
+ dmSaveHeader._saveVersion = file->readSint32BE();
+ dmSaveHeader._saveFormat = (OriginalSaveFormat)file->readSint32BE();
+ dmSaveHeader._savePlatform = (OriginalSavePlatform)file->readSint32BE();
+ dmSaveHeader._gameId = file->readSint32BE();
+ dmSaveHeader._dungeonId = file->readUint16BE();
+
+ _gameId = dmSaveHeader._gameId;
+
+ _gameTime = file->readSint32BE();
+ // G0349_ul_LastRandomNumber = L1371_s_GlobalData.LastRandomNumber;
+ _championMan->_partyChampionCount = file->readUint16BE();
+ _dungeonMan->_partyMapX = file->readSint16BE();
+ _dungeonMan->_partyMapY = file->readSint16BE();
+ _dungeonMan->_partyDir = (Direction)file->readUint16BE();
+ _dungeonMan->_partyMapIndex = file->readByte();
+ _championMan->_leaderIndex = (ChampionIndex)file->readSint16BE();
+ _championMan->_magicCasterChampionIndex = (ChampionIndex)file->readSint16BE();
+ _timeline->_eventCount = file->readUint16BE();
+ _timeline->_firstUnusedEventIndex = file->readUint16BE();
+ _timeline->_eventMaxCount = file->readUint16BE();
+ _groupMan->_currActiveGroupCount = file->readUint16BE();
+ _projexpl->_lastCreatureAttackTime = file->readSint32BE();
+ _projexpl->_lastPartyMovementTime = file->readSint32BE();
+ _disabledMovementTicks = file->readSint16BE();
+ _projectileDisableMovementTicks = file->readSint16BE();
+ _lastProjectileDisabledMovementDirection = file->readSint16BE();
+ _championMan->_leaderHandObject = Thing(file->readUint16BE());
+ _groupMan->_maxActiveGroupCount = file->readUint16BE();
+ if (!_restartGameRequest) {
+ _timeline->initTimeline();
+ _groupMan->initActiveGroups();
+ }
+
+ _groupMan->loadActiveGroupPart(file);
+ _championMan->loadPartyPart2(file);
+ _timeline->loadEventsPart(file);
+ _timeline->loadTimelinePart(file);
+
+ // read sentinel
+ uint32 sentinel = file->readUint32BE();
+ assert(sentinel == 0x6f85e3d3);
+ }
+
+ _dungeonMan->loadDungeonFile(file);
+ delete file;
+
+ if (_newGameFl) {
+ _timeline->initTimeline();
+ _groupMan->initActiveGroups();
+
+ if (fadePalette) {
+ _displayMan->startEndFadeToPalette(_displayMan->_blankBuffer);
+ delay(1);
+ _displayMan->fillScreen(k0_ColorBlack);
+ _displayMan->startEndFadeToPalette(_displayMan->_paletteTopAndBottomScreen);
+ }
+ } else {
+ _dungeonId = dmSaveHeader._dungeonId;
+
+ _restartGameAllowed = true;
+
+ switch (getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY:
+ _dialog->dialogDraw(nullptr, "LOADING GAME . . .", nullptr, nullptr, nullptr, nullptr, true, true, true);
+ break;
+ case Common::DE_DEU:
+ _dialog->dialogDraw(nullptr, "SPIEL WIRD GELADEN . . .", nullptr, nullptr, nullptr, nullptr, true, true, true);
+ break;
+ case Common::FR_FRA:
+ _dialog->dialogDraw(nullptr, "CHARGEMENT DU JEU . . .", nullptr, nullptr, nullptr, nullptr, true, true, true);
+ break;
+ }
+ }
+ _championMan->_partyDead = false;
+
+ return k1_LoadgameSuccess;
+}
+
+
+void DMEngine::saveGame() {
+ _menuMan->drawDisabledMenu();
+ _eventMan->showMouse();
+
+ switch (getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY:
+ _dialog->dialogDraw(nullptr, nullptr, "SAVE AND PLAY", "SAVE AND QUIT", "CANCEL", "LOAD", false, false, false);
+ break;
+ case Common::DE_DEU:
+ _dialog->dialogDraw(nullptr, nullptr, "SICHERN/SPIEL", "SICHERN/ENDEN", "WIDERRUFEN", "LOAD", false, false, false);
+ break;
+ case Common::FR_FRA:
+ _dialog->dialogDraw(nullptr, nullptr, "GARDER/JOUER", "GARDER/SORTIR", "ANNULLER", "LOAD", false, false, false);
+ break;
+ }
+
+ enum SaveAndPlayChoice {
+ kSaveAndPlay = 1,
+ kSaveAndQuit = 2,
+ kCancel = 3,
+ kLoad = 4
+ };
+
+ SaveAndPlayChoice saveAndPlayChoice = (SaveAndPlayChoice)_dialog->getChoice(4, k0_DIALOG_SET_VIEWPORT, 0, k0_DIALOG_CHOICE_NONE);
+
+ if (saveAndPlayChoice == kLoad) {
+ GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
+ int loadSlot = dialog->runModalWithCurrentTarget();
+ if (loadSlot >= 0) {
+ _loadSaveSlotAtRuntime = loadSlot;
+ return;
+ }
+
+ saveAndPlayChoice = kCancel;
+ }
+
+ if (saveAndPlayChoice == kSaveAndQuit || saveAndPlayChoice == kSaveAndPlay) {
+ GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
+ int16 saveSlot = dialog->runModalWithCurrentTarget();
+ Common::String saveDescription = dialog->getResultString();
+ if (saveDescription.empty())
+ saveDescription = "Nice save ^^";
+ delete dialog;
+
+ if (saveSlot >= 0) {
+ switch (getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY:
+ _dialog->dialogDraw(nullptr, "SAVING GAME . . .", nullptr, nullptr, nullptr, nullptr, false, false, false);
+ break;
+ case Common::DE_DEU:
+ _dialog->dialogDraw(nullptr, "SPIEL WIRD GESICHERT . . .", nullptr, nullptr, nullptr, nullptr, false, false, false);
+ break;
+ case Common::FR_FRA:
+ _dialog->dialogDraw(nullptr, "UN MOMENT A SAUVEGARDER DU JEU...", nullptr, nullptr, nullptr, nullptr, false, false, false);
+ break;
+ }
+
+ uint16 champHandObjWeight = 0;
+ if (!_championMan->_leaderEmptyHanded) {
+ champHandObjWeight = _dungeonMan->getObjectWeight(_championMan->_leaderHandObject);
+ _championMan->_champions[_championMan->_leaderIndex]._load -= champHandObjWeight;
+ }
+
+ if (!writeCompleteSaveFile(saveSlot, saveDescription, saveAndPlayChoice)) {
+ _dialog->dialogDraw(nullptr, "Unable to open file for saving", "OK", nullptr, nullptr, nullptr, false, false, false);
+ _dialog->getChoice(1, k0_DIALOG_SET_VIEWPORT, 0, k0_DIALOG_CHOICE_NONE);
+ }
+
+ if (!_championMan->_leaderEmptyHanded) {
+ _championMan->_champions[_championMan->_leaderIndex]._load += champHandObjWeight;
+ }
+ } else
+ saveAndPlayChoice = kCancel;
+ }
+
+
+ if (saveAndPlayChoice == kSaveAndQuit) {
+ _eventMan->hideMouse();
+ endGame(false);
+ }
+
+ _restartGameAllowed = true;
+ _menuMan->drawEnabledMenus();
+ _eventMan->hideMouse();
+}
+
+Common::String DMEngine::getSavefileName(uint16 slot) {
+ return Common::String::format("%s.%03u", _targetName.c_str(), slot);
+}
+
+#define SAVEGAME_ID MKTAG('D', 'M', '2', '1')
+#define SAVEGAME_VERSION 1
+
+void DMEngine::writeSaveGameHeader(Common::OutSaveFile *out, const Common::String& saveName) {
+ out->writeUint32BE(SAVEGAME_ID);
+
+ // Write version
+ out->writeByte(SAVEGAME_VERSION);
+
+ // Write savegame name
+ out->writeString(saveName);
+ out->writeByte(0);
+
+ // Save the game thumbnail
+ if (_saveThumbnail)
+ out->write(_saveThumbnail->getData(), _saveThumbnail->size());
+ else
+ Graphics::saveThumbnail(*out);
+
+ // Creation date/time
+ TimeDate curTime;
+ _system->getTimeAndDate(curTime);
+
+ uint32 saveDate = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF);
+ uint16 saveTime = ((curTime.tm_hour & 0xFF) << 8) | ((curTime.tm_min) & 0xFF);
+ uint32 playTime = getTotalPlayTime() / 1000;
+
+ out->writeUint32BE(saveDate);
+ out->writeUint16BE(saveTime);
+ out->writeUint32BE(playTime);
+}
+
+bool DMEngine::writeCompleteSaveFile(int16 saveSlot, Common::String& saveDescription, int16 saveAndPlayChoice) {
+ Common::String savefileName = getSavefileName(saveSlot);
+ Common::SaveFileManager *saveFileManager = _system->getSavefileManager();
+ Common::OutSaveFile *file = saveFileManager->openForSaving(savefileName);
+
+ if (!file)
+ return false;
+
+ writeSaveGameHeader(file, saveDescription);
+
+ file->writeSint32BE(_gameVersion->_saveTargetToWrite);
+ file->writeSint32BE(1); // save version
+ file->writeSint32BE(_gameVersion->_origSaveFormatToWrite);
+ file->writeSint32BE(_gameVersion->_origPlatformToWrite);
+ file->writeSint32BE(_gameId);
+ file->writeUint16BE(_dungeonId);
+
+ // write C0_SAVE_PART_GLOBAL_DATA part
+ file->writeSint32BE(_gameTime);
+ //L1348_s_GlobalData.LastRandomNumber = G0349_ul_LastRandomNumber;
+ file->writeUint16BE(_championMan->_partyChampionCount);
+ file->writeSint16BE(_dungeonMan->_partyMapX);
+ file->writeSint16BE(_dungeonMan->_partyMapY);
+ file->writeUint16BE(_dungeonMan->_partyDir);
+ file->writeByte(_dungeonMan->_partyMapIndex);
+ file->writeSint16BE(_championMan->_leaderIndex);
+ file->writeSint16BE(_championMan->_magicCasterChampionIndex);
+ file->writeUint16BE(_timeline->_eventCount);
+ file->writeUint16BE(_timeline->_firstUnusedEventIndex);
+ file->writeUint16BE(_timeline->_eventMaxCount);
+ file->writeUint16BE(_groupMan->_currActiveGroupCount);
+ file->writeSint32BE(_projexpl->_lastCreatureAttackTime);
+ file->writeSint32BE(_projexpl->_lastPartyMovementTime);
+ file->writeSint16BE(_disabledMovementTicks);
+ file->writeSint16BE(_projectileDisableMovementTicks);
+ file->writeSint16BE(_lastProjectileDisabledMovementDirection);
+ file->writeUint16BE(_championMan->_leaderHandObject.toUint16());
+ file->writeUint16BE(_groupMan->_maxActiveGroupCount);
+
+ // write C1_SAVE_PART_ACTIVE_GROUP part
+ _groupMan->saveActiveGroupPart(file);
+ // write C2_SAVE_PART_PARTY part
+ _championMan->savePartyPart2(file);
+ // write C3_SAVE_PART_EVENTS part
+ _timeline->saveEventsPart(file);
+ // write C4_SAVE_PART_TIMELINE part
+ _timeline->saveTimelinePart(file);
+
+ // write sentinel
+ file->writeUint32BE(0x6f85e3d3);
+
+ // save _g278_dungeonFileHeader
+ DungeonFileHeader &header = _dungeonMan->_dungeonFileHeader;
+ file->writeUint16BE(header._ornamentRandomSeed);
+ file->writeUint16BE(header._rawMapDataSize);
+ file->writeByte(header._mapCount);
+ file->writeByte(0); // to match the structure of dungeon.dat, will be discarded
+ file->writeUint16BE(header._textDataWordCount);
+ file->writeUint16BE(header._partyStartLocation);
+ file->writeUint16BE(header._squareFirstThingCount);
+ for (uint16 i = 0; i < 16; ++i)
+ file->writeUint16BE(header._thingCounts[i]);
+
+ // save _g277_dungeonMaps
+ for (uint16 i = 0; i < _dungeonMan->_dungeonFileHeader._mapCount; ++i) {
+ Map &map = _dungeonMan->_dungeonMaps[i];
+
+ file->writeUint16BE(map._rawDunDataOffset);
+ file->writeUint32BE(0); // to match the structure of dungeon.dat, will be discarded
+ file->writeByte(map._offsetMapX);
+ file->writeByte(map._offsetMapY);
+
+ uint16 tmp;
+ tmp = ((map._height & 0x1F) << 11) | ((map._width & 0x1F) << 6) | (map._level & 0x3F);
+ file->writeUint16BE(tmp);
+
+ tmp = ((map._randFloorOrnCount & 0xF) << 12) | ((map._floorOrnCount & 0xF) << 8)
+ | ((map._randWallOrnCount & 0xF) << 4) | (map._wallOrnCount & 0xF);
+ file->writeUint16BE(tmp);
+
+ tmp = ((map._difficulty & 0xF) << 12) | ((map._creatureTypeCount & 0xF) << 4) | (map._doorOrnCount & 0xF);
+ file->writeUint16BE(tmp);
+
+ tmp = ((map._doorSet1 & 0xF) << 12) | ((map._doorSet0 & 0xF) << 8)
+ | ((map._wallSet & 0xF) << 4) | (map._floorSet & 0xF);
+ file->writeUint16BE(tmp);
+ }
+
+ // save _g280_dungeonColumnsCumulativeSquareThingCount
+ for (uint16 i = 0; i < _dungeonMan->_dungeonColumCount; ++i)
+ file->writeUint16BE(_dungeonMan->_dungeonColumnsCumulativeSquareThingCount[i]);
+
+ // save _g283_squareFirstThings
+ for (uint16 i = 0; i < _dungeonMan->_dungeonFileHeader._squareFirstThingCount; ++i)
+ file->writeUint16BE(_dungeonMan->_squareFirstThings[i].toUint16());
+
+ // save _g260_dungeonTextData
+ for (uint16 i = 0; i < _dungeonMan->_dungeonFileHeader._textDataWordCount; ++i)
+ file->writeUint16BE(_dungeonMan->_dungeonTextData[i]);
+
+ // save _g284_thingData
+ for (uint16 thingIndex = 0; thingIndex < 16; ++thingIndex)
+ for (uint16 i = 0; i < _dungeonMan->_thingDataWordCount[thingIndex] * _dungeonMan->_dungeonFileHeader._thingCounts[thingIndex]; ++i)
+ file->writeUint16BE(_dungeonMan->_thingData[thingIndex][i]);
+
+ // save _g276_dungeonRawMapData
+ for (uint32 i = 0; i < _dungeonMan->_dungeonFileHeader._rawMapDataSize; ++i)
+ file->writeByte(_dungeonMan->_dungeonRawMapData[i]);
+
+ file->flush();
+ file->finalize();
+ delete file;
+
+ return true;
+}
+
+bool readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader *header) {
+ uint32 id = in->readUint32BE();
+
+ // Check if it's a valid ScummVM savegame
+ if (id != SAVEGAME_ID)
+ return false;
+
+ // Read in the version
+ header->_version = in->readByte();
+
+ // Check that the save version isn't newer than this binary
+ if (header->_version > SAVEGAME_VERSION)
+ return false;
+
+ // Read in the save name
+ Common::String saveName;
+ char ch;
+ while ((ch = (char)in->readByte()) != '\0')
+ saveName += ch;
+ header->_descr.setDescription(saveName);
+
+ // Get the thumbnail
+ header->_descr.setThumbnail(Graphics::loadThumbnail(*in));
+
+ uint32 saveDate = in->readUint32BE();
+ uint16 saveTime = in->readUint16BE();
+ uint32 playTime = in->readUint32BE();
+
+ int day = (saveDate >> 24) & 0xFF;
+ int month = (saveDate >> 16) & 0xFF;
+ int year = saveDate & 0xFFFF;
+ header->_descr.setSaveDate(year, month, day);
+
+ int hour = (saveTime >> 8) & 0xFF;
+ int minutes = saveTime & 0xFF;
+ header->_descr.setSaveTime(hour, minutes);
+
+ header->_descr.setPlayTime(playTime * 1000);
+ if (g_engine)
+ g_engine->setTotalPlayTime(playTime * 1000);
+
+ return true;
+}
+
+}
+
diff --git a/engines/dm/loadsave.h b/engines/dm/loadsave.h
new file mode 100644
index 0000000000..ca8f4d6369
--- /dev/null
+++ b/engines/dm/loadsave.h
@@ -0,0 +1,44 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_LOADSAVE_H
+#define DM_LOADSAVE_H
+
+#include "dm/dm.h"
+
+namespace DM {
+
+class LoadsaveMan {
+ DMEngine *_vm;
+public:
+ explicit LoadsaveMan(DMEngine *vm);
+
+};
+
+}
+
+#endif
diff --git a/engines/dm/lzw.cpp b/engines/dm/lzw.cpp
new file mode 100644
index 0000000000..dc5055f687
--- /dev/null
+++ b/engines/dm/lzw.cpp
@@ -0,0 +1,187 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "common/memstream.h"
+
+#include "dm/lzw.h"
+
+namespace DM {
+
+LZWdecompressor::LZWdecompressor() {
+ _repetitionEnabled = false;
+ _codeBitCount = 0;
+ _currentMaximumCode = 0;
+ _absoluteMaximumCode = 4096;
+ for (int i = 0; i < 12; ++i)
+ _inputBuffer[i] = 0;
+ _dictNextAvailableCode = 0;
+ _dictFlushed = false;
+
+ byte leastSignificantBitmasks[9] = {0x00,0x01,0x03,0x07,0x0F,0x1F,0x3F,0x7F,0xFF};
+ for (uint16 i = 0; i < 9; ++i)
+ _leastSignificantBitmasks[i] = leastSignificantBitmasks[i];
+ _inputBufferBitIndex = 0;
+ _inputBufferBitCount = 0;
+ _charToRepeat = 0;
+
+ _tempBuffer = new byte[5004];
+ _prefixCode = new int16[5003];
+ _appendCharacter = new byte[5226];
+}
+
+LZWdecompressor::~LZWdecompressor() {
+ delete[] _appendCharacter;
+ delete[] _prefixCode;
+ delete[] _tempBuffer;
+}
+
+int16 LZWdecompressor::getNextInputCode(Common::MemoryReadStream &inputStream, int32 *inputByteCount) {
+ byte *inputBuffer = _inputBuffer;
+ if (_dictFlushed || (_inputBufferBitIndex >= _inputBufferBitCount) || (_dictNextAvailableCode > _currentMaximumCode)) {
+ if (_dictNextAvailableCode > _currentMaximumCode) {
+ _codeBitCount++;
+ if (_codeBitCount == 12) {
+ _currentMaximumCode = _absoluteMaximumCode;
+ } else {
+ _currentMaximumCode = (1 << _codeBitCount) - 1;
+ }
+ }
+ if (_dictFlushed) {
+ _currentMaximumCode = (1 << (_codeBitCount = 9)) - 1;
+ _dictFlushed = false;
+ }
+ if (*inputByteCount > _codeBitCount) {
+ _inputBufferBitCount = _codeBitCount;
+ } else {
+ _inputBufferBitCount = *inputByteCount;
+ }
+ if (_inputBufferBitCount > 0) {
+ inputStream.read(_inputBuffer, _inputBufferBitCount);
+ *inputByteCount -= _inputBufferBitCount;
+ } else {
+ return -1;
+ }
+ _inputBufferBitIndex = 0;
+ _inputBufferBitCount = (_inputBufferBitCount << 3) - (_codeBitCount - 1);
+ }
+ int16 bitIndex = _inputBufferBitIndex;
+ int16 requiredInputBitCount = _codeBitCount;
+ inputBuffer += bitIndex >> 3; /* Address of byte in input buffer containing current bit */
+ bitIndex &= 0x0007; /* Bit index of the current bit in the byte */
+ int16 nextInputCode = *inputBuffer++ >> bitIndex; /* Get the first bits of the next input code from the input buffer byte */
+ requiredInputBitCount -= 8 - bitIndex; /* Remaining number of bits to get for a complete input code */
+ bitIndex = 8 - bitIndex;
+ if (requiredInputBitCount >= 8) {
+ nextInputCode |= *inputBuffer++ << bitIndex;
+ bitIndex += 8;
+ requiredInputBitCount -= 8;
+ }
+ nextInputCode |= (*inputBuffer & _leastSignificantBitmasks[requiredInputBitCount]) << bitIndex;
+ _inputBufferBitIndex += _codeBitCount;
+ return nextInputCode;
+}
+
+void LZWdecompressor::outputCharacter(byte character, byte **out) {
+ byte *output = *out;
+
+ if (false == _repetitionEnabled) {
+ if (character == 0x90)
+ _repetitionEnabled = true;
+ else
+ *output++ = _charToRepeat = character;
+ } else {
+ if (character) { /* If character following 0x90 is not 0x00 then it is the repeat count */
+ while (--character)
+ *output++ = _charToRepeat;
+ } else /* else output a 0x90 character */
+ *output++ = 0x90;
+
+ _repetitionEnabled = false;
+ }
+
+ *out = output;
+ return;
+}
+
+int32 LZWdecompressor::decompress(Common::MemoryReadStream &inStream, int32 inputByteCount, byte *out) {
+ byte *reversedDecodedStringStart;
+ byte *reversedDecodedStringEnd = reversedDecodedStringStart = _tempBuffer;
+ byte *originalOut = out;
+ _repetitionEnabled = false;
+ _codeBitCount = 9;
+ _dictFlushed = false;
+ _currentMaximumCode = (1 << (_codeBitCount = 9)) - 1;
+ for (int16 code = 255; code >= 0; code--) {
+ _prefixCode[code] = 0;
+ _appendCharacter[code] = code;
+ }
+ _dictNextAvailableCode = 257;
+ int16 oldCode;
+ int16 character = oldCode = getNextInputCode(inStream, &inputByteCount);
+ if (oldCode == -1) {
+ return -1L;
+ }
+ outputCharacter(character, &out);
+ int16 code;
+ while ((code = getNextInputCode(inStream, &inputByteCount)) > -1) {
+ if (code == 256) { /* This code is used to flush the dictionary */
+ for (int i = 0; i < 256; ++i)
+ _prefixCode[i] = 0;
+ _dictFlushed = true;
+ _dictNextAvailableCode = 256;
+ if ((code = getNextInputCode(inStream, &inputByteCount)) == -1) {
+ break;
+ }
+ }
+ /* This code checks for the special STRING+CHARACTER+STRING+CHARACTER+STRING case which generates an undefined code.
+ It handles it by decoding the last code, adding a single character to the end of the decoded string */
+ int16 newCode = code;
+ if (code >= _dictNextAvailableCode) { /* If code is not defined yet */
+ *reversedDecodedStringEnd++ = character;
+ code = oldCode;
+ }
+ /* Use the string table to decode the string corresponding to the code and store the string in the temporary buffer */
+ while (code >= 256) {
+ *reversedDecodedStringEnd++ = _appendCharacter[code];
+ code = _prefixCode[code];
+ }
+ *reversedDecodedStringEnd++ = (character = _appendCharacter[code]);
+ /* Output the decoded string in reverse order */
+ do {
+ outputCharacter(*(--reversedDecodedStringEnd), &out);
+ } while (reversedDecodedStringEnd > reversedDecodedStringStart);
+ /* If possible, add a new code to the string table */
+ if ((code = _dictNextAvailableCode) < _absoluteMaximumCode) {
+ _prefixCode[code] = oldCode;
+ _appendCharacter[code] = character;
+ _dictNextAvailableCode = code + 1;
+ }
+ oldCode = newCode;
+ }
+ return out - originalOut; /* Byte count of decompressed data */
+}
+}
diff --git a/engines/dm/lzw.h b/engines/dm/lzw.h
new file mode 100644
index 0000000000..313b74b6d8
--- /dev/null
+++ b/engines/dm/lzw.h
@@ -0,0 +1,69 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_LZW_H
+#define DM_LZW_H
+
+#include "common/file.h"
+
+#include "dm/dm.h"
+
+namespace Common {
+ class MemoryReadStream;
+}
+
+namespace DM {
+class LZWdecompressor {
+ bool _repetitionEnabled;
+ int16 _codeBitCount;
+ int16 _currentMaximumCode;
+ int16 _absoluteMaximumCode;
+ byte _inputBuffer[12];
+ int16 _dictNextAvailableCode;
+ bool _dictFlushed;
+
+ byte _leastSignificantBitmasks[9];
+ int16 _inputBufferBitIndex;
+ int16 _inputBufferBitCount;
+ int16 _charToRepeat;
+
+ byte *_tempBuffer;
+ int16 *_prefixCode;
+ byte *_appendCharacter;
+
+ int16 getNextInputCode(Common::MemoryReadStream &stream, int32 *inputByteCount);
+ void outputCharacter(byte character, byte **out);
+ void operator=(const LZWdecompressor&); // deleted
+public:
+ LZWdecompressor();
+ ~LZWdecompressor();
+ int32 decompress(Common::MemoryReadStream &inputStream, int32 inputByteCount, byte *out);
+};
+
+}
+
+#endif
diff --git a/engines/dm/menus.cpp b/engines/dm/menus.cpp
new file mode 100644
index 0000000000..e3bc0880b1
--- /dev/null
+++ b/engines/dm/menus.cpp
@@ -0,0 +1,1787 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "dm/menus.h"
+#include "dm/gfx.h"
+#include "dm/champion.h"
+#include "dm/dungeonman.h"
+#include "dm/objectman.h"
+#include "dm/inventory.h"
+#include "dm/text.h"
+#include "dm/eventman.h"
+#include "dm/timeline.h"
+#include "dm/movesens.h"
+#include "dm/group.h"
+#include "dm/projexpl.h"
+#include "dm/sounds.h"
+
+
+namespace DM {
+
+void MenuMan::initConstants() {
+ static unsigned char actionSkillIndex[44] = { // @ G0496_auc_Graphic560_ActionSkillIndex
+ 0, /* N */
+ 7, /* BLOCK */
+ 6, /* CHOP */
+ 0, /* X */
+ 14, /* BLOW HORN */
+ 12, /* FLIP */
+ 9, /* PUNCH */
+ 9, /* KICK */
+ 7, /* WAR CRY Atari ST Versions 1.0 1987-12-08 1987-12-11 1.1: 14 */
+ 9, /* STAB */
+ 8, /* CLIMB DOWN */
+ 14, /* FREEZE LIFE */
+ 9, /* HIT */
+ 4, /* SWING */
+ 5, /* STAB */
+ 5, /* THRUST */
+ 5, /* JAB */
+ 7, /* PARRY */
+ 4, /* HACK */
+ 4, /* BERZERK */
+ 16, /* FIREBALL */
+ 17, /* DISPELL */
+ 14, /* CONFUSE */
+ 17, /* LIGHTNING */
+ 17, /* DISRUPT */
+ 6, /* MELEE */
+ 8, /* X */
+ 3, /* INVOKE */
+ 4, /* SLASH */
+ 4, /* CLEAVE */
+ 6, /* BASH */
+ 6, /* STUN */
+ 11, /* SHOOT */
+ 15, /* SPELLSHIELD */
+ 15, /* FIRESHIELD */
+ 3, /* FLUXCAGE */
+ 13, /* HEAL */
+ 14, /* CALM */
+ 17, /* LIGHT */
+ 18, /* WINDOW */
+ 16, /* SPIT */
+ 14, /* BRANDISH */
+ 10, /* THROW */
+ 3 /* FUSE */
+ };
+ static unsigned char actionDisabledTicks[44] = {
+ 0, /* N */
+ 6, /* BLOCK */
+ 8, /* CHOP */
+ 0, /* X */
+ 6, /* BLOW HORN */
+ 3, /* FLIP */
+ 1, /* PUNCH */
+ 5, /* KICK */
+ 3, /* WAR CRY */
+ 5, /* STAB */
+ 35, /* CLIMB DOWN */
+ 20, /* FREEZE LIFE */
+ 4, /* HIT */
+ 6, /* SWING */
+ 10, /* STAB */
+ 16, /* THRUST */
+ 2, /* JAB */
+ 18, /* PARRY */
+ 8, /* HACK */
+ 30, /* BERZERK */
+ 42, /* FIREBALL */
+ 31, /* DISPELL */
+ 10, /* CONFUSE */
+ 38, /* LIGHTNING */
+ 9, /* DISRUPT */
+ 20, /* MELEE */
+ 10, /* X */
+ 16, /* INVOKE */
+ 4, /* SLASH */
+ 12, /* CLEAVE */
+ 20, /* BASH */
+ 7, /* STUN */
+ 14, /* SHOOT */
+ 30, /* SPELLSHIELD */
+ 35, /* FIRESHIELD */
+ 2, /* FLUXCAGE */
+ 19, /* HEAL */
+ 9, /* CALM */
+ 10, /* LIGHT */
+ 15, /* WINDOW */
+ 22, /* SPIT */
+ 10, /* BRANDISH */
+ 0, /* THROW */
+ 2 /* FUSE */
+ };
+
+ _boxActionArea1ActionMenu = Box(224, 319, 77, 97); // @ G0501_s_Graphic560_Box_ActionArea1ActionMenu
+ _boxActionArea2ActionMenu = Box(224, 319, 77, 109); // @ G0500_s_Graphic560_Box_ActionArea2ActionsMenu
+ _boxActionArea3ActionMenu = Box(224, 319, 77, 121); // @ G0499_s_Graphic560_Box_ActionArea3ActionsMenu
+ _boxActionArea = Box(224, 319, 77, 121); // @ G0001_s_Graphic562_Box_ActionArea
+ _boxSpellArea = Box(224, 319, 42, 74);
+
+ for (int i = 0; i < 44; i++) {
+ _actionSkillIndex[i] = actionSkillIndex[i];
+ _actionDisabledTicks[i] = actionDisabledTicks[i];
+ }
+}
+
+MenuMan::MenuMan(DMEngine *vm) : _vm(vm) {
+ _refreshActionArea = false;
+ _actionAreaContainsIcons = false;
+ _actionDamage = 0;
+ _actionList.resetToZero();
+ _bitmapSpellAreaLine = new byte[96 * 12];
+ _bitmapSpellAreaLines = new byte[3 * 96 * 12];
+ _actionTargetGroupThing = Thing(0);
+ _actionCount = 0;
+
+ initConstants();
+}
+
+MenuMan::~MenuMan() {
+ delete[] _bitmapSpellAreaLine;
+}
+
+void MenuMan::drawMovementArrows() {
+ _vm->_eventMan->showMouse();
+ _vm->_displayMan->blitToScreen(_vm->_displayMan->getNativeBitmapOrGraphic(k13_MovementArrowsIndice),
+ &_vm->_displayMan->_boxMovementArrows, k48_byteWidth, kM1_ColorNoTransparency, 45);
+ _vm->_eventMan->hideMouse();
+}
+void MenuMan::clearActingChampion() {
+ ChampionMan &cm = *_vm->_championMan;
+ if (cm._actingChampionOrdinal) {
+ cm._actingChampionOrdinal--;
+ cm._champions[cm._actingChampionOrdinal].setAttributeFlag(kDMAttributeActionHand, true);
+ cm.drawChampionState((ChampionIndex)cm._actingChampionOrdinal);
+ cm._actingChampionOrdinal = _vm->indexToOrdinal(kDMChampionNone);
+ _refreshActionArea = true;
+ }
+}
+
+void MenuMan::drawActionIcon(ChampionIndex championIndex) {
+ static byte palChangesActionAreaObjectIcon[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0}; // @ G0498_auc_Graphic560_PaletteChanges_ActionAreaObjectIcon
+
+ if (!_actionAreaContainsIcons)
+ return;
+ DisplayMan &dm = *_vm->_displayMan;
+ Champion &champion = _vm->_championMan->_champions[championIndex];
+
+ Box box;
+ box._x1 = championIndex * 22 + 233;
+ box._x2 = box._x1 + 19;
+ box._y1 = 86;
+ box._y2 = 120;
+ dm._useByteBoxCoordinates = false;
+ if (!champion._currHealth) {
+ dm.fillScreenBox(box, k0_ColorBlack);
+ return;
+ }
+ byte *bitmapIcon = dm._tmpBitmap;
+ Thing thing = champion.getSlot(kDMSlotActionHand);
+ IconIndice iconIndex;
+ if (thing == Thing::_none) {
+ iconIndex = kDMIconIndiceActionEmptyHand;
+ } else if (_vm->_dungeonMan->_objectInfos[_vm->_dungeonMan->getObjectInfoIndex(thing)]._actionSetIndex) {
+ iconIndex = _vm->_objectMan->getIconIndex(thing);
+ } else {
+ dm.fillBitmap(bitmapIcon, k4_ColorCyan, 16, 16);
+ goto T0386006;
+ }
+ _vm->_objectMan->extractIconFromBitmap(iconIndex, bitmapIcon);
+ dm.blitToBitmapShrinkWithPalChange(bitmapIcon, bitmapIcon, 16, 16, 16, 16, palChangesActionAreaObjectIcon);
+T0386006:
+ dm.fillScreenBox(box, k4_ColorCyan);
+ Box box2;
+ box2._x1 = box._x1 + 2;
+ box2._x2 = box._x2 - 2;
+ box2._y1 = 95;
+ box2._y2 = 110;
+ dm.blitToScreen(bitmapIcon, &box2, k8_byteWidth, kM1_ColorNoTransparency, 16);
+ if (champion.getAttributes(kDMAttributeDisableAction) || _vm->_championMan->_candidateChampionOrdinal || _vm->_championMan->_partyIsSleeping) {
+ _vm->_displayMan->shadeScreenBox(&box, k0_ColorBlack);
+ }
+}
+
+void MenuMan::drawDisabledMenu() {
+ if (!_vm->_championMan->_partyIsSleeping) {
+ _vm->_eventMan->highlightBoxDisable();
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ if (_vm->_inventoryMan->_inventoryChampionOrdinal) {
+ if (_vm->_inventoryMan->_panelContent == k4_PanelContentChest) {
+ _vm->_inventoryMan->closeChest();
+ }
+ } else {
+ _vm->_displayMan->shadeScreenBox(&_vm->_displayMan->_boxMovementArrows, k0_ColorBlack);
+ }
+ _vm->_displayMan->shadeScreenBox(&_boxSpellArea, k0_ColorBlack);
+ _vm->_displayMan->shadeScreenBox(&_boxActionArea, k0_ColorBlack);
+ _vm->_eventMan->setMousePointerToNormal(k0_pointerArrow);
+ }
+}
+
+void MenuMan::refreshActionAreaAndSetChampDirMaxDamageReceived() {
+ ChampionMan &champMan = *_vm->_championMan;
+
+ if (!champMan._partyChampionCount)
+ return;
+
+ Champion *champ = nullptr;
+ if (champMan._partyIsSleeping || champMan._candidateChampionOrdinal) {
+ if (champMan._actingChampionOrdinal) {
+ clearActingChampion();
+ return;
+ }
+ if (!champMan._candidateChampionOrdinal)
+ return;
+ } else {
+ champ = champMan._champions;
+ int16 champIndex = kDMChampionFirst;
+
+ do {
+ if ((champIndex != champMan._leaderIndex)
+ && (_vm->indexToOrdinal(champIndex) != champMan._actingChampionOrdinal)
+ && (champ->_maximumDamageReceived)
+ && (champ->_dir != champ->_directionMaximumDamageReceived)) {
+
+ champ->_dir = (Direction)champ->_directionMaximumDamageReceived;
+ champ->setAttributeFlag(kDMAttributeIcon, true);
+ champMan.drawChampionState((ChampionIndex)champIndex);
+ }
+ champ->_maximumDamageReceived = 0;
+ champ++;
+ champIndex++;
+ } while (champIndex < champMan._partyChampionCount);
+ }
+
+ if (_refreshActionArea) {
+ if (!champMan._actingChampionOrdinal) {
+ if (_actionDamage) {
+ drawActionDamage(_actionDamage);
+ _actionDamage = 0;
+ } else {
+ _actionAreaContainsIcons = true;
+ drawActionArea();
+ }
+ } else {
+ _actionAreaContainsIcons = false;
+ champ->setAttributeFlag(kDMAttributeActionHand, true);
+ champMan.drawChampionState((ChampionIndex)_vm->ordinalToIndex(champMan._actingChampionOrdinal));
+ drawActionArea();
+ }
+ }
+}
+
+#define k7_ChampionNameMaximumLength 7 // @ C007_CHAMPION_NAME_MAXIMUM_LENGTH
+#define k12_ActionNameMaximumLength 12 // @ C012_ACTION_NAME_MAXIMUM_LENGTH
+
+void MenuMan::drawActionArea() {
+ DisplayMan &dispMan = *_vm->_displayMan;
+ ChampionMan &champMan = *_vm->_championMan;
+ TextMan &textMan = *_vm->_textMan;
+
+ _vm->_eventMan->hideMouse();
+ dispMan._useByteBoxCoordinates = false;
+ dispMan.fillScreenBox(_boxActionArea, k0_ColorBlack);
+ if (_actionAreaContainsIcons) {
+ for (uint16 champIndex = kDMChampionFirst; champIndex < champMan._partyChampionCount; ++champIndex)
+ drawActionIcon((ChampionIndex)champIndex);
+ } else if (champMan._actingChampionOrdinal) {
+ Box box = _boxActionArea3ActionMenu;
+ if (_actionList._actionIndices[2] == kDMActionNone)
+ box = _boxActionArea2ActionMenu;
+ if (_actionList._actionIndices[1] == kDMActionNone)
+ box = _boxActionArea1ActionMenu;
+ dispMan.blitToScreen(_vm->_displayMan->getNativeBitmapOrGraphic(k10_MenuActionAreaIndice),
+ &box, k48_byteWidth, kM1_ColorNoTransparency, 45);
+ textMan.printWithTrailingSpaces(dispMan._bitmapScreen, k160_byteWidthScreen,
+ 235, 83, k0_ColorBlack, k4_ColorCyan, champMan._champions[_vm->ordinalToIndex(champMan._actingChampionOrdinal)]._name,
+ k7_ChampionNameMaximumLength, k200_heightScreen);
+ for (uint16 actionListIndex = 0; actionListIndex < 3; actionListIndex++) {
+ textMan.printWithTrailingSpaces(dispMan._bitmapScreen, k160_byteWidthScreen, 241, 93 + actionListIndex * 12, k4_ColorCyan, k0_ColorBlack,
+ getActionName(_actionList._actionIndices[actionListIndex]),
+ k12_ActionNameMaximumLength, k200_heightScreen);
+ }
+ }
+ _vm->_eventMan->showMouse();
+ _refreshActionArea = false;
+}
+
+const char *MenuMan::getActionName(ChampionAction actionIndex) {
+ const char *championActionNames[44] = { // @ G0490_ac_Graphic560_ActionNames
+ "N", "BLOCK", "CHOP", "X", "BLOW HORN", "FLIP", "PUNCH",
+ "KICK", "WAR CRY", "STAB", "CLIMB DOWN", "FREEZE LIFE",
+ "HIT", "SWING", "STAB", "THRUST", "JAB", "PARRY", "HACK",
+ "BERZERK", "FIREBALL", "DISPELL", "CONFUSE", "LIGHTNING",
+ "DISRUPT", "MELEE", "X", "INVOKE", "SLASH", "CLEAVE",
+ "BASH", "STUN", "SHOOT", "SPELLSHIELD", "FIRESHIELD",
+ "FLUXCAGE", "HEAL", "CALM", "LIGHT", "WINDOW", "SPIT",
+ "BRANDISH", "THROW", "FUSE"
+ };
+
+ return (actionIndex == kDMActionNone) ? "" : championActionNames[actionIndex];
+}
+
+void MenuMan::drawSpellAreaControls(ChampionIndex champIndex) {
+ static Box boxSpellAreaControls(233, 319, 42, 49); // @ G0504_s_Graphic560_Box_SpellAreaControls
+
+ Champion *champ = &_vm->_championMan->_champions[champIndex];
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ int16 champHP0 = _vm->_championMan->_champions[0]._currHealth;
+ int16 champHP1 = _vm->_championMan->_champions[1]._currHealth;
+ int16 champHP2 = _vm->_championMan->_champions[2]._currHealth;
+ int16 champHP3 = _vm->_championMan->_champions[3]._currHealth;
+ _vm->_eventMan->showMouse();
+ _vm->_displayMan->fillScreenBox(boxSpellAreaControls, k0_ColorBlack);
+
+ switch (champIndex) {
+ case 0:
+ _vm->_eventMan->highlightScreenBox(233, 277, 42, 49);
+ _vm->_textMan->printToLogicalScreen(235, 48, k0_ColorBlack, k4_ColorCyan, champ->_name);
+ if (_vm->_championMan->_partyChampionCount > 1) {
+ if (champHP1)
+ _vm->_eventMan->highlightScreenBox(280, 291, 42, 48);
+
+ if (_vm->_championMan->_partyChampionCount > 2) {
+ if (champHP2)
+ _vm->_eventMan->highlightScreenBox(294, 305, 42, 48);
+
+ if ((_vm->_championMan->_partyChampionCount > 3) && champHP3)
+ _vm->_eventMan->highlightScreenBox(308, 319, 42, 48);
+ }
+ }
+ break;
+ case 1:
+ if (champHP0)
+ _vm->_eventMan->highlightScreenBox(233, 244, 42, 48);
+
+ _vm->_eventMan->highlightScreenBox(247, 291, 42, 49);
+ _vm->_textMan->printToLogicalScreen(249, 48, k0_ColorBlack, k4_ColorCyan, champ->_name);
+ if (_vm->_championMan->_partyChampionCount > 2) {
+ if (champHP2)
+ _vm->_eventMan->highlightScreenBox(294, 305, 42, 48);
+
+ if ((_vm->_championMan->_partyChampionCount > 3) && champHP3)
+ _vm->_eventMan->highlightScreenBox(308, 319, 42, 48);
+ }
+ break;
+ case 2:
+ if (champHP0)
+ _vm->_eventMan->highlightScreenBox(233, 244, 42, 48);
+
+ if (champHP1)
+ _vm->_eventMan->highlightScreenBox(247, 258, 42, 48);
+
+ _vm->_eventMan->highlightScreenBox(261, 305, 42, 49);
+ _vm->_textMan->printToLogicalScreen(263, 48, k0_ColorBlack, k4_ColorCyan, champ->_name);
+ if ((_vm->_championMan->_partyChampionCount > 3) && champHP3)
+ _vm->_eventMan->highlightScreenBox(308, 319, 42, 48);
+ break;
+
+ case 3:
+ if (champHP0)
+ _vm->_eventMan->highlightScreenBox(233, 244, 42, 48);
+
+ if (champHP1)
+ _vm->_eventMan->highlightScreenBox(247, 258, 42, 48);
+
+ if (champHP2)
+ _vm->_eventMan->highlightScreenBox(261, 272, 42, 48);
+
+ _vm->_eventMan->highlightScreenBox(275, 319, 42, 49);
+ _vm->_textMan->printToLogicalScreen(277, 48, k0_ColorBlack, k4_ColorCyan, champ->_name);
+ break;
+ default:
+ break;
+ }
+ _vm->_eventMan->hideMouse();
+}
+
+void MenuMan::buildSpellAreaLine(int16 spellAreaBitmapLine) {
+ static Box boxSpellAreaLine(0, 95, 0, 11); // @ K0074_s_Box_SpellAreaLine
+
+ char spellSymbolString[2] = {'\0', '\0'};
+ Champion *magicChampion = &_vm->_championMan->_champions[_vm->_championMan->_magicCasterChampionIndex];
+ if (spellAreaBitmapLine == k2_SpellAreaAvailableSymbols) {
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ _vm->_displayMan->blitToBitmap(_bitmapSpellAreaLines, _bitmapSpellAreaLine, boxSpellAreaLine, 0, 12, k48_byteWidth, k48_byteWidth, kM1_ColorNoTransparency, 36, 12);
+ int16 x = 1;
+ char character = 96 + (6 * magicChampion->_symbolStep);
+ for (uint16 symbolIndex = 0; symbolIndex < 6; symbolIndex++) {
+ spellSymbolString[0] = character++;
+ x += 14;
+ _vm->_textMan->printTextToBitmap(_bitmapSpellAreaLine, 48, x, 8, k4_ColorCyan, k0_ColorBlack, spellSymbolString, 12);
+ }
+ } else if (spellAreaBitmapLine == k3_SpellAreaChampionSymbols) {
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ _vm->_displayMan->blitToBitmap(_bitmapSpellAreaLines, _bitmapSpellAreaLine, boxSpellAreaLine, 0, 24, k48_byteWidth, k48_byteWidth, kM1_ColorNoTransparency, 36, 12);
+ int16 x = 8;
+ for (uint16 symbolIndex = 0; symbolIndex < 4; symbolIndex++) {
+ if ((spellSymbolString[0] = magicChampion->_symbols[symbolIndex]) == '\0')
+ break;
+ x += 9;
+ _vm->_textMan->printTextToBitmap(_bitmapSpellAreaLine, 48, x, 8, k4_ColorCyan, k0_ColorBlack, spellSymbolString, 12);
+ }
+ }
+}
+
+void MenuMan::setMagicCasterAndDrawSpellArea(ChampionIndex champIndex) {
+ static Box boxSpellAreaLine2(224, 319, 50, 61); // @ K0075_s_Box_SpellAreaLine2
+ static Box boxSpellAreaLine3(224, 319, 62, 73); // @ K0076_s_Box_SpellAreaLine3
+
+ if ((champIndex == _vm->_championMan->_magicCasterChampionIndex)
+ || ((champIndex != kDMChampionNone) && !_vm->_championMan->_champions[champIndex]._currHealth))
+ return;
+
+ if (_vm->_championMan->_magicCasterChampionIndex == kDMChampionNone) {
+ _vm->_eventMan->showMouse();
+ _vm->_displayMan->blitToScreen(_vm->_displayMan->getNativeBitmapOrGraphic(k9_MenuSpellAreaBackground), &_boxSpellArea, k48_byteWidth, kM1_ColorNoTransparency, 33);
+ _vm->_eventMan->hideMouse();
+ }
+ if (champIndex == kDMChampionNone) {
+ _vm->_championMan->_magicCasterChampionIndex = kDMChampionNone;
+ _vm->_eventMan->showMouse();
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ _vm->_displayMan->fillScreenBox(_boxSpellArea, k0_ColorBlack);
+ _vm->_eventMan->hideMouse();
+ return;
+ }
+ _vm->_championMan->_magicCasterChampionIndex = champIndex;
+ buildSpellAreaLine(k2_SpellAreaAvailableSymbols);
+ _vm->_eventMan->showMouse();
+ drawSpellAreaControls(champIndex);
+ _vm->_displayMan->blitToScreen(_bitmapSpellAreaLine, &boxSpellAreaLine2, k48_byteWidth, kM1_ColorNoTransparency, 12);
+ buildSpellAreaLine(k3_SpellAreaChampionSymbols);
+ _vm->_displayMan->blitToScreen(_bitmapSpellAreaLine, &boxSpellAreaLine3, k48_byteWidth, kM1_ColorNoTransparency, 12);
+ _vm->_eventMan->hideMouse();
+}
+
+void MenuMan::drawEnabledMenus() {
+ if (_vm->_championMan->_partyIsSleeping) {
+ _vm->_eventMan->drawSleepScreen();
+ _vm->_displayMan->drawViewport(k0_viewportNotDungeonView);
+ } else {
+ ChampionIndex casterChampionIndex = _vm->_championMan->_magicCasterChampionIndex;
+ _vm->_championMan->_magicCasterChampionIndex = kDMChampionNone; /* Force next function to draw the spell area */
+ setMagicCasterAndDrawSpellArea(casterChampionIndex);
+ if (!_vm->_championMan->_actingChampionOrdinal)
+ _actionAreaContainsIcons = true;
+
+ drawActionArea();
+ int16 AL1462_i_InventoryChampionOrdinal = _vm->_inventoryMan->_inventoryChampionOrdinal;
+ if (AL1462_i_InventoryChampionOrdinal) {
+ _vm->_inventoryMan->_inventoryChampionOrdinal = _vm->indexToOrdinal(kDMChampionNone);
+ _vm->_inventoryMan->toggleInventory((ChampionIndex)_vm->ordinalToIndex(AL1462_i_InventoryChampionOrdinal));
+ } else {
+ _vm->_displayMan->drawFloorAndCeiling();
+ drawMovementArrows();
+ }
+ _vm->_eventMan->setMousePointer();
+ }
+}
+
+int16 MenuMan::getClickOnSpellCastResult() {
+ Champion *casterChampion = &_vm->_championMan->_champions[_vm->_championMan->_magicCasterChampionIndex];
+ _vm->_eventMan->showMouse();
+ _vm->_eventMan->highlightBoxDisable();
+
+ int16 spellCastResult = getChampionSpellCastResult(_vm->_championMan->_magicCasterChampionIndex);
+ if (spellCastResult != kDMSpellCastFailureNeedsFlask) {
+ casterChampion->_symbols[0] = '\0';
+ drawAvailableSymbols(casterChampion->_symbolStep = 0);
+ drawChampionSymbols(casterChampion);
+ } else
+ spellCastResult = kDMSpellCastFailure;
+
+ _vm->_eventMan->hideMouse();
+ return spellCastResult;
+}
+
+int16 MenuMan::getChampionSpellCastResult(uint16 champIndex) {
+ if (champIndex >= _vm->_championMan->_partyChampionCount)
+ return kDMSpellCastFailure;
+
+ Champion *curChampion = &_vm->_championMan->_champions[champIndex];
+ if (!curChampion->_currHealth)
+ return kDMSpellCastFailure;
+
+ Spell *curSpell = getSpellFromSymbols((unsigned char *)curChampion->_symbols);
+ if (!curSpell) {
+ menusPrintSpellFailureMessage(curChampion, kDMSpellCastSuccess, 0);
+ return kDMSpellCastFailure;
+ }
+ int16 powerSymbolOrdinal = curChampion->_symbols[0] - '_'; /* Values 1 to 6 */
+ uint16 requiredSkillLevel = curSpell->_baseRequiredSkillLevel + powerSymbolOrdinal;
+ uint16 experience = _vm->getRandomNumber(8) + (requiredSkillLevel << 4) + ((_vm->ordinalToIndex(powerSymbolOrdinal) * curSpell->_baseRequiredSkillLevel) << 3) + (requiredSkillLevel * requiredSkillLevel);
+ uint16 skillLevel = _vm->_championMan->getSkillLevel(champIndex, curSpell->_skillIndex);
+ if (skillLevel < requiredSkillLevel) {
+ int16 missingSkillLevelCount = requiredSkillLevel - skillLevel;
+ while (missingSkillLevelCount--) {
+ if (_vm->getRandomNumber(128) > MIN(curChampion->_statistics[kDMStatWisdom][kDMStatCurrent] + 15, 115)) {
+ _vm->_championMan->addSkillExperience(champIndex, curSpell->_skillIndex, experience >> (requiredSkillLevel - skillLevel));
+ menusPrintSpellFailureMessage(curChampion, kDMFailureNeedsMorePractice, curSpell->_skillIndex);
+ return kDMSpellCastFailure;
+ }
+ }
+ }
+ switch (curSpell->getKind()) {
+ case kDMSpellKindPotion: {
+ Thing newObject;
+ Potion *newPotion = getEmptyFlaskInHand(curChampion, &newObject);
+ if (!newPotion) {
+ menusPrintSpellFailureMessage(curChampion, kDMFailureNeedsFlaskInHand, 0);
+ return kDMSpellCastFailureNeedsFlask;
+ }
+ uint16 emptyFlaskWeight = _vm->_dungeonMan->getObjectWeight(newObject);
+ newPotion->setType((PotionType)curSpell->getType());
+ newPotion->setPower(_vm->getRandomNumber(16) + (powerSymbolOrdinal * 40));
+ curChampion->_load += _vm->_dungeonMan->getObjectWeight(newObject) - emptyFlaskWeight;
+ _vm->_championMan->drawChangedObjectIcons();
+ if (_vm->_inventoryMan->_inventoryChampionOrdinal == _vm->indexToOrdinal(champIndex)) {
+ setFlag(curChampion->_attributes, kDMAttributeLoad);
+ _vm->_championMan->drawChampionState((ChampionIndex)champIndex);
+ }
+ }
+ break;
+ case kDMSpellKindProjectile:
+ if (curChampion->_dir != _vm->_dungeonMan->_partyDir) {
+ curChampion->_dir = _vm->_dungeonMan->_partyDir;
+ setFlag(curChampion->_attributes, kDMAttributeIcon);
+ _vm->_championMan->drawChampionState((ChampionIndex)champIndex);
+ }
+ if (curSpell->getType() == kDMSpellTypeProjectileOpenDoor)
+ skillLevel <<= 1;
+
+ _vm->_championMan->isProjectileSpellCast(champIndex, Thing(curSpell->getType() + Thing::_firstExplosion.toUint16()), getBoundedValue(21, (powerSymbolOrdinal + 2) * (4 + (skillLevel << 1)), 255), 0);
+ break;
+ case kDMSpellKindOther: {
+ TimelineEvent newEvent;
+ newEvent._priority = 0;
+ uint16 spellPower = (powerSymbolOrdinal + 1) << 2;
+ uint16 ticks;
+ switch (curSpell->getType()) {
+ case kDMSpellTypeOtherLight: {
+ ticks = 10000 + ((spellPower - 8) << 9);
+ uint16 lightPower = (spellPower >> 1);
+ lightPower--;
+ _vm->_championMan->_party._magicalLightAmount += _vm->_championMan->_lightPowerToLightAmount[lightPower];
+ createEvent70_light(-lightPower, ticks);
+ }
+ break;
+ case kDMSpellTypeOtherMagicTorch: {
+ ticks = 2000 + ((spellPower - 3) << 7);
+ uint16 lightPower = (spellPower >> 2);
+ lightPower++;
+ _vm->_championMan->_party._magicalLightAmount += _vm->_championMan->_lightPowerToLightAmount[lightPower];
+ createEvent70_light(-lightPower, ticks);
+ }
+ break;
+ case kDMSpellTypeOtherDarkness: {
+ uint16 lightPower = (spellPower >> 2);
+ _vm->_championMan->_party._magicalLightAmount -= _vm->_championMan->_lightPowerToLightAmount[lightPower];
+ createEvent70_light(lightPower, 98);
+ }
+ break;
+ case kDMSpellTypeOtherThievesEye: {
+ newEvent._type = k73_TMEventTypeThievesEye;
+ _vm->_championMan->_party._event73Count_ThievesEye++;
+ spellPower = (spellPower >> 1);
+ uint16 spellTicks = spellPower * spellPower;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + spellTicks);
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ }
+ break;
+ case kDMSpellTypeOtherInvisibility: {
+ newEvent._type = k71_TMEventTypeInvisibility;
+ _vm->_championMan->_party._event71Count_Invisibility++;
+ uint16 spellTicks = spellPower;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + spellTicks);
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ }
+ break;
+ case kDMSpellTypeOtherPartyShield: {
+ newEvent._type = k74_TMEventTypePartyShield;
+ newEvent._B._defense = spellPower;
+ if (_vm->_championMan->_party._shieldDefense > 50)
+ newEvent._B._defense >>= 2;
+
+ _vm->_championMan->_party._shieldDefense += newEvent._B._defense;
+ _vm->_timeline->refreshAllChampionStatusBoxes();
+ uint16 spellTicks = spellPower * spellPower;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + spellTicks);
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ }
+ break;
+ case kDMSpellTypeOtherFootprints: {
+ newEvent._type = k79_TMEventTypeFootprints;
+ _vm->_championMan->_party._event79Count_Footprints++;
+ _vm->_championMan->_party._firstScentIndex = _vm->_championMan->_party._scentCount;
+ if (powerSymbolOrdinal < 3)
+ _vm->_championMan->_party._lastScentIndex = _vm->_championMan->_party._firstScentIndex;
+ else
+ _vm->_championMan->_party._lastScentIndex = 0;
+
+ uint16 spellTicks = spellPower * spellPower;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + spellTicks);
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ }
+ break;
+ case kDMSpellTypeOtherZokathra: {
+ Thing unusedObject = _vm->_dungeonMan->getUnusedThing(k10_JunkThingType);
+ if (unusedObject == Thing::_none)
+ break;
+
+ Junk *junkData = (Junk *)_vm->_dungeonMan->getThingData(unusedObject);
+ junkData->setType(k51_JunkTypeZokathra);
+ ChampionSlot slotIndex;
+ if (curChampion->_slots[kDMSlotReadyHand] == Thing::_none)
+ slotIndex = kDMSlotReadyHand;
+ else if (curChampion->_slots[kDMSlotActionHand] == Thing::_none)
+ slotIndex = kDMSlotActionHand;
+ else
+ slotIndex = kDMSlotLeaderHand;
+
+ if ((slotIndex == kDMSlotReadyHand) || (slotIndex == kDMSlotActionHand)) {
+ _vm->_championMan->addObjectInSlot((ChampionIndex)champIndex, unusedObject, slotIndex);
+ _vm->_championMan->drawChampionState((ChampionIndex)champIndex);
+ } else
+ _vm->_moveSens->getMoveResult(unusedObject, kM1_MapXNotOnASquare, 0, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY);
+
+ }
+ break;
+ case kDMSpellTypeOtherFireshield:
+ isPartySpellOrFireShieldSuccessful(curChampion, false, (spellPower * spellPower) + 100, false);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ _vm->_championMan->addSkillExperience(champIndex, curSpell->_skillIndex, experience);
+ _vm->_championMan->disableAction(champIndex, curSpell->getDuration());
+ return kDMSpellCastSuccess;
+}
+
+Spell *MenuMan::getSpellFromSymbols(byte *symbols) {
+ static Spell SpellsArray[25] = {
+ /* { Symbols, BaseRequiredSkillLevel, SkillIndex, Attributes } */
+ Spell(0x00666F00, 2, 15, 0x7843),
+ Spell(0x00667073, 1, 18, 0x4863),
+ Spell(0x00686D77, 3, 17, 0xB433),
+ Spell(0x00686C00, 3, 19, 0x6C72),
+ Spell(0x00686D76, 3, 18, 0x8423),
+ Spell(0x00686E76, 4, 17, 0x7822),
+ Spell(0x00686F76, 4, 17, 0x5803),
+ Spell(0x00690000, 1, 16, 0x3C53),
+ Spell(0x00696F00, 3, 16, 0xA802),
+ Spell(0x00697072, 4, 13, 0x3C71),
+ Spell(0x00697075, 4, 15, 0x7083),
+ Spell(0x006A6D00, 1, 18, 0x5032),
+ Spell(0x006A6C00, 1, 19, 0x4062),
+ Spell(0x006A6F77, 1, 15, 0x3013),
+ Spell(0x006B0000, 1, 17, 0x3C42),
+ Spell(0x00667000, 2, 15, 0x64C1),
+ Spell(0x00660000, 2, 13, 0x3CB1),
+ Spell(0x00667074, 4, 13, 0x3C81),
+ Spell(0x00667075, 4, 13, 0x3C91),
+ Spell(0x00670000, 1, 13, 0x80E1),
+ Spell(0x00677000, 1, 13, 0x68A1),
+ Spell(0x00687073, 4, 13, 0x3C61),
+ Spell(0x006B7076, 3, 2, 0xFCD1),
+ Spell(0x006B6C00, 2, 19, 0x7831),
+ Spell(0x006B6E76, 0, 3, 0x3C73)
+ };
+
+ if (*(symbols + 1)) {
+ int16 bitShiftCount = 24;
+ int32 curSymbols = 0;
+ do
+ curSymbols |= (long)*symbols++ << bitShiftCount;
+ while (*symbols && ((bitShiftCount -= 8) >= 0));
+ Spell *curSpell = SpellsArray;
+ int16 spellIndex = 25;
+ while (spellIndex--) {
+ if (curSpell->_symbols & 0xFF000000) { /* If byte 1 of spell is not 0 then the spell includes the power symbol */
+ if (curSymbols == curSpell->_symbols) { /* Compare champion symbols, including power symbol, with spell (never used with actual spells) */
+ return curSpell;
+ }
+ } else if ((curSymbols & 0x00FFFFFF) == curSpell->_symbols) /* Compare champion symbols, except power symbol, with spell */
+ return curSpell;
+
+ curSpell++;
+ }
+ }
+ return nullptr;
+}
+
+void MenuMan::menusPrintSpellFailureMessage(Champion *champ, uint16 failureType, uint16 skillIndex) {
+ const char *messagesEN[4] = {" NEEDS MORE PRACTICE WITH THIS ", " SPELL.", " MUMBLES A MEANINGLESS SPELL."," NEEDS AN EMPTY FLASK IN HAND FOR POTION."};
+ const char *messagesDE[4] = {" BRAUCHT MEHR UEBUNG MIT DIESEM ", " ZAUBERSPRUCH.",
+ " MURMELT EINEN SINNLOSEN ZAUBERSPRUCH.", " MUSS FUER DEN TRANK EINE LEERE FLASCHE BEREITHALTEN."};
+ const char *messagesFR[5] = {" DOIT PRATIQUER DAVANTAGE SON ", "ENVOUTEMENT.", " MARMONNE UNE CONJURATION IMCOMPREHENSIBLE.",
+ " DOIT AVOIR UN FLACON VIDE EN MAIN POUR LA POTION.", "EXORCISME."};
+
+ if (skillIndex > kDMSkillWizard)
+ skillIndex = (skillIndex - 4) / 4;
+
+ _vm->_textMan->printLineFeed();
+ _vm->_textMan->printMessage(k4_ColorCyan, champ->_name);
+
+ const char **messages;
+ switch (_vm->getGameLanguage()) { // localized
+ case Common::DE_DEU:
+ messages = messagesDE;
+ break;
+ case Common::FR_FRA:
+ messages = messagesFR;
+ break;
+ default:
+ messages = messagesEN;
+ break;
+ }
+
+ const char *message = nullptr;
+ switch (failureType) {
+ case kDMFailureNeedsMorePractice:
+ _vm->_textMan->printMessage(k4_ColorCyan, messages[0]);
+ _vm->_textMan->printMessage(k4_ColorCyan, _vm->_championMan->_baseSkillName[skillIndex]);
+ if (_vm->getGameLanguage() != Common::FR_FRA || skillIndex == kDMSkillWizard)
+ message = messages[1];
+ else
+ message = messages[4];
+
+ break;
+ case kDMFailureMeaninglessSpell:
+ message = messages[2];
+ break;
+ case kDMFailureNeedsFlaskInHand:
+ message = messages[3];
+ break;
+ default:
+ break;
+ }
+ _vm->_textMan->printMessage(k4_ColorCyan, message);
+}
+
+Potion *MenuMan::getEmptyFlaskInHand(Champion *champ, Thing *potionThing) {
+ for (int16 slotIndex = kDMSlotHead; --slotIndex >= kDMSlotReadyHand; ) {
+ Thing curThing = champ->_slots[slotIndex];
+ if ((curThing != Thing::_none) && (_vm->_objectMan->getIconIndex(curThing) == kDMIconIndicePotionEmptyFlask)) {
+ *potionThing = curThing;
+ return (Potion *)_vm->_dungeonMan->getThingData(curThing);
+ }
+ }
+ return nullptr;
+}
+
+void MenuMan::createEvent70_light(int16 lightPower, int16 ticks) {
+ TimelineEvent newEvent;
+ newEvent._type = k70_TMEventTypeLight;
+ newEvent._B._lightPower = lightPower;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + ticks);
+ newEvent._priority = 0;
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ _vm->_inventoryMan->setDungeonViewPalette();
+}
+
+bool MenuMan::isPartySpellOrFireShieldSuccessful(Champion *champ, bool spellShield, uint16 ticks, bool useMana) {
+ bool isPartyMagicShieldSuccessful = true;
+ if (useMana) {
+ if (champ->_currMana == 0)
+ return false;
+
+ if (champ->_currMana < 4) {
+ ticks >>= 1;
+ champ->_currMana = 0;
+ isPartyMagicShieldSuccessful = false;
+ } else
+ champ->_currMana -= 4;
+ }
+ TimelineEvent newEvent;
+ newEvent._B._defense = ticks >> 5;
+ if (spellShield) {
+ newEvent._type = k77_TMEventTypeSpellShield;
+ if (_vm->_championMan->_party._spellShieldDefense > 50)
+ newEvent._B._defense >>= 2;
+
+ _vm->_championMan->_party._spellShieldDefense += newEvent._B._defense;
+ } else {
+ newEvent._type = k78_TMEventTypeFireShield;
+ if (_vm->_championMan->_party._fireShieldDefense > 50)
+ newEvent._B._defense >>= 2;
+
+ _vm->_championMan->_party._fireShieldDefense += newEvent._B._defense;
+ }
+ newEvent._priority = 0;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + ticks);
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ _vm->_timeline->refreshAllChampionStatusBoxes();
+
+ return isPartyMagicShieldSuccessful;
+}
+
+void MenuMan::drawAvailableSymbols(uint16 symbolStep) {
+ char displayBuffer[2];
+ displayBuffer[1] = '\0';
+ char curCharacter = 96 + 6 * symbolStep;
+ int16 textPosX = 225;
+ for (uint16 L1214_ui_Counter = 0; L1214_ui_Counter < 6; L1214_ui_Counter++) {
+ displayBuffer[0] = curCharacter++;
+ textPosX += 14;
+ _vm->_textMan->printToLogicalScreen(textPosX, 58, k4_ColorCyan, k0_ColorBlack, displayBuffer);
+ }
+}
+
+void MenuMan::drawChampionSymbols(Champion *champ) {
+ uint16 symbolCount = strlen(champ->_symbols);
+ int16 textPosX = 232;
+ char displayBuffer[2];
+ displayBuffer[1] = '\0';
+
+ for (uint16 symbolIndex = 0; symbolIndex < 4; symbolIndex++) {
+ if (symbolIndex >= symbolCount)
+ displayBuffer[0] = ' ';
+ else
+ displayBuffer[0] = champ->_symbols[symbolIndex];
+
+ textPosX += 9;
+ _vm->_textMan->printToLogicalScreen(textPosX, 70, k4_ColorCyan, k0_ColorBlack, displayBuffer);
+ }
+}
+
+void MenuMan::addChampionSymbol(int16 symbolIndex) {
+ static byte symbolBaseManaCost[4][6] = {
+ {1, 2, 3, 4, 5, 6}, /* Power 1 */
+ {2, 3, 4, 5, 6, 7}, /* Power 2 */
+ {4, 5, 6, 7, 7, 9}, /* Power 3 */
+ {2, 2, 3, 4, 6, 7} /* Power 4 */
+ };
+ static byte symbolManaCostMultiplier[6] = {8, 12, 16, 20, 24, 28};
+
+ Champion *casterChampion = &_vm->_championMan->_champions[_vm->_championMan->_magicCasterChampionIndex];
+ uint16 symbolStep = casterChampion->_symbolStep;
+ uint16 manaCost = symbolBaseManaCost[symbolStep][symbolIndex];
+ if (symbolStep) {
+ uint16 symbolIndex = casterChampion->_symbols[0] - 96;
+ manaCost = (manaCost * symbolManaCostMultiplier[symbolIndex]) >> 3;
+ }
+
+ if (manaCost <= casterChampion->_currMana) {
+ casterChampion->_currMana -= manaCost;
+ setFlag(casterChampion->_attributes, kDMAttributeStatistics);
+ casterChampion->_symbols[symbolStep] = 96 + (symbolStep * 6) + symbolIndex;
+ casterChampion->_symbols[symbolStep + 1] = '\0';
+ casterChampion->_symbolStep = symbolStep = returnNextVal(symbolStep);
+ _vm->_eventMan->showMouse();
+ drawAvailableSymbols(symbolStep);
+ drawChampionSymbols(casterChampion);
+ _vm->_championMan->drawChampionState(_vm->_championMan->_magicCasterChampionIndex);
+ _vm->_eventMan->hideMouse();
+ }
+}
+
+void MenuMan::deleteChampionSymbol() {
+ Champion *casterChampion = &_vm->_championMan->_champions[_vm->_championMan->_magicCasterChampionIndex];
+ if (!strlen(casterChampion->_symbols))
+ return;
+
+ int16 symbolStep = returnPrevVal(casterChampion->_symbolStep);
+ casterChampion->_symbolStep = symbolStep;
+ casterChampion->_symbols[symbolStep] = '\0';
+ _vm->_eventMan->showMouse();
+ drawAvailableSymbols(symbolStep);
+ drawChampionSymbols(casterChampion);
+ _vm->_eventMan->hideMouse();
+}
+
+bool MenuMan::didClickTriggerAction(int16 actionListIndex) {
+ bool retVal = false;
+
+ if (!_vm->_championMan->_actingChampionOrdinal || (actionListIndex != -1 && (_actionList._actionIndices[actionListIndex] == kDMActionNone)))
+ return retVal;
+
+ uint16 championIndex = _vm->ordinalToIndex(_vm->_championMan->_actingChampionOrdinal);
+ Champion *curChampion = &_vm->_championMan->_champions[championIndex];
+ if (actionListIndex == -1)
+ retVal = true;
+ else {
+ uint16 actionIndex = _actionList._actionIndices[actionListIndex];
+ // Fix original bug - When disabled ticks is equal to zero, increasing the defense leads
+ // to a permanent increment.
+ if (_actionDisabledTicks[actionIndex])
+ curChampion->_actionDefense += _vm->_timeline->_actionDefense[actionIndex];
+
+ setFlag(curChampion->_attributes, kDMAttributeStatistics);
+ retVal = isActionPerformed(championIndex, actionIndex);
+ curChampion->_actionIndex = (ChampionAction)actionIndex;
+ }
+ clearActingChampion();
+ return retVal;
+}
+
+bool MenuMan::isActionPerformed(uint16 champIndex, int16 actionIndex) {
+ static unsigned char actionStaminaArray[44] = {
+ 0, /* N */
+ 4, /* BLOCK */
+ 10, /* CHOP */
+ 0, /* X */
+ 1, /* BLOW HORN */
+ 0, /* FLIP */
+ 1, /* PUNCH */
+ 3, /* KICK */
+ 1, /* WAR CRY */
+ 3, /* STAB */
+ 40, /* CLIMB DOWN */
+ 3, /* FREEZE LIFE */
+ 3, /* HIT */
+ 2, /* SWING */
+ 4, /* STAB */
+ 17, /* THRUST */
+ 3, /* JAB */
+ 1, /* PARRY */
+ 6, /* HACK */
+ 40, /* BERZERK */
+ 5, /* FIREBALL */
+ 2, /* DISPELL */
+ 2, /* CONFUSE */
+ 4, /* LIGHTNING */
+ 5, /* DISRUPT */
+ 25, /* MELEE */
+ 1, /* X */
+ 2, /* INVOKE */
+ 2, /* SLASH */
+ 10, /* CLEAVE */
+ 9, /* BASH */
+ 2, /* STUN */
+ 3, /* SHOOT */
+ 1, /* SPELLSHIELD */
+ 2, /* FIRESHIELD */
+ 6, /* FLUXCAGE */
+ 1, /* HEAL */
+ 1, /* CALM */
+ 3, /* LIGHT */
+ 2, /* WINDOW */
+ 3, /* SPIT */
+ 2, /* BRANDISH */
+ 0, /* THROW */
+ 2 /* FUSE */
+ };
+ static unsigned char actionExperienceGainArray[44] = {
+ 0, /* N */
+ 8, /* BLOCK */
+ 10, /* CHOP */
+ 0, /* X */
+ 0, /* BLOW HORN */
+ 0, /* FLIP */
+ 8, /* PUNCH */
+ 13, /* KICK */
+ 7, /* WAR CRY */
+ 15, /* STAB */
+ 15, /* CLIMB DOWN */
+ 22, /* FREEZE LIFE */
+ 10, /* HIT */
+ 6, /* SWING */
+ 12, /* STAB */
+ 19, /* THRUST */
+ 11, /* JAB */
+ 17, /* PARRY */
+ 9, /* HACK */
+ 40, /* BERZERK */
+ 35, /* FIREBALL */
+ 25, /* DISPELL */
+ 0, /* CONFUSE */
+ 30, /* LIGHTNING */
+ 10, /* DISRUPT */
+ 24, /* MELEE */
+ 0, /* X */
+ 25, /* INVOKE */
+ 9, /* SLASH */
+ 12, /* CLEAVE */
+ 11, /* BASH */
+ 10, /* STUN */
+ 20, /* SHOOT Atari ST Versions 1.0 1987-12-08 1987-12-11: 9 */
+ 20, /* SPELLSHIELD */
+ 20, /* FIRESHIELD */
+ 12, /* FLUXCAGE */
+ 0, /* HEAL */
+ 0, /* CALM */
+ 20, /* LIGHT */
+ 30, /* WINDOW */
+ 25, /* SPIT */
+ 0, /* BRANDISH */
+ 5, /* THROW */
+ 1 /* FUSE */
+ };
+
+ if (champIndex >= _vm->_championMan->_partyChampionCount)
+ return false;
+
+ Champion *curChampion = &_vm->_championMan->_champions[champIndex];
+ if (!curChampion->_currHealth)
+ return false;
+
+ Weapon *weaponInHand = (Weapon *)_vm->_dungeonMan->getThingData(curChampion->_slots[kDMSlotActionHand]);
+
+ int16 nextMapX = _vm->_dungeonMan->_partyMapX;
+ int16 nextMapY = _vm->_dungeonMan->_partyMapY;
+ nextMapX += _vm->_dirIntoStepCountEast[curChampion->_dir];
+ nextMapY += _vm->_dirIntoStepCountNorth[curChampion->_dir];
+ _actionTargetGroupThing = _vm->_groupMan->groupGetThing(nextMapX, nextMapY);
+ uint16 actionDisabledTicks = _actionDisabledTicks[actionIndex];
+ int16 actionSkillIndex = _actionSkillIndex[actionIndex];
+ int16 actionStamina = actionStaminaArray[actionIndex] + _vm->getRandomNumber(2);
+ int16 actionExperienceGain = actionExperienceGainArray[actionIndex];
+ uint16 targetSquare = _vm->_dungeonMan->getSquare(nextMapX, nextMapY).toByte();
+
+ int16 requiredManaAmount = 0;
+ if (((actionSkillIndex >= kDMSkillFire) && (actionSkillIndex <= kDMSkillWater)) || (actionSkillIndex == kDMSkillWizard))
+ requiredManaAmount = 7 - MIN<uint16>(6, _vm->_championMan->getSkillLevel(champIndex, actionSkillIndex));
+
+ bool setDirectionFl = false;
+ int16 kineticEnergy = 0;
+ Thing explosionThing = Thing::_none;
+ bool actionPerformed = true;
+ switch (actionIndex) {
+ case kDMActionLightning:
+ kineticEnergy = 180;
+ explosionThing = Thing::_explLightningBolt;
+ setDirectionFl = true;
+ break;
+ case kDMActionDispel:
+ kineticEnergy = 150;
+ explosionThing = Thing::_explHarmNonMaterial;
+ setDirectionFl = true;
+ break;
+ case kDMActionFireball:
+ kineticEnergy = 150;
+ explosionThing = Thing::_explFireBall;
+ setDirectionFl = true;
+ break;
+ case kDMActionSpit:
+ kineticEnergy = 250;
+ explosionThing = Thing::_explFireBall;
+ setDirectionFl = true;
+ break;
+ case kDMActionBash:
+ case kDMActionHack:
+ case kDMActionBerzerk:
+ case kDMActionKick:
+ case kDMActionSwing:
+ case kDMActionChop:
+ if ((Square(targetSquare).getType() == k4_DoorElemType) && (Square(targetSquare).getDoorState() == k4_doorState_CLOSED)) {
+ _vm->_sound->requestPlay(k16_soundCOMBAT_ATTACK_SKELETON_ANIMATED_ARMOUR_DETH_KNIGHT, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, k1_soundModePlayIfPrioritized);
+ actionDisabledTicks = 6;
+ _vm->_groupMan->groupIsDoorDestoryedByAttack(nextMapX, nextMapY, _vm->_championMan->getStrength(champIndex, kDMSlotActionHand), false, 2);
+ _vm->_sound->requestPlay(k04_soundWOODEN_THUD_ATTACK_TROLIN_ANTMAN_STONE_GOLEM, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, k2_soundModePlayOneTickLater);
+ break;
+ }
+ case kDMActionDisrupt:
+ case kDMActionJab:
+ case kDMActionParry:
+ case kDMActionStab14:
+ case kDMActionStab9:
+ case kDMActionStun:
+ case kDMActionThrust:
+ case kDMActionMelee:
+ case kDMActionSlash:
+ case kDMActionCleave:
+ case kDMActionPunch:
+ if (!(actionPerformed = isMeleeActionPerformed(champIndex, curChampion, actionIndex, nextMapX, nextMapY, actionSkillIndex))) {
+ actionExperienceGain >>= 1;
+ actionDisabledTicks >>= 1;
+ }
+ break;
+ case kDMActionConfuse:
+ decrementCharges(curChampion);
+ // No break on purpose
+ case kDMActionWarCry:
+ case kDMActionCalm:
+ case kDMActionBrandish:
+ case kDMActionBlowHorn:
+ if (actionIndex == kDMActionWarCry)
+ _vm->_sound->requestPlay(k28_soundWAR_CRY, nextMapX, nextMapY, k0_soundModePlayImmediately);
+ else if (actionIndex == kDMActionBlowHorn)
+ _vm->_sound->requestPlay(k25_soundBLOW_HORN, nextMapX, nextMapY, k0_soundModePlayImmediately);
+
+ actionPerformed = isGroupFrightenedByAction(champIndex, actionIndex, nextMapX, nextMapY);
+ break;
+ case kDMActionShoot: {
+ if (Thing(curChampion->_slots[kDMSlotReadyHand]).getType() != k5_WeaponThingType) {
+ _actionDamage = kM2_damageNoAmmunition;
+ actionExperienceGain = 0;
+ actionPerformed = false;
+ break;
+ }
+
+ WeaponInfo *weaponInfoActionHand = &_vm->_dungeonMan->_weaponInfos[weaponInHand->getType()];
+ WeaponInfo *weaponInfoReadyHand = _vm->_dungeonMan->getWeaponInfo(curChampion->_slots[kDMSlotReadyHand]);
+ int16 actionHandWeaponClass = weaponInfoActionHand->_class;
+ int16 readyHandWeaponClass = weaponInfoReadyHand->_class;
+ int16 stepEnergy = actionHandWeaponClass;
+ if ((actionHandWeaponClass >= k16_WeaponClassFirstBow) && (actionHandWeaponClass <= k31_WeaponClassLastBow)) {
+ if (readyHandWeaponClass != k10_WeaponClassBowAmmunition) {
+ _actionDamage = kM2_damageNoAmmunition;
+ actionExperienceGain = 0;
+ actionPerformed = false;
+ break;
+ }
+ stepEnergy -= k16_WeaponClassFirstBow;
+ } else if ((actionHandWeaponClass >= k32_WeaponClassFirstSling) && (actionHandWeaponClass <= k47_WeaponClassLastSling)) {
+ if (readyHandWeaponClass != k11_WeaponClassSlingAmmunition) {
+ _actionDamage = kM2_damageNoAmmunition;
+ actionExperienceGain = 0;
+ actionPerformed = false;
+ break;
+ }
+ stepEnergy -= k32_WeaponClassFirstSling;
+ }
+
+ setChampionDirectionToPartyDirection(curChampion);
+ Thing removedObject = _vm->_championMan->getObjectRemovedFromSlot(champIndex, kDMSlotReadyHand);
+ _vm->_sound->requestPlay(k16_soundCOMBAT_ATTACK_SKELETON_ANIMATED_ARMOUR_DETH_KNIGHT, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, k1_soundModePlayIfPrioritized);
+ _vm->_championMan->championShootProjectile(curChampion, removedObject, weaponInfoActionHand->_kineticEnergy + weaponInfoReadyHand->_kineticEnergy, (weaponInfoActionHand->getShootAttack() + _vm->_championMan->getSkillLevel(champIndex, kDMSkillShoot)) << 1, stepEnergy);
+ }
+ break;
+ case kDMActionFlip: {
+ const char *messagesEN[2] = {"IT COMES UP HEADS.", "IT COMES UP TAILS."};
+ const char *messagesDE[2] = {"DIE KOPFSEITE IST OBEN.", "DIE ZAHL IST OBEN."};
+ const char *messagesFR[2] = {"C'EST FACE.", "C'EST PILE."};
+ const char **message;
+ switch (_vm->getGameLanguage()) { // localized
+ default:
+ case Common::EN_ANY:
+ message = messagesEN;
+ break;
+ case Common::DE_DEU:
+ message = messagesDE;
+ break;
+ case Common::FR_FRA:
+ message = messagesFR;
+ break;
+ }
+ if (_vm->getRandomNumber(2))
+ printMessageAfterReplacements(message[0]);
+ else
+ printMessageAfterReplacements(message[1]);
+
+ }
+ break;
+ case kDMActionSpellshield:
+ case kDMActionFireshield:
+ if (!isPartySpellOrFireShieldSuccessful(curChampion, actionIndex == kDMActionSpellshield, 280, true)) {
+ actionExperienceGain >>= 2;
+ actionDisabledTicks >>= 1;
+ } else
+ decrementCharges(curChampion);
+
+ break;
+ case kDMActionInvoke:
+ kineticEnergy = _vm->getRandomNumber(128) + 100;
+ switch (_vm->getRandomNumber(6)) {
+ case 0:
+ explosionThing = Thing::_explPoisonBolt;
+ break;
+ case 1:
+ explosionThing = Thing::_explPoisonCloud;
+ break;
+ case 2:
+ explosionThing = Thing::_explHarmNonMaterial;
+ break;
+ default:
+ explosionThing = Thing::_explFireBall;
+ break;
+ }
+ setDirectionFl = true;
+ break;
+
+ case kDMActionFluxcage:
+ setChampionDirectionToPartyDirection(curChampion);
+ _vm->_groupMan->fluxCageAction(nextMapX, nextMapY);
+ break;
+ case kDMActionFuse:
+ setChampionDirectionToPartyDirection(curChampion);
+ nextMapX = _vm->_dungeonMan->_partyMapX;
+ nextMapY = _vm->_dungeonMan->_partyMapY;
+ nextMapX += _vm->_dirIntoStepCountEast[_vm->_dungeonMan->_partyDir], nextMapY += _vm->_dirIntoStepCountNorth[_vm->_dungeonMan->_partyDir];
+ _vm->_groupMan->fuseAction(nextMapX, nextMapY);
+ break;
+ case kDMActionHeal: {
+ /* CHANGE2_17_IMPROVEMENT Heal action is much more effective
+ Heal cycles occur as long as the champion has missing health and enough mana. Cycle count = Min(Current Mana / 2, Missing health / Min(10, Heal skill level))
+ Healing amount is Min(Missing health, Min(10, Heal skill level)) * heal cycle count
+ Mana cost is 2 * heal cycle count
+ Experience gain is 2 + 2 * heal cycle count */
+ int16 missingHealth = curChampion->_maxHealth - curChampion->_currHealth;
+ if ((missingHealth > 0) && curChampion->_currMana) {
+ int16 healingCapability = MIN((uint16)10, _vm->_championMan->getSkillLevel(champIndex, kDMSkillHeal));
+ actionExperienceGain = 2;
+ uint16 healingAmount;
+ do {
+ healingAmount = MIN(missingHealth, healingCapability);
+ curChampion->_currHealth += healingAmount;
+ actionExperienceGain += 2;
+ curChampion->_currMana = curChampion->_currMana - 2;
+ if (curChampion->_currMana > 0)
+ missingHealth -= healingAmount;
+ } while ((curChampion->_currMana > 0) && missingHealth);
+
+ if (curChampion->_currMana < 0)
+ curChampion->_currMana = 0;
+
+ setFlag(curChampion->_attributes, kDMAttributeStatistics);
+ actionPerformed = true;
+ }
+ }
+ break;
+ case kDMActionWindow: {
+ int16 windowTicks = _vm->getRandomNumber(_vm->_championMan->getSkillLevel(champIndex, actionSkillIndex) + 8) + 5;
+ TimelineEvent newEvent;
+ newEvent._priority = 0;
+ newEvent._type = k73_TMEventTypeThievesEye;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + windowTicks);
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ _vm->_championMan->_party._event73Count_ThievesEye++;
+ decrementCharges(curChampion);
+ }
+ break;
+ case kDMActionClimbDown:
+ nextMapX = _vm->_dungeonMan->_partyMapX;
+ nextMapY = _vm->_dungeonMan->_partyMapY;
+ nextMapX += _vm->_dirIntoStepCountEast[_vm->_dungeonMan->_partyDir];
+ nextMapY += _vm->_dirIntoStepCountNorth[_vm->_dungeonMan->_partyDir];
+ /* CHANGE6_00_FIX The presence of a group over the pit is checked so that you cannot climb down a pit with the rope if there is a group levitating over it */
+ if ((_vm->_dungeonMan->getSquare(nextMapX, nextMapY).getType() == k2_PitElemType) && (_vm->_groupMan->groupGetThing(nextMapX, nextMapY) == Thing::_endOfList)) {
+ /* BUG0_77 The party moves forward when using the rope in front of a closed pit. The engine does not check whether
+ the pit is open before moving the party over the pit. This is not consistent with the behavior when using the
+ rope in front of a corridor where nothing happens */
+ _vm->_moveSens->_useRopeToClimbDownPit = true;
+ _vm->_moveSens->getMoveResult(Thing::_party, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, nextMapX, nextMapY);
+ _vm->_moveSens->_useRopeToClimbDownPit = false;
+ } else {
+ actionDisabledTicks = 0;
+ }
+ break;
+ case kDMActionFreezeLife: {
+ int16 freezeTicks;
+ if (weaponInHand->getType() == k42_JunkTypeMagicalBoxBlue) {
+ freezeTicks = 30;
+ _vm->_championMan->getObjectRemovedFromSlot(champIndex, kDMSlotActionHand);
+ weaponInHand->setNextThing(Thing::_none);
+ } else if (weaponInHand->getType() == k43_JunkTypeMagicalBoxGreen) {
+ freezeTicks = 125;
+ _vm->_championMan->getObjectRemovedFromSlot(champIndex, kDMSlotActionHand);
+ weaponInHand->setNextThing(Thing::_none);
+ } else {
+ freezeTicks = 70;
+ decrementCharges(curChampion);
+ }
+ _vm->_championMan->_party._freezeLifeTicks = MIN(200, _vm->_championMan->_party._freezeLifeTicks + freezeTicks);
+ }
+ break;
+ case kDMActionLight:
+ _vm->_championMan->_party._magicalLightAmount += _vm->_championMan->_lightPowerToLightAmount[2];
+ createEvent70_light(-2, 2500);
+ decrementCharges(curChampion);
+ break;
+ case kDMActionThrow:
+ setChampionDirectionToPartyDirection(curChampion);
+ actionPerformed = _vm->_championMan->isObjectThrown(champIndex, kDMSlotActionHand, (curChampion->_cell == returnNextVal(_vm->_dungeonMan->_partyDir)) || (curChampion->_cell == (ViewCell)returnOppositeDir(_vm->_dungeonMan->_partyDir)));
+ if (actionPerformed)
+ _vm->_timeline->_events[curChampion->_enableActionEventIndex]._B._slotOrdinal = _vm->indexToOrdinal(kDMSlotActionHand);
+ break;
+ }
+
+ if (setDirectionFl) {
+ setChampionDirectionToPartyDirection(curChampion);
+ if (curChampion->_currMana < requiredManaAmount) {
+ kineticEnergy = MAX(2, curChampion->_currMana * kineticEnergy / requiredManaAmount);
+ requiredManaAmount = curChampion->_currMana;
+ }
+ actionPerformed = _vm->_championMan->isProjectileSpellCast(champIndex, explosionThing, kineticEnergy, requiredManaAmount);
+ if (!actionPerformed)
+ actionExperienceGain >>= 1;
+
+ decrementCharges(curChampion);
+ }
+ if (actionDisabledTicks)
+ _vm->_championMan->disableAction(champIndex, actionDisabledTicks);
+
+ if (actionStamina)
+ _vm->_championMan->decrementStamina(champIndex, actionStamina);
+
+ if (actionExperienceGain)
+ _vm->_championMan->addSkillExperience(champIndex, actionSkillIndex, actionExperienceGain);
+
+ _vm->_championMan->drawChampionState((ChampionIndex)champIndex);
+ return actionPerformed;
+}
+
+void MenuMan::setChampionDirectionToPartyDirection(Champion *champ) {
+ if (champ->_dir != _vm->_dungeonMan->_partyDir) {
+ champ->_dir = _vm->_dungeonMan->_partyDir;
+ setFlag(champ->_attributes, kDMAttributeIcon);
+ }
+}
+
+void MenuMan::decrementCharges(Champion *champ) {
+ Thing slotActionThing = champ->_slots[kDMSlotActionHand];
+ Junk *slotActionData = (Junk *)_vm->_dungeonMan->getThingData(slotActionThing);
+ switch (slotActionThing.getType()) {
+ case k5_WeaponThingType:
+ if (((Weapon *)slotActionData)->getChargeCount()) {
+ ((Weapon *)slotActionData)->setChargeCount(((Weapon *)slotActionData)->getChargeCount() - 1);
+ }
+ break;
+ case k6_ArmourThingType:
+ if (((Armour *)slotActionData)->getChargeCount()) {
+ ((Armour *)slotActionData)->setChargeCount(((Armour *)slotActionData)->getChargeCount() - 1);
+ }
+ break;
+ case k10_JunkThingType:
+ if (slotActionData->getChargeCount()) {
+ slotActionData->setChargeCount(slotActionData->getChargeCount() - 1);
+ }
+ break;
+ default:
+ break;
+ }
+ _vm->_championMan->drawChangedObjectIcons();
+}
+
+bool MenuMan::isMeleeActionPerformed(int16 champIndex, Champion *champ, int16 actionIndex, int16 targetMapX, int16 targetMapY, int16 skillIndex) {
+ static unsigned char actionDamageFactorArray[44] = {
+ 0, /* N */
+ 15, /* BLOCK */
+ 48, /* CHOP */
+ 0, /* X */
+ 0, /* BLOW HORN */
+ 0, /* FLIP */
+ 32, /* PUNCH */
+ 48, /* KICK */
+ 0, /* WAR CRY */
+ 48, /* STAB */
+ 0, /* CLIMB DOWN */
+ 0, /* FREEZE LIFE */
+ 20, /* HIT */
+ 16, /* SWING */
+ 60, /* STAB */
+ 66, /* THRUST */
+ 8, /* JAB */
+ 8, /* PARRY */
+ 25, /* HACK */
+ 96, /* BERZERK */
+ 0, /* FIREBALL */
+ 0, /* DISPELL */
+ 0, /* CONFUSE */
+ 0, /* LIGHTNING */
+ 55, /* DISRUPT */
+ 60, /* MELEE */
+ 0, /* X */
+ 0, /* INVOKE */
+ 16, /* SLASH */
+ 48, /* CLEAVE */
+ 50, /* BASH */
+ 16, /* STUN */
+ 0, /* SHOOT */
+ 0, /* SPELLSHIELD */
+ 0, /* FIRESHIELD */
+ 0, /* FLUXCAGE */
+ 0, /* HEAL */
+ 0, /* CALM */
+ 0, /* LIGHT */
+ 0, /* WINDOW */
+ 0, /* SPIT */
+ 0, /* BRANDISH */
+ 0, /* THROW */
+ 0 /* FUSE */
+ };
+ static unsigned char actionHitProbabilityArray[44] = {
+ 0, /* N */
+ 22, /* BLOCK */
+ 48, /* CHOP */
+ 0, /* X */
+ 0, /* BLOW HORN */
+ 0, /* FLIP */
+ 38, /* PUNCH */
+ 28, /* KICK */
+ 0, /* WAR CRY */
+ 30, /* STAB */
+ 0, /* CLIMB DOWN */
+ 0, /* FREEZE LIFE */
+ 20, /* HIT */
+ 32, /* SWING */
+ 42, /* STAB */
+ 57, /* THRUST */
+ 70, /* JAB */
+ 18, /* PARRY */
+ 27, /* HACK */
+ 46, /* BERZERK */
+ 0, /* FIREBALL */
+ 0, /* DISPELL */
+ 0, /* CONFUSE */
+ 0, /* LIGHTNING */
+ 46, /* DISRUPT */
+ 64, /* MELEE */
+ 0, /* X */
+ 0, /* INVOKE */
+ 26, /* SLASH */
+ 40, /* CLEAVE */
+ 32, /* BASH */
+ 50, /* STUN */
+ 0, /* SHOOT */
+ 0, /* SPELLSHIELD */
+ 0, /* FIRESHIELD */
+ 0, /* FLUXCAGE */
+ 0, /* HEAL */
+ 0, /* CALM */
+ 0, /* LIGHT */
+ 0, /* WINDOW */
+ 0, /* SPIT */
+ 0, /* BRANDISH */
+ 0, /* THROW */
+ 0 /* FUSE */
+ };
+
+ _vm->_sound->requestPlay(k16_soundCOMBAT_ATTACK_SKELETON_ANIMATED_ARMOUR_DETH_KNIGHT, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, k1_soundModePlayIfPrioritized);
+ if (_actionTargetGroupThing == Thing::_endOfList)
+ return false;
+
+ uint16 championCell = champ->_cell;
+ int16 targetCreatureOrdinal = _vm->_groupMan->getMeleeTargetCreatureOrdinal(targetMapX, targetMapY, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, championCell);
+ if (targetCreatureOrdinal) {
+ uint16 viewCell = normalizeModulo4(championCell + 4 - champ->_dir);
+ switch (viewCell) {
+ case k2_ViewCellBackRight: /* Champion is on the back right of the square and tries to attack a creature in the front right of its square */
+ case k3_ViewCellBackLeft: /* Champion is on the back left of the square and tries to attack a creature in the front left of its square */
+ uint16 cellDelta = (viewCell == k2_ViewCellBackRight) ? 3 : 1;
+ /* Check if there is another champion in front */
+ if (_vm->_championMan->getIndexInCell(normalizeModulo4(championCell + cellDelta)) != kDMChampionNone) {
+ _actionDamage = kM1_damageCantReach;
+ return false;
+ }
+ break;
+ }
+
+ if ((actionIndex == kDMActionDisrupt) && !getFlag(_vm->_dungeonMan->getCreatureAttributes(_actionTargetGroupThing), k0x0040_MaskCreatureInfo_nonMaterial))
+ return false;
+
+ uint16 actionHitProbability = actionHitProbabilityArray[actionIndex];
+ uint16 actionDamageFactor = actionDamageFactorArray[actionIndex];
+ if ((_vm->_objectMan->getIconIndex(champ->_slots[kDMSlotActionHand]) == kDMIconIndiceWeaponVorpalBlade) || (actionIndex == kDMActionDisrupt)) {
+ setFlag(actionHitProbability, k0x8000_hitNonMaterialCreatures);
+ }
+ _actionDamage = _vm->_groupMan->getMeleeActionDamage(champ, champIndex, (Group *)_vm->_dungeonMan->getThingData(_actionTargetGroupThing), _vm->ordinalToIndex(targetCreatureOrdinal), targetMapX, targetMapY, actionHitProbability, actionDamageFactor, skillIndex);
+ return true;
+ }
+
+ return false;
+}
+
+bool MenuMan::isGroupFrightenedByAction(int16 champIndex, uint16 actionIndex, int16 mapX, int16 mapY) {
+ bool isGroupFrightenedByAction = false;
+ if (_actionTargetGroupThing == Thing::_endOfList)
+ return isGroupFrightenedByAction;
+
+ uint16 experience = 0;
+ int16 frightAmount = 0;
+
+ switch (actionIndex) {
+ case kDMActionWarCry:
+ frightAmount = 3;
+ experience = 12; /* War Cry gives experience in priest skill k14_ChampionSkillInfluence below. The War Cry action also has an experience gain of 7 defined in G0497_auc_Graphic560_ActionExperienceGain in the same skill (versions 1.1 and below) or in the fighter skill k7_ChampionSkillParry (versions 1.2 and above). In versions 1.2 and above, this is the only action that gives experience in two skills */
+ break;
+ case kDMActionCalm:
+ frightAmount = 7;
+ experience = 35;
+ break;
+ case kDMActionBrandish:
+ frightAmount = 6;
+ experience = 30;
+ break;
+ case kDMActionBlowHorn:
+ frightAmount = 6;
+ experience = 20;
+ break;
+ case kDMActionConfuse:
+ frightAmount = 12;
+ experience = 45;
+ break;
+ }
+ frightAmount += _vm->_championMan->getSkillLevel(champIndex, kDMSkillInfluence);
+ Group *targetGroup = (Group *)_vm->_dungeonMan->getThingData(_actionTargetGroupThing);
+ CreatureInfo *creatureInfo = &_vm->_dungeonMan->_creatureInfos[targetGroup->_type];
+ uint16 fearResistance = creatureInfo->getFearResistance();
+ if ((fearResistance > _vm->getRandomNumber(frightAmount)) || (fearResistance == k15_immuneToFear)) {
+ experience >>= 1;
+ } else {
+ ActiveGroup *activeGroup = &_vm->_groupMan->_activeGroups[targetGroup->getActiveGroupIndex()];
+ if (targetGroup->getBehaviour() == k6_behavior_ATTACK) {
+ _vm->_groupMan->stopAttacking(activeGroup, mapX, mapY);
+ _vm->_groupMan->startWandering(mapX, mapY);
+ }
+ targetGroup->setBehaviour(k5_behavior_FLEE);
+ activeGroup->_delayFleeingFromTarget = ((16 - fearResistance) << 2) / creatureInfo->_movementTicks;
+ isGroupFrightenedByAction = true;
+ }
+ _vm->_championMan->addSkillExperience(champIndex, kDMSkillInfluence, experience);
+
+ return isGroupFrightenedByAction;
+}
+
+void MenuMan::printMessageAfterReplacements(const char *str) {
+ char outputString[128];
+ char *curCharacter = outputString;
+ *curCharacter++ = '\n'; /* New line */
+ char *replacementString = "";
+ do {
+ if (*str == '@') {
+ str++;
+ if (*(curCharacter - 1) != '\n') /* New line */
+ *curCharacter++ = ' ';
+
+ if (*str == 'p') /* '@p' in the source string is replaced by the champion name followed by a space */
+ replacementString = _vm->_championMan->_champions[_vm->ordinalToIndex(_vm->_championMan->_actingChampionOrdinal)]._name;
+
+ *curCharacter = '\0';
+ strcat(outputString, replacementString);
+ curCharacter += strlen(replacementString);
+ *curCharacter++ = ' ';
+ } else {
+ *curCharacter++ = *str;
+ }
+ } while (*str++);
+ *curCharacter = '\0';
+
+ if (outputString[1]) /* If the string is not empty (the first character is a new line \n) */
+ _vm->_textMan->printMessage(k4_ColorCyan, outputString);
+}
+
+void MenuMan::processCommands116To119_setActingChampion(uint16 champIndex) {
+ static ActionSet actionSets[44] = {
+ /* { ActionIndices[0], ActionIndices[1], ActionIndices[2], ActionProperties[0], ActionProperties[1], Useless } */
+ ActionSet(255, 255, 255, 0x00, 0x00),
+ ActionSet(27, 43, 35, 0x00, 0x00),
+ ActionSet(6, 7, 8, 0x00, 0x00),
+ ActionSet(0, 0, 0, 0x00, 0x00),
+ ActionSet(0, 0, 0, 0x00, 0x00),
+ ActionSet(13, 255, 255, 0x00, 0x00),
+ ActionSet(13, 20, 255, 0x87, 0x00),
+ ActionSet(13, 23, 255, 0x83, 0x00),
+ ActionSet(28, 41, 22, 0x02, 0x83),
+ ActionSet(16, 2, 23, 0x00, 0x84),
+ ActionSet(2, 25, 20, 0x02, 0x86),
+ ActionSet(17, 41, 34, 0x03, 0x05),
+ ActionSet(42, 9, 28, 0x00, 0x02),
+ ActionSet(13, 17, 2, 0x02, 0x03),
+ ActionSet(16, 17, 15, 0x01, 0x05),
+ ActionSet(28, 17, 25, 0x01, 0x05),
+ ActionSet(2, 25, 15, 0x05, 0x06),
+ ActionSet(9, 2, 29, 0x02, 0x05),
+ ActionSet(16, 29, 24, 0x02, 0x04),
+ ActionSet(13, 15, 19, 0x05, 0x07),
+ ActionSet(13, 2, 25, 0x00, 0x05),
+ ActionSet(2, 29, 19, 0x03, 0x08),
+ ActionSet(13, 30, 31, 0x02, 0x04),
+ ActionSet(13, 31, 25, 0x03, 0x06),
+ ActionSet(42, 30, 255, 0x00, 0x00),
+ ActionSet(0, 0, 0, 0x00, 0x00),
+ ActionSet(42, 9, 255, 0x00, 0x00),
+ ActionSet(32, 255, 255, 0x00, 0x00),
+ ActionSet(37, 33, 36, 0x82, 0x03),
+ ActionSet(37, 33, 34, 0x83, 0x84),
+ ActionSet(17, 38, 21, 0x80, 0x83),
+ ActionSet(13, 21, 34, 0x83, 0x84),
+ ActionSet(36, 37, 41, 0x02, 0x03),
+ ActionSet(13, 23, 39, 0x82, 0x84),
+ ActionSet(13, 17, 40, 0x00, 0x83),
+ ActionSet(17, 36, 38, 0x03, 0x84),
+ ActionSet(4, 255, 255, 0x00, 0x00),
+ ActionSet(5, 255, 255, 0x00, 0x00),
+ ActionSet(11, 255, 255, 0x00, 0x00),
+ ActionSet(10, 255, 255, 0x00, 0x00),
+ ActionSet(42, 9, 255, 0x00, 0x00),
+ ActionSet(1, 12, 255, 0x02, 0x00),
+ ActionSet(42, 255, 255, 0x00, 0x00),
+ ActionSet(6, 11, 255, 0x80, 0x00)
+ };
+
+ Champion *curChampion = &_vm->_championMan->_champions[champIndex];
+ if (getFlag(curChampion->_attributes, kDMAttributeDisableAction) || !curChampion->_currHealth)
+ return;
+
+ uint16 actionSetIndex;
+ Thing slotActionThing = curChampion->_slots[kDMSlotActionHand];
+
+ if (slotActionThing == Thing::_none)
+ actionSetIndex = 2; /* Actions Punch, Kick and War Cry */
+ else {
+ actionSetIndex = _vm->_dungeonMan->_objectInfos[_vm->_dungeonMan->getObjectInfoIndex(slotActionThing)]._actionSetIndex;
+ if (actionSetIndex == 0)
+ return;
+ }
+
+ ActionSet *actionSet = &actionSets[actionSetIndex];
+ _vm->_championMan->_actingChampionOrdinal = _vm->indexToOrdinal(champIndex);
+ setActionList(actionSet);
+ _actionAreaContainsIcons = false;
+ setFlag(curChampion->_attributes, kDMAttributeActionHand);
+ _vm->_championMan->drawChampionState((ChampionIndex)champIndex);
+ drawActionArea();
+ drawActionArea();
+}
+
+void MenuMan::setActionList(ActionSet *actionSet) {
+ _actionList._actionIndices[0] = (ChampionAction)actionSet->_actionIndices[0];
+ _actionList._minimumSkillLevel[0] = 1;
+ uint16 nextAvailableActionListIndex = 1;
+ for (uint16 idx = 1; idx < 3; idx++) {
+ uint16 actionIndex = actionSet->_actionIndices[idx];
+
+ if (actionIndex == kDMActionNone)
+ continue;
+
+ uint16 minimumSkillLevel = actionSet->_actionProperties[idx - 1];
+ if (getFlag(minimumSkillLevel, k0x0080_actionRequiresCharge) && !getActionObjectChargeCount())
+ continue;
+
+ clearFlag(minimumSkillLevel, k0x0080_actionRequiresCharge);
+ if (_vm->_championMan->getSkillLevel(_vm->ordinalToIndex(_vm->_championMan->_actingChampionOrdinal), _actionSkillIndex[actionIndex]) >= minimumSkillLevel) {
+ _actionList._actionIndices[nextAvailableActionListIndex] = (ChampionAction)actionIndex;
+ _actionList._minimumSkillLevel[nextAvailableActionListIndex] = minimumSkillLevel;
+ nextAvailableActionListIndex++;
+ }
+ }
+ _actionCount = nextAvailableActionListIndex;
+
+ for (uint16 idx = nextAvailableActionListIndex; idx < 3; idx++)
+ _actionList._actionIndices[idx] = kDMActionNone;
+}
+
+int16 MenuMan::getActionObjectChargeCount() {
+ Thing slotActionThing = _vm->_championMan->_champions[_vm->ordinalToIndex(_vm->_championMan->_actingChampionOrdinal)]._slots[kDMSlotActionHand];
+ Junk *junkData = (Junk *)_vm->_dungeonMan->getThingData(slotActionThing);
+ switch (slotActionThing.getType()) {
+ case k5_WeaponThingType:
+ return ((Weapon *)junkData)->getChargeCount();
+ case k6_ArmourThingType:
+ return ((Armour *)junkData)->getChargeCount();
+ case k10_JunkThingType:
+ return junkData->getChargeCount();
+ default:
+ return 1;
+ }
+}
+
+void MenuMan::drawActionDamage(int16 damage) {
+ static const Box actionAreaMediumDamage(242, 305, 81, 117);
+ static const Box actionAreaSmallDamage(251, 292, 81, 117);
+
+ _vm->_eventMan->showMouse();
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ _vm->_displayMan->fillScreenBox(_boxActionArea, k0_ColorBlack);
+ if (damage < 0) {
+ static const char *messagesEN[2] = {"CAN'T REACH", "NEED AMMO"};
+ static const char *messagesDE[2] = {"ZU WEIT WEG", "MEHR MUNITION"};
+ static const char *messagesFR[2] = {"TROP LOIN", "SANS MUNITION"};
+ static int16 posEN[2] = {242, 248};
+ static int16 posDE[2] = {242, 236};
+ static int16 posFR[2] = {248, 236};
+ const char **message;
+ int16 *pos;
+ switch (_vm->getGameLanguage()) { // localized
+ case Common::DE_DEU:
+ message = messagesDE;
+ pos = posDE;
+ break;
+ case Common::FR_FRA:
+ message = messagesFR;
+ pos = posFR;
+ break;
+ default:
+ message = messagesEN;
+ pos = posEN;
+ break;
+ }
+
+ const char *displayString;
+ int16 textPosX;
+ if (damage == kM1_damageCantReach) {
+ textPosX = pos[0];
+ displayString = message[0];
+ } else {
+ textPosX = pos[1];
+ displayString = message[1];
+ }
+ _vm->_textMan->printToLogicalScreen(textPosX, 100, k4_ColorCyan, k0_ColorBlack, displayString);
+ } else {
+ int16 byteWidth;
+ byte *blitBitmap;
+ const Box *blitBox;
+ int16 displayHeight;
+ if (damage > 40) {
+ blitBox = &_boxActionArea3ActionMenu;
+ blitBitmap = _vm->_displayMan->getNativeBitmapOrGraphic(k14_damageToCreatureIndice);
+ byteWidth = k48_byteWidth;
+ displayHeight = 45;
+ } else {
+ uint16 derivedBitmapIndex;
+ int16 destPixelWidth;
+ if (damage > 15) {
+ derivedBitmapIndex = k2_DerivedBitmapDamageToCreatureMedium;
+ destPixelWidth = 64;
+ byteWidth = k32_byteWidth;
+ blitBox = &actionAreaMediumDamage;
+ } else {
+ derivedBitmapIndex = k3_DerivedBitmapDamageToCreatureSmall;
+ destPixelWidth = 42;
+ byteWidth = k24_byteWidth;
+ blitBox = &actionAreaSmallDamage;
+ }
+ displayHeight = 37;
+ if (!_vm->_displayMan->isDerivedBitmapInCache(derivedBitmapIndex)) {
+ byte *nativeBitmap = _vm->_displayMan->getNativeBitmapOrGraphic(k14_damageToCreatureIndice);
+ blitBitmap = _vm->_displayMan->getDerivedBitmap(derivedBitmapIndex);
+ _vm->_displayMan->blitToBitmapShrinkWithPalChange(nativeBitmap, blitBitmap, 96, 45, destPixelWidth, 37, _vm->_displayMan->_palChangesNoChanges);
+ _vm->_displayMan->addDerivedBitmap(derivedBitmapIndex);
+ } else {
+ blitBitmap = _vm->_displayMan->getDerivedBitmap(derivedBitmapIndex);
+ }
+ }
+ _vm->_displayMan->blitToScreen(blitBitmap, blitBox, byteWidth, kM1_ColorNoTransparency, displayHeight);
+ /* Convert damage value to string */
+ uint16 charIndex = 5;
+ int16 textPosX = 274;
+ char scoreString[6];
+ scoreString[5] = '\0';
+ do {
+ scoreString[--charIndex] = '0' + (damage % 10);
+ textPosX -= 3;
+ } while (damage /= 10);
+ _vm->_textMan->printToLogicalScreen(textPosX, 100, k4_ColorCyan, k0_ColorBlack, &scoreString[charIndex]);
+ }
+ _vm->_eventMan->hideMouse();
+}
+}
diff --git a/engines/dm/menus.h b/engines/dm/menus.h
new file mode 100644
index 0000000000..11232486d5
--- /dev/null
+++ b/engines/dm/menus.h
@@ -0,0 +1,135 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+-* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_MENUS_H
+#define DM_MENUS_H
+
+#include "dm/dm.h"
+#include "dm/champion.h"
+#include "dm/dungeonman.h"
+
+namespace DM {
+
+#define kM1_damageCantReach -1 // @ CM1_DAMAGE_CANT_REACH
+#define kM2_damageNoAmmunition -2 // @ CM2_DAMAGE_NO_AMMUNITION
+#define k2_SpellAreaAvailableSymbols 2 // @ C2_SPELL_AREA_AVAILABLE_SYMBOLS
+#define k3_SpellAreaChampionSymbols 3 // @ C3_SPELL_AREA_CHAMPION_SYMBOLS
+
+#define k0x0080_actionRequiresCharge 0x0080 // @ MASK0x0080_ACTION_REQUIRES_CHARGE
+#define k0x8000_hitNonMaterialCreatures 0x8000 // @ MASK0x8000_HIT_NON_MATERIAL_CREATURES
+
+class ActionList {
+public:
+ byte _minimumSkillLevel[3]; /* Bit 7: requires charge, Bit 6-0: minimum skill level. */
+ ChampionAction _actionIndices[3];
+ ActionList() { resetToZero(); }
+ void resetToZero() {
+ for (uint16 i = 0; i < 3; ++i) {
+ _minimumSkillLevel[i] = 0;
+ _actionIndices[i] = (ChampionAction)0;
+ }
+ }
+}; // @ ACTION_LIST
+
+class ActionSet {
+public:
+ byte _actionIndices[3]; /* 1 byte of padding inserted by compiler on Atari ST, not on Amiga */
+ byte _actionProperties[2]; /* Bit 7: requires charge, Bit 6-0: minimum skill level */
+ ActionSet(byte a1, byte a2, byte a3, byte b1, byte b2) {
+ _actionIndices[0] = a1;
+ _actionIndices[1] = a2;
+ _actionIndices[2] = a3;
+ _actionProperties[0] = b1;
+ _actionProperties[1] = b2;
+ }
+}; // @ ACTION_SET
+
+class MenuMan {
+ DMEngine *_vm;
+public:
+ explicit MenuMan(DMEngine *vm);
+ ~MenuMan();
+
+ bool _refreshActionArea; // @ G0508_B_RefreshActionArea
+ bool _actionAreaContainsIcons; // @ G0509_B_ActionAreaContainsIcons
+ int16 _actionDamage; // @ G0513_i_ActionDamage
+ ActionList _actionList; // @ G0713_s_ActionList
+ byte *_bitmapSpellAreaLine; // @ K0072_puc_Bitmap_SpellAreaLine
+ byte *_bitmapSpellAreaLines; // @ K0073_puc_Bitmap_SpellAreaLines
+ Thing _actionTargetGroupThing; // @ G0517_T_ActionTargetGroupThing
+ uint16 _actionCount; // @ G0507_ui_ActionCount
+
+ void clearActingChampion(); // @ F0388_MENUS_ClearActingChampion
+ void drawActionIcon(ChampionIndex championIndex); // @ F0386_MENUS_DrawActionIcon
+
+ void drawMovementArrows(); // @ F0395_MENUS_DrawMovementArrows
+ void drawDisabledMenu(); // @ F0456_START_DrawDisabledMenus
+ void refreshActionAreaAndSetChampDirMaxDamageReceived(); // @ F0390_MENUS_RefreshActionAreaAndSetChampionDirectionMaximumDamageReceived
+ void drawActionArea(); // @ F0387_MENUS_DrawActionArea
+ const char *getActionName(ChampionAction actionIndex); // @ F0384_MENUS_GetActionName
+ void drawSpellAreaControls(ChampionIndex champIndex); // @ F0393_MENUS_DrawSpellAreaControls
+ void buildSpellAreaLine(int16 spellAreaBitmapLine);// @ F0392_MENUS_BuildSpellAreaLine
+ void setMagicCasterAndDrawSpellArea(ChampionIndex champIndex); // @ F0394_MENUS_SetMagicCasterAndDrawSpellArea
+ void drawEnabledMenus(); // @ F0457_START_DrawEnabledMenus_CPSF
+ int16 getClickOnSpellCastResult(); // @ F0408_MENUS_GetClickOnSpellCastResult
+ int16 getChampionSpellCastResult(uint16 champIndex); // @ F0412_MENUS_GetChampionSpellCastResult
+ Spell *getSpellFromSymbols(byte *symbols); // @ F0409_MENUS_GetSpellFromSymbols
+ void menusPrintSpellFailureMessage(Champion *champ, uint16 failureType, uint16 skillIndex); // @ F0410_MENUS_PrintSpellFailureMessage
+ Potion *getEmptyFlaskInHand(Champion *champ, Thing *potionThing); // @ F0411_MENUS_GetEmptyFlaskInHand
+ void createEvent70_light(int16 lightPower, int16 ticks); // @ F0404_MENUS_CreateEvent70_Light
+ bool isPartySpellOrFireShieldSuccessful(Champion *champ, bool spellShield, uint16 ticks, bool useMana); // @ F0403_MENUS_IsPartySpellOrFireShieldSuccessful
+ void drawAvailableSymbols(uint16 symbolStep); // @ F0397_MENUS_DrawAvailableSymbols
+ void drawChampionSymbols(Champion *champ); // @ F0398_MENUS_DrawChampionSymbols
+ void addChampionSymbol(int16 symbolIndex); // @ F0399_MENUS_AddChampionSymbol
+ void deleteChampionSymbol(); // @ F0400_MENUS_DeleteChampionSymbol
+ bool didClickTriggerAction(int16 actionListIndex); // @ F0391_MENUS_DidClickTriggerAction
+ bool isActionPerformed(uint16 champIndex, int16 actionIndex); // @ F0407_MENUS_IsActionPerformed
+ void setChampionDirectionToPartyDirection(Champion *champ); // @ F0406_MENUS_SetChampionDirectionToPartyDirection
+ void decrementCharges(Champion *champ); // @ F0405_MENUS_DecrementCharges
+ bool isMeleeActionPerformed(int16 champIndex, Champion *champ, int16 actionIndex, int16 targetMapX,
+ int16 targetMapY, int16 skillIndex); // @ F0402_MENUS_IsMeleeActionPerformed
+ bool isGroupFrightenedByAction(int16 champIndex, uint16 actionIndex, int16 mapX, int16 mapY); // @ F0401_MENUS_IsGroupFrightenedByAction
+ void printMessageAfterReplacements(const char *str); // @ F0381_MENUS_PrintMessageAfterReplacements
+ void processCommands116To119_setActingChampion(uint16 champIndex); // @ F0389_MENUS_ProcessCommands116To119_SetActingChampion
+ void setActionList(ActionSet *actionSet); // @ F0383_MENUS_SetActionList
+ int16 getActionObjectChargeCount(); // @ F0382_MENUS_GetActionObjectChargeCount
+ void drawActionDamage(int16 damage); // @ F0385_MENUS_DrawActionDamage
+
+ Box _boxActionArea3ActionMenu; // @ G0499_s_Graphic560_Box_ActionArea3ActionsMenu
+ Box _boxActionArea2ActionMenu; // @ G0500_s_Graphic560_Box_ActionArea2ActionsMenu
+ Box _boxActionArea1ActionMenu; // @ G0501_s_Graphic560_Box_ActionArea1ActionMenu
+ Box _boxActionArea; // @ G0001_s_Graphic562_Box_ActionArea
+ Box _boxSpellArea;
+ unsigned char _actionSkillIndex[44]; // @ G0496_auc_Graphic560_ActionSkillIndex
+ unsigned char _actionDisabledTicks[44];
+
+ void initConstants();
+};
+
+}
+
+#endif // DM_MENUS_H
diff --git a/engines/dm/module.mk b/engines/dm/module.mk
new file mode 100644
index 0000000000..d495284da5
--- /dev/null
+++ b/engines/dm/module.mk
@@ -0,0 +1,63 @@
+# 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 2
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#
+#
+#
+# Based on the Reverse Engineering work of Christophe Fontanel,
+# maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+#
+
+
+MODULE := engines/dm
+
+MODULE_OBJS := \
+ champion.o \
+ console.o \
+ detection.o \
+ dialog.o \
+ dm.o \
+ dmglobals.o \
+ dungeonman.o \
+ eventman.o \
+ gfx.o \
+ group.o \
+ inventory.o \
+ loadsave.o \
+ lzw.o \
+ menus.o \
+ movesens.o \
+ objectman.o \
+ projexpl.o \
+ sounds.o \
+ text.o \
+ timeline.o
+
+MODULE_DIRS += \
+ engines/dm
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_DM), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
+
diff --git a/engines/dm/movesens.cpp b/engines/dm/movesens.cpp
new file mode 100644
index 0000000000..bca61fe5bb
--- /dev/null
+++ b/engines/dm/movesens.cpp
@@ -0,0 +1,1010 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+
+#include "dm/movesens.h"
+#include "dm/champion.h"
+#include "dm/inventory.h"
+#include "dm/dungeonman.h"
+#include "dm/objectman.h"
+#include "dm/timeline.h"
+#include "dm/group.h"
+#include "dm/projexpl.h"
+#include "dm/text.h"
+#include "dm/sounds.h"
+
+
+namespace DM {
+
+MovesensMan::MovesensMan(DMEngine *vm) : _vm(vm) {
+ _moveResultMapX = 0;
+ _moveResultMapY = 0;
+ _moveResultMapIndex = 0;
+ _moveResultDir = 0;
+ _moveResultCell = 0;
+ _useRopeToClimbDownPit = false;
+ _sensorRotationEffect = 0;
+ _sensorRotationEffMapX = 0;
+ _sensorRotationEffMapY = 0;
+ _sensorRotationEffCell = 0;
+}
+
+bool MovesensMan::sensorIsTriggeredByClickOnWall(int16 mapX, int16 mapY, uint16 cellParam) {
+ bool atLeastOneSensorWasTriggered = false;
+ Thing leaderHandObject = _vm->_championMan->_leaderHandObject;
+ int16 sensorCountToProcessPerCell[4];
+ for (int16 i = k0_CellNorthWest; i < k3_CellSouthWest + 1; i++)
+ sensorCountToProcessPerCell[i] = 0;
+
+ Thing squareFirstThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
+ Thing thingBeingProcessed = squareFirstThing;
+ while (thingBeingProcessed != Thing::_endOfList) {
+ ThingType thingType = thingBeingProcessed.getType();
+ if (thingType == k3_SensorThingType)
+ sensorCountToProcessPerCell[thingBeingProcessed.getCell()]++;
+ else if (thingType >= k4_GroupThingType)
+ break;
+
+ thingBeingProcessed = _vm->_dungeonMan->getNextThing(thingBeingProcessed);
+ }
+ for (Thing thingBeingProcessed = squareFirstThing; thingBeingProcessed != Thing::_endOfList; thingBeingProcessed = _vm->_dungeonMan->getNextThing(thingBeingProcessed)) {
+ Thing lastProcessedThing = thingBeingProcessed;
+ uint16 ProcessedThingType = thingBeingProcessed.getType();
+ if (ProcessedThingType == k3_SensorThingType) {
+ int16 cellIdx = thingBeingProcessed.getCell();
+ sensorCountToProcessPerCell[cellIdx]--;
+ Sensor *currentSensor = (Sensor *)_vm->_dungeonMan->getThingData(thingBeingProcessed);
+ SensorType processedSensorType = currentSensor->getType();
+ if (processedSensorType == k0_SensorDisabled)
+ continue;
+
+ if ((_vm->_championMan->_leaderIndex == kDMChampionNone) && (processedSensorType != k127_SensorWallChampionPortrait))
+ continue;
+
+ if (cellIdx != cellParam)
+ continue;
+
+ bool doNotTriggerSensor;
+ int16 sensorData = 0;
+ int16 sensorEffect = 0;
+
+ sensorData = currentSensor->getData();
+ sensorEffect = currentSensor->getAttrEffectA();
+
+ switch (processedSensorType) {
+ case k1_SensorWallOrnClick:
+ doNotTriggerSensor = false;
+ if (currentSensor->getAttrEffectA() == k3_SensorEffHold)
+ continue;
+ break;
+ case k2_SensorWallOrnClickWithAnyObj:
+ doNotTriggerSensor = (_vm->_championMan->_leaderEmptyHanded != currentSensor->getAttrRevertEffectA());
+ break;
+ case k17_SensorWallOrnClickWithSpecObjRemovedSensor:
+ case k11_SensorWallOrnClickWithSpecObjRemovedRotateSensors:
+ if (sensorCountToProcessPerCell[cellIdx]) /* If the sensor is not the last one of its type on the cell */
+ continue;
+ // No break on purpose
+ case k3_SensorWallOrnClickWithSpecObj:
+ case k4_SensorWallOrnClickWithSpecObjRemoved:
+ doNotTriggerSensor = ((sensorData == _vm->_objectMan->getObjectType(leaderHandObject)) == currentSensor->getAttrRevertEffectA());
+ if (!doNotTriggerSensor && (processedSensorType == k17_SensorWallOrnClickWithSpecObjRemovedSensor)) {
+ if (lastProcessedThing == thingBeingProcessed) /* If the sensor is the only one of its type on the cell */
+ break;
+ Sensor *lastSensor = (Sensor *)_vm->_dungeonMan->getThingData(lastProcessedThing);
+ lastSensor->setNextThing(currentSensor->getNextThing());
+ currentSensor->setNextThing(Thing::_none);
+ thingBeingProcessed = lastProcessedThing;
+ }
+
+ if (!doNotTriggerSensor && (processedSensorType == k11_SensorWallOrnClickWithSpecObjRemovedRotateSensors))
+ triggerLocalEffect(k2_SensorEffToggle, mapX, mapY, cellIdx); /* This will cause a rotation of the sensors at the specified cell on the specified square after all sensors have been processed */
+
+ break;
+ case k12_SensorWallObjGeneratorRotateSensors:
+ if (sensorCountToProcessPerCell[cellIdx]) /* If the sensor is not the last one of its type on the cell */
+ continue;
+
+ doNotTriggerSensor = !_vm->_championMan->_leaderEmptyHanded;
+ if (!doNotTriggerSensor)
+ triggerLocalEffect(k2_SensorEffToggle, mapX, mapY, cellIdx); /* This will cause a rotation of the sensors at the specified cell on the specified square after all sensors have been processed */
+ break;
+ case k13_SensorWallSingleObjStorageRotateSensors:
+ if (_vm->_championMan->_leaderEmptyHanded) {
+ leaderHandObject = getObjectOfTypeInCell(mapX, mapY, cellIdx, sensorData);
+ if (leaderHandObject == Thing::_none)
+ continue;
+
+ _vm->_dungeonMan->unlinkThingFromList(leaderHandObject, Thing(0), mapX, mapY);
+ _vm->_championMan->putObjectInLeaderHand(leaderHandObject, true);
+ } else {
+ if ((_vm->_objectMan->getObjectType(leaderHandObject) != sensorData) || (getObjectOfTypeInCell(mapX, mapY, cellIdx, sensorData) != Thing::_none))
+ continue;
+
+ _vm->_championMan->getObjectRemovedFromLeaderHand();
+ _vm->_dungeonMan->linkThingToList(thingWithNewCell(leaderHandObject, cellIdx), Thing(0), mapX, mapY);
+ leaderHandObject = Thing::_none;
+ }
+ triggerLocalEffect(k2_SensorEffToggle, mapX, mapY, cellIdx); /* This will cause a rotation of the sensors at the specified cell on the specified square after all sensors have been processed */
+ if ((sensorEffect == k3_SensorEffHold) && !_vm->_championMan->_leaderEmptyHanded)
+ doNotTriggerSensor = true;
+ else
+ doNotTriggerSensor = false;
+
+ break;
+ case k16_SensorWallObjExchanger: {
+ if (sensorCountToProcessPerCell[cellIdx]) /* If the sensor is not the last one of its type on the cell */
+ continue;
+
+ Thing thingOnSquare = _vm->_dungeonMan->getSquareFirstObject(mapX, mapY);
+ if ((_vm->_objectMan->getObjectType(leaderHandObject) != sensorData) || (thingOnSquare == Thing::_none))
+ continue;
+
+ _vm->_dungeonMan->unlinkThingFromList(thingOnSquare, Thing(0), mapX, mapY);
+ _vm->_championMan->getObjectRemovedFromLeaderHand();
+ _vm->_dungeonMan->linkThingToList(thingWithNewCell(leaderHandObject, cellIdx), Thing(0), mapX, mapY);
+ _vm->_championMan->putObjectInLeaderHand(thingOnSquare, true);
+ doNotTriggerSensor = false;
+ }
+ break;
+ case k127_SensorWallChampionPortrait:
+ _vm->_championMan->addCandidateChampionToParty(sensorData);
+ continue;
+ break;
+ default:
+ continue;
+ break;
+ }
+
+ if (sensorEffect == k3_SensorEffHold) {
+ sensorEffect = doNotTriggerSensor ? k1_SensorEffClear : k0_SensorEffSet;
+ doNotTriggerSensor = false;
+ }
+ if (!doNotTriggerSensor) {
+ atLeastOneSensorWasTriggered = true;
+ if (currentSensor->getAttrAudibleA())
+ _vm->_sound->requestPlay(k01_soundSWITCH, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, k1_soundModePlayIfPrioritized);
+
+ if (!_vm->_championMan->_leaderEmptyHanded && ((processedSensorType == k4_SensorWallOrnClickWithSpecObjRemoved) || (processedSensorType == k11_SensorWallOrnClickWithSpecObjRemovedRotateSensors) || (processedSensorType == k17_SensorWallOrnClickWithSpecObjRemovedSensor))) {
+ Thing *leaderThing = (Thing *)_vm->_dungeonMan->getThingData(leaderHandObject);
+ *leaderThing = Thing::_none;
+ _vm->_championMan->getObjectRemovedFromLeaderHand();
+ leaderHandObject = Thing::_none;
+ } else if (_vm->_championMan->_leaderEmptyHanded
+ && (processedSensorType == k12_SensorWallObjGeneratorRotateSensors)) {
+ leaderHandObject = _vm->_dungeonMan->getObjForProjectileLaucherOrObjGen(sensorData);
+ if (leaderHandObject != Thing::_none)
+ _vm->_championMan->putObjectInLeaderHand(leaderHandObject, true);
+ }
+ triggerEffect(currentSensor, sensorEffect, mapX, mapY, cellIdx);
+ }
+ continue;
+ }
+ if (ProcessedThingType >= k4_GroupThingType)
+ break;
+ }
+ processRotationEffect();
+ return atLeastOneSensorWasTriggered;
+}
+
+bool MovesensMan::getMoveResult(Thing thing, int16 mapX, int16 mapY, int16 destMapX, int16 destMapY) {
+ ThingType thingType = kM1_PartyThingType;
+ int16 traversedPitCount = 0;
+ uint16 moveGroupResult = 0;
+ uint16 thingCell = 0;
+ bool thingLevitates = false;
+
+ if (thing != Thing::_party) {
+ thingType = thing.getType();
+ thingCell = thing.getCell();
+ thingLevitates = isLevitating(thing);
+ }
+ /* If moving the party or a creature on the party map from a dungeon square then check for a projectile impact */
+ if ((mapX >= 0) && ((thing == Thing::_party) || ((thingType == k4_GroupThingType) && (_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex)))) {
+ if (moveIsKilledByProjectileImpact(mapX, mapY, destMapX, destMapY, thing))
+ return true; /* The specified group thing cannot be moved because it was killed by a projectile impact */
+ }
+
+ uint16 mapIndexSource = 0;
+ uint16 mapIndexDestination = 0;
+ bool groupOnPartyMap = false;
+ bool partySquare = false;
+ bool audibleTeleporter = false;
+
+ if (destMapX >= 0) {
+ mapIndexSource = mapIndexDestination = _vm->_dungeonMan->_currMapIndex;
+ groupOnPartyMap = (mapIndexSource == _vm->_dungeonMan->_partyMapIndex) && (mapX >= 0);
+ uint16 direction = 0;
+ bool fallKilledGroup = false;
+ bool drawDungeonViewWhileFalling = false;
+ bool destinationIsTeleporterTarget = false;
+ int16 requiredTeleporterScope;
+ if (thing == Thing::_party) {
+ _vm->_dungeonMan->_partyMapX = destMapX;
+ _vm->_dungeonMan->_partyMapY = destMapY;
+ requiredTeleporterScope = k0x0002_TelepScopeObjOrParty;
+ drawDungeonViewWhileFalling = !_vm->_inventoryMan->_inventoryChampionOrdinal && !_vm->_championMan->_partyIsSleeping;
+ direction = _vm->_dungeonMan->_partyDir;
+ } else if (thingType == k4_GroupThingType)
+ requiredTeleporterScope = k0x0001_TelepScopeCreatures;
+ else
+ requiredTeleporterScope = (k0x0001_TelepScopeCreatures | k0x0002_TelepScopeObjOrParty);
+
+ if (thingType == k14_ProjectileThingType) {
+ Teleporter *L0712_ps_Teleporter = (Teleporter *)_vm->_dungeonMan->getThingData(thing);
+ _moveResultDir = (_vm->_timeline->_events[((Projectile *)L0712_ps_Teleporter)->_eventIndex])._C._projectile.getDir();
+ }
+
+ int16 destinationSquareData = 0;
+ /* No more than 1000 chained moves at once (in a chain of teleporters and pits for example) */
+ for (int16 chainedMoveCount = 1000; --chainedMoveCount; ) {
+ destinationSquareData = _vm->_dungeonMan->_currMapData[destMapX][destMapY];
+ SquareType destinationSquareType = Square(destinationSquareData).getType();
+ if (destinationSquareType == k5_ElementTypeTeleporter) {
+ if (!getFlag(destinationSquareData, k0x0008_TeleporterOpen))
+ break;
+
+ Teleporter *teleporter = (Teleporter *)_vm->_dungeonMan->getSquareFirstThingData(destMapX, destMapY);
+ if ((teleporter->getScope() == k0x0001_TelepScopeCreatures) && (thingType != k4_GroupThingType))
+ break;
+
+ if ((requiredTeleporterScope != (k0x0001_TelepScopeCreatures | k0x0002_TelepScopeObjOrParty)) && !getFlag(teleporter->getScope(), requiredTeleporterScope))
+ break;
+
+ destinationIsTeleporterTarget = (destMapX == teleporter->getTargetMapX()) && (destMapY == teleporter->getTargetMapY()) && (mapIndexDestination == teleporter->getTargetMapIndex());
+ destMapX = teleporter->getTargetMapX();
+ destMapY = teleporter->getTargetMapY();
+ audibleTeleporter = teleporter->isAudible();
+ _vm->_dungeonMan->setCurrentMap(mapIndexDestination = teleporter->getTargetMapIndex());
+ if (thing == Thing::_party) {
+ _vm->_dungeonMan->_partyMapX = destMapX;
+ _vm->_dungeonMan->_partyMapY = destMapY;
+ if (teleporter->isAudible())
+ _vm->_sound->requestPlay(k17_soundBUZZ, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, k0_soundModePlayImmediately);
+
+ drawDungeonViewWhileFalling = true;
+ if (teleporter->getAbsoluteRotation())
+ _vm->_championMan->setPartyDirection(teleporter->getRotation());
+ else
+ _vm->_championMan->setPartyDirection(normalizeModulo4(_vm->_dungeonMan->_partyDir + teleporter->getRotation()));
+ } else {
+ if (thingType == k4_GroupThingType) {
+ if (teleporter->isAudible())
+ _vm->_sound->requestPlay(k17_soundBUZZ, destMapX, destMapY, k1_soundModePlayIfPrioritized);
+
+ moveGroupResult = getTeleporterRotatedGroupResult(teleporter, thing, mapIndexSource);
+ } else {
+ if (thingType == k14_ProjectileThingType)
+ thing = getTeleporterRotatedProjectileThing(teleporter, thing);
+ else if (!(teleporter->getAbsoluteRotation()) && (mapX != -2))
+ thing = thingWithNewCell(thing, normalizeModulo4(thing.getCell() + teleporter->getRotation()));
+ }
+ }
+ if (destinationIsTeleporterTarget)
+ break;
+ } else {
+ if ((destinationSquareType == k2_ElementTypePit) && !thingLevitates && getFlag(destinationSquareData, k0x0008_PitOpen) && !getFlag(destinationSquareData, k0x0001_PitImaginary)) {
+ if (drawDungeonViewWhileFalling && !_useRopeToClimbDownPit) {
+ drawDungeonViewWhileFalling = true;
+ if (traversedPitCount) {
+ _vm->_dungeonMan->setCurrentMapAndPartyMap(mapIndexDestination);
+ _vm->_displayMan->loadCurrentMapGraphics();
+ }
+ traversedPitCount++;
+ _vm->_displayMan->drawDungeon(_vm->_dungeonMan->_partyDir, destMapX, destMapY); /* BUG0_28 When falling through multiple pits the dungeon view is updated to show each traversed map but the graphics used for creatures, wall and floor ornaments may not be correct. The dungeon view is drawn for each map by using the graphics loaded for the source map. Therefore the graphics for creatures, wall and floor ornaments may not look like what they should */
+ /* BUG0_71 Some timings are too short on fast computers. When the party falls in a series of pits, the dungeon view is refreshed too quickly because the execution speed is not limited */
+ /* BUG0_01 While drawing creatures the engine will read invalid ACTIVE_GROUP data in _vm->_groupMan->_g375_activeGroups because the data is for the creatures on the source map and not the map being drawn. The only consequence is that creatures may be drawn with incorrect bitmaps and/or directions */
+ }
+ mapIndexDestination = _vm->_dungeonMan->getLocationAfterLevelChange(mapIndexDestination, 1, &destMapX, &destMapY);
+ _vm->_dungeonMan->setCurrentMap(mapIndexDestination);
+ if (thing == Thing::_party) {
+ _vm->_dungeonMan->_partyMapX = destMapX;
+ _vm->_dungeonMan->_partyMapY = destMapY;
+ if (_vm->_championMan->_partyChampionCount > 0) {
+ if (_useRopeToClimbDownPit) {
+ Champion *curChampion = _vm->_championMan->_champions;
+ for (int16 championIdx = kDMChampionFirst; championIdx < _vm->_championMan->_partyChampionCount; championIdx++, curChampion++) {
+ if (curChampion->_currHealth)
+ _vm->_championMan->decrementStamina(championIdx, ((curChampion->_load * 25) / _vm->_championMan->getMaximumLoad(curChampion)) + 1);
+ }
+ } else if (_vm->_championMan->getDamagedChampionCount(20, kDMWoundLegs | kDMWoundFeet, kDMAttackTypeSelf))
+ _vm->_sound->requestPlay(k06_soundSCREAM, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, k0_soundModePlayImmediately);
+ }
+ _useRopeToClimbDownPit = false;
+ } else if (thingType == k4_GroupThingType) {
+ _vm->_dungeonMan->setCurrentMap(mapIndexSource);
+ uint16 outcome = _vm->_groupMan->getDamageAllCreaturesOutcome((Group *)_vm->_dungeonMan->getThingData(thing), mapX, mapY, 20, false);
+ _vm->_dungeonMan->setCurrentMap(mapIndexDestination);
+ fallKilledGroup = (outcome == k2_outcomeKilledAllCreaturesInGroup);
+ if (fallKilledGroup)
+ break;
+
+ if (outcome == k1_outcomeKilledSomeCreaturesInGroup)
+ _vm->_groupMan->dropMovingCreatureFixedPossession(thing, destMapX, destMapY);
+ }
+ } else if ((destinationSquareType == k3_ElementTypeStairs) && (thing != Thing::_party) && (thingType != k14_ProjectileThingType)) {
+ if (!getFlag(destinationSquareData, k0x0004_StairsUp)) {
+ mapIndexDestination = _vm->_dungeonMan->getLocationAfterLevelChange(mapIndexDestination, 1, &destMapX, &destMapY);
+ _vm->_dungeonMan->setCurrentMap(mapIndexDestination);
+ }
+ direction = _vm->_dungeonMan->getStairsExitDirection(destMapX, destMapY);
+ destMapX += _vm->_dirIntoStepCountEast[direction], destMapY += _vm->_dirIntoStepCountNorth[direction];
+ direction = returnOppositeDir((Direction)direction);
+ uint16 thingCell = thing.getCell();
+ thingCell = normalizeModulo4((((thingCell - direction + 1) & 0x0002) >> 1) + direction);
+ thing = thingWithNewCell(thing, thingCell);
+ } else
+ break;
+ }
+ }
+ if ((thingType == k4_GroupThingType) && (fallKilledGroup || !_vm->_dungeonMan->isCreatureAllowedOnMap(thing, mapIndexDestination))) {
+ _vm->_groupMan->dropMovingCreatureFixedPossession(thing, destMapX, destMapY);
+ _vm->_groupMan->dropGroupPossessions(destMapX, destMapY, thing, k2_soundModePlayOneTickLater);
+ _vm->_dungeonMan->setCurrentMap(mapIndexSource);
+ if (mapX >= 0)
+ _vm->_groupMan->groupDelete(mapX, mapY);
+
+ return true; /* The specified group thing cannot be moved because it was killed by a fall or because it is not allowed on the destination map */
+ }
+ _moveResultMapX = destMapX;
+ _moveResultMapY = destMapY;
+ _moveResultMapIndex = mapIndexDestination;
+ _moveResultCell = thing.getCell();
+ partySquare = (mapIndexDestination == mapIndexSource) && (destMapX == mapX) && (destMapY == mapY);
+ if (partySquare) {
+ if (thing == Thing::_party) {
+ if (_vm->_dungeonMan->_partyDir == direction)
+ return false;
+ } else if ((_moveResultCell == thingCell) && (thingType != k14_ProjectileThingType))
+ return false;
+ } else {
+ if ((thing == Thing::_party) && _vm->_championMan->_partyChampionCount) {
+ uint16 oldDestinationSquare = destinationSquareData;
+ int16 scentIndex = _vm->_championMan->_party._scentCount;
+ while (scentIndex >= 24) {
+ _vm->_championMan->deleteScent(0);
+ scentIndex--;
+ }
+
+ if (scentIndex)
+ _vm->_championMan->addScentStrength(mapX, mapY, (int)(_vm->_gameTime - _vm->_projexpl->_lastPartyMovementTime));
+
+ _vm->_projexpl->_lastPartyMovementTime = _vm->_gameTime;
+ _vm->_championMan->_party._scentCount++;
+ if (_vm->_championMan->_party._event79Count_Footprints)
+ _vm->_championMan->_party._lastScentIndex = _vm->_championMan->_party._scentCount;
+
+ _vm->_championMan->_party._scents[scentIndex].setMapX(destMapX);
+ _vm->_championMan->_party._scents[scentIndex].setMapY(destMapY);
+ _vm->_championMan->_party._scents[scentIndex].setMapIndex(mapIndexDestination);
+ _vm->_championMan->_party._scentStrengths[scentIndex] = 0;
+ _vm->_championMan->addScentStrength(destMapX, destMapY, kDMMaskMergeCycles | 24);
+ destinationSquareData = oldDestinationSquare;
+ }
+ if (mapIndexDestination != mapIndexSource)
+ _vm->_dungeonMan->setCurrentMap(mapIndexSource);
+ }
+ }
+ if (mapX >= 0) {
+ if (thing == Thing::_party)
+ processThingAdditionOrRemoval(mapX, mapY, Thing::_party, partySquare, false);
+ else if (thingLevitates)
+ _vm->_dungeonMan->unlinkThingFromList(thing, Thing::_none, mapX, mapY);
+ else
+ processThingAdditionOrRemoval(mapX, mapY, thing, (_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (mapX == _vm->_dungeonMan->_partyMapX) && (mapY == _vm->_dungeonMan->_partyMapY), false);
+ }
+ if (destMapX >= 0) {
+ if (thing == Thing::_party) {
+ _vm->_dungeonMan->setCurrentMap(mapIndexDestination);
+ if ((thing = _vm->_groupMan->groupGetThing(_vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY)) != Thing::_endOfList) { /* Delete group if party moves onto its square */
+ _vm->_groupMan->dropGroupPossessions(_vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, thing, k1_soundModePlayIfPrioritized);
+ _vm->_groupMan->groupDelete(_vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY);
+ }
+
+ if (mapIndexDestination == mapIndexSource)
+ processThingAdditionOrRemoval(_vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, Thing::_party, partySquare, true);
+ else {
+ _vm->_dungeonMan->setCurrentMap(mapIndexSource);
+ _vm->_newPartyMapIndex = mapIndexDestination;
+ }
+ } else {
+ if (thingType == k4_GroupThingType) {
+ _vm->_dungeonMan->setCurrentMap(mapIndexDestination);
+ Teleporter *L0712_ps_Teleporter = (Teleporter *)_vm->_dungeonMan->getThingData(thing);
+ int16 activeGroupIndex = ((Group *)L0712_ps_Teleporter)->getActiveGroupIndex();
+ if (((mapIndexDestination == _vm->_dungeonMan->_partyMapIndex) && (destMapX == _vm->_dungeonMan->_partyMapX) && (destMapY == _vm->_dungeonMan->_partyMapY)) || (_vm->_groupMan->groupGetThing(destMapX, destMapY) != Thing::_endOfList)) { /* If a group tries to move to the party square or over another group then create an event to move the group later */
+ _vm->_dungeonMan->setCurrentMap(mapIndexSource);
+ if (mapX >= 0)
+ _vm->_groupMan->groupDeleteEvents(mapX, mapY);
+
+ if (groupOnPartyMap)
+ _vm->_groupMan->removeActiveGroup(activeGroupIndex);
+
+ createEventMoveGroup(thing, destMapX, destMapY, mapIndexDestination, audibleTeleporter);
+ return true; /* The specified group thing cannot be moved because the party or another group is on the destination square */
+ }
+ uint16 movementSoundIndex = getSound(((Group *)_vm->_dungeonMan->_thingData[k4_GroupThingType])[thing.getIndex()]._type);
+ if (movementSoundIndex < k34_D13_soundCount)
+ _vm->_sound->requestPlay(movementSoundIndex, destMapX, destMapY, k1_soundModePlayIfPrioritized);
+
+ if (groupOnPartyMap && (mapIndexDestination != _vm->_dungeonMan->_partyMapIndex)) { /* If the group leaves the party map */
+ _vm->_groupMan->removeActiveGroup(activeGroupIndex);
+ moveGroupResult = true;
+ } else if ((mapIndexDestination == _vm->_dungeonMan->_partyMapIndex) && (!groupOnPartyMap)) { /* If the group arrives on the party map */
+ _vm->_groupMan->addActiveGroup(thing, destMapX, destMapY);
+ moveGroupResult = true;
+ }
+ if (thingLevitates)
+ _vm->_dungeonMan->linkThingToList(thing, Thing(0), destMapX, destMapY);
+ else
+ processThingAdditionOrRemoval(destMapX, destMapY, thing, false, true);
+
+ if (moveGroupResult || (mapX < 0)) /* If group moved from one map to another or if it was just placed on a square */
+ _vm->_groupMan->startWandering(destMapX, destMapY);
+
+ _vm->_dungeonMan->setCurrentMap(mapIndexSource);
+ if (mapX >= 0) {
+ if (moveGroupResult > 1) /* If the group behavior was C6_BEHAVIOR_ATTACK before being teleported from and to the party map */
+ _vm->_groupMan->stopAttacking(&_vm->_groupMan->_activeGroups[moveGroupResult - 2], mapX, mapY);
+ else if (moveGroupResult) /* If the group was teleported or leaved the party map or entered the party map */
+ _vm->_groupMan->groupDeleteEvents(mapX, mapY);
+ }
+ return moveGroupResult;
+ }
+ _vm->_dungeonMan->setCurrentMap(mapIndexDestination);
+ if (thingType == k14_ProjectileThingType) /* BUG0_29 An explosion can trigger a floor sensor. Explosions do not trigger floor sensors on the square where they are created. However, if an explosion is moved by a teleporter (or by falling into a pit, see BUG0_26) after it was created, it can trigger floor sensors on the destination square. This is because explosions are not considered as levitating in the code, while projectiles are. The condition here should be (L0713_B_ThingLevitates) so that explosions would not start sensor processing on their destination square as they should be Levitating. This would work if F0264_MOVE_IsLevitating returned true for explosions (see BUG0_26) */
+ _vm->_dungeonMan->linkThingToList(thing, Thing(0), destMapX, destMapY);
+ else
+ processThingAdditionOrRemoval(destMapX, destMapY, thing, (_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (destMapX == _vm->_dungeonMan->_partyMapX) && (destMapY == _vm->_dungeonMan->_partyMapY), true);
+
+ _vm->_dungeonMan->setCurrentMap(mapIndexSource);
+ }
+ }
+ return false;
+}
+
+bool MovesensMan::isLevitating(Thing thing) {
+ ThingType thingType = thing.getType();
+ bool retVal = false;
+ if (thingType == k4_GroupThingType)
+ retVal = getFlag(_vm->_dungeonMan->getCreatureAttributes(thing), k0x0020_MaskCreatureInfo_levitation);
+ else if ((thingType == k14_ProjectileThingType) || (thingType == k15_ExplosionThingType))
+ // Fix original bug involving explosions falling in pits
+ retVal = true;
+
+ return retVal;
+}
+
+bool MovesensMan::moveIsKilledByProjectileImpact(int16 srcMapX, int16 srcMapY, int16 destMapX, int16 destMapY, Thing thing) {
+ /* This array is used only when moving between two adjacent squares and is used to test projectile
+ impacts when the party or group is in the 'intermediary' step between the two squares. Without
+ this test, in the example below no impact would be detected. In this example, the party moves from
+ the source square on the left (which contains a single champion at cell 2) to the destination square
+ on the right (which contains a single projectile at cell 3).
+ Party: Projectiles on target square: Incorrect result without the test for the intermediary step (the champion would have passed through the projectile without impact):
+ 00 -> 00 00
+ 01 P0 P1 */
+ byte intermediaryChampionOrCreatureOrdinalInCell[4];
+
+ /* This array has an entry for each cell on the source square, containing the ordinal of the champion
+ or creature (0 if there is no champion or creature at this cell) */
+ byte championOrCreatureOrdinalInCell[4];
+
+ bool checkDestinationSquareProjectileImpacts = false;
+ for (int16 i = 0; i < 4; ++i)
+ championOrCreatureOrdinalInCell[i] = 0;
+
+ SquareType impactType;
+ if (thing == Thing::_party) {
+ impactType = kM2_ChampionElemType;
+ for (uint16 cellIdx = k0_CellNorthWest; cellIdx < k3_CellSouthWest + 1; cellIdx++) {
+ if (_vm->_championMan->getIndexInCell((ViewCell)cellIdx) >= 0)
+ championOrCreatureOrdinalInCell[cellIdx] = _vm->indexToOrdinal(cellIdx);
+ }
+ } else {
+ impactType = kM1_CreatureElemType;
+ Group *curGroup = (Group *)_vm->_dungeonMan->getThingData(thing);
+ int16 creatureAlive = 0;
+ for (uint16 cellIdx = k0_CellNorthWest; cellIdx < k3_CellSouthWest + 1; cellIdx++) {
+ creatureAlive |= curGroup->_health[cellIdx];
+ if (_vm->_groupMan->getCreatureOrdinalInCell(curGroup, cellIdx))
+ championOrCreatureOrdinalInCell[cellIdx] = _vm->indexToOrdinal(cellIdx);
+ }
+ if (!creatureAlive)
+ return false;
+ }
+ if ((destMapX >= 0) && ((abs(srcMapX - destMapX) + abs(srcMapY - destMapY)) == 1)) {
+ /* If source and destination squares are adjacent (if party or group is not being teleported) */
+ int16 primaryDirection = _vm->_groupMan->getDirsWhereDestIsVisibleFromSource(srcMapX, srcMapY, destMapX, destMapY);
+ int16 secondaryDirection = returnNextVal(primaryDirection);
+ for (int16 i = 0; i < 4; ++i)
+ intermediaryChampionOrCreatureOrdinalInCell[i] = 0;
+
+ intermediaryChampionOrCreatureOrdinalInCell[returnPrevVal(primaryDirection)] = championOrCreatureOrdinalInCell[primaryDirection];
+ if (intermediaryChampionOrCreatureOrdinalInCell[returnPrevVal(primaryDirection)])
+ checkDestinationSquareProjectileImpacts = true;
+
+ intermediaryChampionOrCreatureOrdinalInCell[returnNextVal(secondaryDirection)] = championOrCreatureOrdinalInCell[secondaryDirection];
+ if (intermediaryChampionOrCreatureOrdinalInCell[returnNextVal(secondaryDirection)])
+ checkDestinationSquareProjectileImpacts = true;
+
+ if (!championOrCreatureOrdinalInCell[primaryDirection])
+ championOrCreatureOrdinalInCell[primaryDirection] = championOrCreatureOrdinalInCell[returnPrevVal(primaryDirection)];
+
+ if (!championOrCreatureOrdinalInCell[secondaryDirection])
+ championOrCreatureOrdinalInCell[secondaryDirection] = championOrCreatureOrdinalInCell[returnNextVal(secondaryDirection)];
+ }
+ uint16 projectileMapX = srcMapX; /* Check impacts with projectiles on the source square */
+ uint16 projectileMapY = srcMapY;
+T0266017_CheckProjectileImpacts:
+ Thing curThing = _vm->_dungeonMan->getSquareFirstThing(projectileMapX, projectileMapY);
+ while (curThing != Thing::_endOfList) {
+ if ((curThing.getType() == k14_ProjectileThingType) &&
+ (_vm->_timeline->_events[(((Projectile *)_vm->_dungeonMan->_thingData[k14_ProjectileThingType])[curThing.getIndex()])._eventIndex]._type != k48_TMEventTypeMoveProjectileIgnoreImpacts)) {
+ int16 championOrCreatureOrdinal = championOrCreatureOrdinalInCell[curThing.getCell()];
+ if (championOrCreatureOrdinal && _vm->_projexpl->hasProjectileImpactOccurred(impactType, srcMapX, srcMapY, _vm->ordinalToIndex(championOrCreatureOrdinal), curThing)) {
+ _vm->_projexpl->projectileDeleteEvent(curThing);
+ if (_vm->_projexpl->_creatureDamageOutcome == k2_outcomeKilledAllCreaturesInGroup)
+ return true;
+
+ goto T0266017_CheckProjectileImpacts;
+ }
+ }
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+ if (checkDestinationSquareProjectileImpacts) {
+ srcMapX |= ((projectileMapX = destMapX) + 1) << 8; /* Check impacts with projectiles on the destination square */
+ srcMapY |= (projectileMapY = destMapY) << 8;
+ for (uint16 i = 0; i < 4; ++i)
+ championOrCreatureOrdinalInCell[i] = intermediaryChampionOrCreatureOrdinalInCell[i];
+ checkDestinationSquareProjectileImpacts = false;
+ goto T0266017_CheckProjectileImpacts;
+ }
+ return false;
+}
+
+void MovesensMan::addEvent(byte type, byte mapX, byte mapY, byte cell, byte effect, int32 time) {
+ TimelineEvent newEvent;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_currMapIndex, time);
+ newEvent._type = type;
+ newEvent._priority = 0;
+ newEvent._B._location._mapX = mapX;
+ newEvent._B._location._mapY = mapY;
+ newEvent._C.A._cell = cell;
+ newEvent._C.A._effect = effect;
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+}
+
+int16 MovesensMan::getSound(byte creatureType) {
+ if (_vm->_championMan->_partyIsSleeping)
+ return 35;
+
+ switch (creatureType) {
+ case k3_CreatureTypeWizardEyeFlyingEye:
+ case k8_CreatureTypeGhostRive:
+ case k11_CreatureTypeBlackFlame:
+ case k19_CreatureTypeMaterializerZytaz:
+ case k23_CreatureTypeLordChaos:
+ case k25_CreatureTypeLordOrder:
+ case k26_CreatureTypeGreyLord:
+ return 35;
+ case k2_CreatureTypeGiggler:
+ case k9_CreatureTypeStoneGolem:
+ case k10_CreatureTypeMummy:
+ case k14_CreatureTypeVexirk:
+ case k16_CreatureTypeTrolinAntman:
+ case k22_CreatureTypeDemon:
+ return k24_soundMOVE_MUMMY_TROLIN_ANTMAN_STONE_GOLEM_GIGGLER_VEXIRK_DEMON;
+ case k0_CreatureTypeGiantScorpionScorpion:
+ case k4_CreatureTypePainRatHellHound:
+ case k5_CreatureTypeRuster:
+ case k6_CreatureTypeScreamer:
+ case k7_CreatureTypeRockpile:
+ case k15_CreatureTypeMagnetaWormWorm:
+ case k21_CreatureTypeOitu:
+ return k26_soundMOVE_SCREAMER_ROCK_ROCKPILE_MAGENTA_WORM_WORM_PAIN_RAT_HELLHOUND_RUSTER_GIANT_SCORPION_SCORPION_OITU;
+ case k24_CreatureTypeRedDragon:
+ return k32_soundMOVE_RED_DRAGON;
+ case k12_CreatureTypeSkeleton:
+ return k33_soundMOVE_SKELETON;
+ case k18_CreatureTypeAnimatedArmourDethKnight:
+ return k22_soundMOVE_ANIMATED_ARMOUR_DETH_KNIGHT;
+ case k1_CreatureTypeSwampSlimeSlime:
+ case k20_CreatureTypeWaterElemental:
+ return k27_soundMOVE_SWAMP_SLIME_SLIME_DEVIL_WATER_ELEMENTAL;
+ case k13_CreatureTypeCouatl:
+ case k17_CreatureTypeGiantWaspMuncher:
+ return k23_soundMOVE_COUATL_GIANT_WASP_MUNCHER;
+ }
+
+ return 35;
+}
+
+int16 MovesensMan::getTeleporterRotatedGroupResult(Teleporter *teleporter, Thing thing, uint16 mapIndex) {
+ Group *group = (Group *)_vm->_dungeonMan->getThingData(thing);
+ Direction rotation = teleporter->getRotation();
+ uint16 groupDirections = _vm->_groupMan->getGroupDirections(group, mapIndex);
+
+ bool absoluteRotation = teleporter->getAbsoluteRotation();
+ uint16 updatedGroupDirections;
+ if (absoluteRotation)
+ updatedGroupDirections = rotation;
+ else
+ updatedGroupDirections = normalizeModulo4(groupDirections + rotation);
+
+ uint16 updatedGroupCells = _vm->_groupMan->getGroupCells(group, mapIndex);
+ if (updatedGroupCells != k255_CreatureTypeSingleCenteredCreature) {
+ int16 groupCells = updatedGroupCells;
+ int16 creatureSize = getFlag(_vm->_dungeonMan->_creatureInfos[group->_type]._attributes, k0x0003_MaskCreatureInfo_size);
+ int16 relativeRotation = normalizeModulo4(4 + updatedGroupDirections - groupDirections);
+ for (int16 creatureIdx = 0; creatureIdx <= group->getCount(); creatureIdx++) {
+ updatedGroupDirections = _vm->_groupMan->getGroupValueUpdatedWithCreatureValue(updatedGroupDirections, creatureIdx, absoluteRotation ? rotation : normalizeModulo4(groupDirections + rotation));
+ if (creatureSize == k0_MaskCreatureSizeQuarter) {
+ relativeRotation = absoluteRotation ? 1 : 0;
+ if (relativeRotation)
+ relativeRotation = rotation;
+ }
+ if (relativeRotation)
+ updatedGroupCells = _vm->_groupMan->getGroupValueUpdatedWithCreatureValue(updatedGroupCells, creatureIdx, normalizeModulo4(groupCells + relativeRotation));
+
+ groupDirections >>= 2;
+ groupCells >>= 2;
+ }
+ }
+ _vm->_dungeonMan->setGroupDirections(group, updatedGroupDirections, mapIndex);
+ _vm->_dungeonMan->setGroupCells(group, updatedGroupCells, mapIndex);
+ if ((mapIndex == _vm->_dungeonMan->_partyMapIndex) && (group->setBehaviour(k6_behavior_ATTACK)))
+ return group->getActiveGroupIndex() + 2;
+
+ return 1;
+}
+
+Thing MovesensMan::getTeleporterRotatedProjectileThing(Teleporter *teleporter, Thing projectileThing) {
+ int16 updatedDirection = _moveResultDir;
+ int16 rotation = teleporter->getRotation();
+ if (teleporter->getAbsoluteRotation())
+ updatedDirection = rotation;
+ else {
+ updatedDirection = normalizeModulo4(updatedDirection + rotation);
+ projectileThing = thingWithNewCell(projectileThing, normalizeModulo4(projectileThing.getCell() + rotation));
+ }
+ _moveResultDir = updatedDirection;
+ return projectileThing;
+}
+
+void MovesensMan::processThingAdditionOrRemoval(uint16 mapX, uint16 mapY, Thing thing, bool partySquare, bool addThing) {
+ int16 thingType;
+ IconIndice objectType;
+ if (thing != Thing::_party) {
+ thingType = thing.getType();
+ objectType = _vm->_objectMan->getObjectType(thing);
+ } else {
+ thingType = kM1_PartyThingType;
+ objectType = kDMIconIndiceNone;
+ }
+
+ if ((!addThing) && (thingType != kM1_PartyThingType))
+ _vm->_dungeonMan->unlinkThingFromList(thing, Thing(0), mapX, mapY);
+
+ Square curSquare = Square(_vm->_dungeonMan->_currMapData[mapX][mapY]);
+ int16 sensorTriggeredCell;
+ if (curSquare.getType() == k0_WallElemType)
+ sensorTriggeredCell = thing.getCell();
+ else
+ sensorTriggeredCell = kM1_CellAny; // this will wrap around
+
+ bool squareContainsObject = false;
+ bool squareContainsGroup = false;
+ bool squareContainsThingOfSameType = false;
+ bool squareContainsThingOfDifferentType = false;
+ Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
+ if (sensorTriggeredCell == kM1_CellAny) {
+ while (curThing != Thing::_endOfList) {
+ uint16 curThingType = curThing.getType();
+ if (curThingType == k4_GroupThingType)
+ squareContainsGroup = true;
+ else if ((curThingType == k2_TextstringType) && (thingType == kM1_PartyThingType) && addThing && !partySquare) {
+ _vm->_dungeonMan->decodeText(_vm->_stringBuildBuffer, curThing, k1_TextTypeMessage);
+ _vm->_textMan->printMessage(k15_ColorWhite, _vm->_stringBuildBuffer);
+ } else if ((curThingType > k4_GroupThingType) && (curThingType < k14_ProjectileThingType)) {
+ squareContainsObject = true;
+ squareContainsThingOfSameType |= (_vm->_objectMan->getObjectType(curThing) == objectType);
+ squareContainsThingOfDifferentType |= (_vm->_objectMan->getObjectType(curThing) != objectType);
+ }
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+ } else {
+ while (curThing != Thing::_endOfList) {
+ if ((sensorTriggeredCell == curThing.getCell()) && (curThing.getType() > k4_GroupThingType)) {
+ squareContainsObject = true;
+ squareContainsThingOfSameType |= (_vm->_objectMan->getObjectType(curThing) == objectType);
+ squareContainsThingOfDifferentType |= (_vm->_objectMan->getObjectType(curThing) != objectType);
+ }
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+ }
+ if (addThing && (thingType != kM1_PartyThingType))
+ _vm->_dungeonMan->linkThingToList(thing, Thing(0), mapX, mapY);
+
+ for (curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY); curThing != Thing::_endOfList; curThing = _vm->_dungeonMan->getNextThing(curThing)) {
+ uint16 curThingType = curThing.getType();
+ if (curThingType == k3_SensorThingType) {
+ Sensor *curSensor = (Sensor *)_vm->_dungeonMan->getThingData(curThing);
+ if (curSensor->getType() == k0_SensorDisabled)
+ continue;
+
+ int16 curSensorData = curSensor->getData();
+ bool triggerSensor = addThing;
+ if (sensorTriggeredCell == kM1_CellAny) {
+ switch (curSensor->getType()) {
+ case k1_SensorFloorTheronPartyCreatureObj:
+ if (partySquare || squareContainsObject || squareContainsGroup) /* BUG0_30 A floor sensor is not triggered when you put an object on the floor if a levitating creature is present on the same square. The condition to determine if the sensor should be triggered checks if there is a creature on the square but does not check whether the creature is levitating. While it is normal not to trigger the sensor if there is a non levitating creature on the square (because it was already triggered by the creature itself), a levitating creature should not prevent triggering the sensor with an object. */
+ continue;
+ break;
+ case k2_SensorFloorTheronPartyCreature:
+ if ((thingType > k4_GroupThingType) || partySquare || squareContainsGroup)
+ continue;
+ break;
+ case k3_SensorFloorParty:
+ if ((thingType != kM1_PartyThingType) || (_vm->_championMan->_partyChampionCount == 0))
+ continue;
+
+ if (curSensorData == 0) {
+ if (partySquare)
+ continue;
+ } else if (!addThing)
+ triggerSensor = false;
+ else
+ triggerSensor = (curSensorData == _vm->indexToOrdinal(_vm->_dungeonMan->_partyDir));
+ break;
+ case k4_SensorFloorObj:
+ if ((curSensorData != _vm->_objectMan->getObjectType(thing)) || squareContainsThingOfSameType)
+ continue;
+ break;
+ case k5_SensorFloorPartyOnStairs:
+ if ((thingType != kM1_PartyThingType) || (curSquare.getType() != k3_StairsElemType))
+ continue;
+ break;
+ case k6_SensorFloorGroupGenerator:
+ continue;
+ break;
+ case k7_SensorFloorCreature:
+ if ((thingType > k4_GroupThingType) || (thingType == kM1_PartyThingType) || squareContainsGroup)
+ continue;
+ break;
+ case k8_SensorFloorPartyPossession:
+ if (thingType != kM1_PartyThingType)
+ continue;
+
+ triggerSensor = isObjectInPartyPossession(curSensorData);
+ break;
+ case k9_SensorFloorVersionChecker:
+ if ((thingType != kM1_PartyThingType) || !addThing || partySquare)
+ continue;
+
+ // Strangerke: 20 is a hardcoded version of the game. later version uses 21. Not present in the original dungeons anyway.
+ triggerSensor = (curSensorData <= 20);
+ break;
+ default:
+ continue;
+ break;
+ }
+ } else {
+ if (sensorTriggeredCell != curThing.getCell())
+ continue;
+
+ switch (curSensor->getType()) {
+ case k1_SensorWallOrnClick:
+ if (squareContainsObject)
+ continue;
+ break;
+ case k2_SensorWallOrnClickWithAnyObj:
+ if (squareContainsThingOfSameType || (curSensor->getData() != _vm->_objectMan->getObjectType(thing)))
+ continue;
+ break;
+ case k3_SensorWallOrnClickWithSpecObj:
+ if (squareContainsThingOfDifferentType || (curSensor->getData() == _vm->_objectMan->getObjectType(thing)))
+ continue;
+ break;
+ default:
+ continue;
+ break;
+ }
+ }
+
+ triggerSensor ^= curSensor->getAttrRevertEffectA();
+ int16 curSensorEffect = curSensor->getAttrEffectA();
+ if (curSensorEffect == k3_SensorEffHold)
+ curSensorEffect = triggerSensor ? k0_SensorEffSet : k1_SensorEffClear;
+ else if (!triggerSensor)
+ continue;
+
+ if (curSensor->getAttrAudibleA())
+ _vm->_sound->requestPlay(k01_soundSWITCH, mapX, mapY, k1_soundModePlayIfPrioritized);
+
+ triggerEffect(curSensor, curSensorEffect, mapX, mapY, (uint16)kM1_CellAny); // this will wrap around
+ continue;
+ }
+
+ if (curThingType >= k4_GroupThingType)
+ break;
+ }
+ processRotationEffect();
+}
+
+bool MovesensMan::isObjectInPartyPossession(int16 objectType) {
+ bool leaderHandObjectProcessed = false;
+ Champion *curChampion = _vm->_championMan->_champions;
+ int16 championIdx;
+ uint16 slotIdx = 0;
+ Thing curThing;
+ Thing *curSlotThing = nullptr;
+ for (championIdx = kDMChampionFirst; championIdx < _vm->_championMan->_partyChampionCount; championIdx++, curChampion++) {
+ if (curChampion->_currHealth) {
+ curSlotThing = curChampion->_slots;
+ for (slotIdx = kDMSlotReadyHand; (slotIdx < kDMSlotChest1) || !leaderHandObjectProcessed; slotIdx++) {
+ if (slotIdx < kDMSlotChest1)
+ curThing = *curSlotThing++;
+ else {
+ leaderHandObjectProcessed = true;
+ curThing = _vm->_championMan->_leaderHandObject;
+ }
+
+ int16 curObjectType = _vm->_objectMan->getObjectType(curThing);
+ if (curObjectType == objectType)
+ return true;
+
+ if (curObjectType == kDMIconIndiceContainerChestClosed) {
+ Container *container = (Container *)_vm->_dungeonMan->getThingData(curThing);
+ curThing = container->getSlot();
+ while (curThing != Thing::_endOfList) {
+ if (_vm->_objectMan->getObjectType(curThing) == objectType)
+ return true;
+
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+void MovesensMan::triggerEffect(Sensor *sensor, int16 effect, int16 mapX, int16 mapY, uint16 cell) {
+ TimelineEventType squareTypeToEventTypeArray[7] = { // @ G0059_auc_Graphic562_SquareTypeToEventType
+ k6_TMEventTypeWall,
+ k5_TMEventTypeCorridor,
+ k9_TMEventTypePit,
+ k0_TMEventTypeNone,
+ k10_TMEventTypeDoor,
+ k8_TMEventTypeTeleporter,
+ k7_TMEventTypeFakeWall
+ };
+
+ if (sensor->getAttrOnlyOnce())
+ sensor->setTypeDisabled();
+
+ int32 endTime = _vm->_gameTime + sensor->getAttrValue();
+ if (sensor->getAttrLocalEffect())
+ triggerLocalEffect(sensor->getActionLocalEffect(), mapX, mapY, cell);
+ else {
+ int16 targetMapX = sensor->getActionTargetMapX();
+ int16 targetMapY = sensor->getActionTargetMapY();
+ SquareType curSquareType = Square(_vm->_dungeonMan->_currMapData[targetMapX][targetMapY]).getType();
+ uint16 targetCell;
+ if (curSquareType == k0_ElementTypeWall)
+ targetCell = sensor->getActionTargetCell();
+ else
+ targetCell = k0_CellNorthWest;
+
+ addEvent(squareTypeToEventTypeArray[curSquareType], targetMapX, targetMapY, targetCell, effect, endTime);
+ }
+}
+
+void MovesensMan::triggerLocalEffect(int16 localEffect, int16 effX, int16 effY, int16 effCell) {
+ if (localEffect == k10_SensorEffAddExp) {
+ addSkillExperience(kDMSkillSteal, 300, localEffect != kM1_CellAny);
+ return;
+ }
+ _sensorRotationEffect = localEffect;
+ _sensorRotationEffMapX = effX;
+ _sensorRotationEffMapY = effY;
+ _sensorRotationEffCell = effCell;
+}
+
+void MovesensMan::addSkillExperience(int16 skillIndex, uint16 exp, bool leaderOnly) {
+ if (leaderOnly) {
+ if (_vm->_championMan->_leaderIndex != kDMChampionNone)
+ _vm->_championMan->addSkillExperience(_vm->_championMan->_leaderIndex, skillIndex, exp);
+ } else {
+ exp /= _vm->_championMan->_partyChampionCount;
+ Champion *curChampion = _vm->_championMan->_champions;
+ for (int16 championIdx = kDMChampionFirst; championIdx < _vm->_championMan->_partyChampionCount; championIdx++, curChampion++) {
+ if (curChampion->_currHealth)
+ _vm->_championMan->addSkillExperience(championIdx, skillIndex, exp);
+ }
+ }
+}
+
+void MovesensMan::processRotationEffect() {
+ if (_sensorRotationEffect == kM1_SensorEffNone)
+ return;
+
+ switch (_sensorRotationEffect) {
+ case k1_SensorEffClear:
+ case k2_SensorEffToggle:
+ Thing firstSensorThing = _vm->_dungeonMan->getSquareFirstThing(_sensorRotationEffMapX, _sensorRotationEffMapY);
+ while ((firstSensorThing.getType() != k3_SensorThingType)
+ || ((_sensorRotationEffCell != kM1_CellAny) && (firstSensorThing.getCell() != _sensorRotationEffCell))) {
+ firstSensorThing = _vm->_dungeonMan->getNextThing(firstSensorThing);
+ }
+ Sensor *firstSensor = (Sensor *)_vm->_dungeonMan->getThingData(firstSensorThing);
+ Thing lastSensorThing = firstSensor->getNextThing();
+ while ((lastSensorThing != Thing::_endOfList)
+ && ((lastSensorThing.getType() != k3_SensorThingType)
+ || ((_sensorRotationEffCell != kM1_CellAny) && (lastSensorThing.getCell() != _sensorRotationEffCell)))) {
+ lastSensorThing = _vm->_dungeonMan->getNextThing(lastSensorThing);
+ }
+ if (lastSensorThing == Thing::_endOfList)
+ break;
+ _vm->_dungeonMan->unlinkThingFromList(firstSensorThing, Thing(0), _sensorRotationEffMapX, _sensorRotationEffMapY);
+ Sensor *lastSensor = (Sensor *)_vm->_dungeonMan->getThingData(lastSensorThing);
+ lastSensorThing = _vm->_dungeonMan->getNextThing(lastSensorThing);
+ while (((lastSensorThing != Thing::_endOfList) && (lastSensorThing.getType() == k3_SensorThingType))) {
+ if ((_sensorRotationEffCell == kM1_CellAny) || (lastSensorThing.getCell() == _sensorRotationEffCell))
+ lastSensor = (Sensor *)_vm->_dungeonMan->getThingData(lastSensorThing);
+ lastSensorThing = _vm->_dungeonMan->getNextThing(lastSensorThing);
+ }
+ firstSensor->setNextThing(lastSensor->getNextThing());
+ lastSensor->setNextThing(firstSensorThing);
+ }
+ _sensorRotationEffect = kM1_SensorEffNone;
+}
+
+void MovesensMan::createEventMoveGroup(Thing groupThing, int16 mapX, int16 mapY, int16 mapIndex, bool audible) {
+ TimelineEvent newEvent;
+ setMapAndTime(newEvent._mapTime, mapIndex, _vm->_gameTime + 5);
+ newEvent._type = audible ? k61_TMEventTypeMoveGroupAudible : k60_TMEventTypeMoveGroupSilent;
+ newEvent._priority = 0;
+ newEvent._B._location._mapX = mapX;
+ newEvent._B._location._mapY = mapY;
+ newEvent._C._slot = groupThing.toUint16();
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+}
+
+Thing MovesensMan::getObjectOfTypeInCell(int16 mapX, int16 mapY, int16 cell, int16 objectType) {
+ Thing curThing = _vm->_dungeonMan->getSquareFirstObject(mapX, mapY);
+ while (curThing != Thing::_endOfList) {
+ if (_vm->_objectMan->getObjectType(curThing) == objectType) {
+ if ((cell == kM1_CellAny) || (curThing.getCell() == cell))
+ return curThing;
+ }
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+ return Thing::_none;
+}
+}
diff --git a/engines/dm/movesens.h b/engines/dm/movesens.h
new file mode 100644
index 0000000000..7746fcac6c
--- /dev/null
+++ b/engines/dm/movesens.h
@@ -0,0 +1,74 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+
+#ifndef DM_MOVESENS_H
+#define DM_MOVESENS_H
+
+#include "dm/dm.h"
+
+namespace DM {
+ class Sensor;
+ class Teleporter;
+
+ class MovesensMan {
+ DMEngine *_vm;
+public:
+ explicit MovesensMan(DMEngine *vm);
+
+ int16 _moveResultMapX; // @ G0397_i_MoveResultMapX
+ int16 _moveResultMapY; // @ G0398_i_MoveResultMapY
+ uint16 _moveResultMapIndex; // @ G0399_ui_MoveResultMapIndex
+ int16 _moveResultDir; // @ G0400_i_MoveResultDirection
+ uint16 _moveResultCell; // @ G0401_ui_MoveResultCell
+ bool _useRopeToClimbDownPit; // @ G0402_B_UseRopeToClimbDownPit
+ int16 _sensorRotationEffect; // @ G0403_i_SensorRotationEffect
+ int16 _sensorRotationEffMapX; // @ G0404_i_SensorRotationEffectMapX
+ int16 _sensorRotationEffMapY; // @ G0405_i_SensorRotationEffectMapY
+ int16 _sensorRotationEffCell; // @ G0406_i_SensorRotationEffectCell
+
+ bool sensorIsTriggeredByClickOnWall(int16 mapX, int16 mapY, uint16 cellParam); // @ F0275_SENSOR_IsTriggeredByClickOnWall
+ bool getMoveResult(Thing thing, int16 mapX, int16 mapY, int16 destMapX, int16 destMapY); // @ F0267_MOVE_GetMoveResult_CPSCE
+ bool isLevitating(Thing thing); // @ F0264_MOVE_IsLevitating
+ bool moveIsKilledByProjectileImpact(int16 srcMapX, int16 srcMapY, int16 destMapX, int16 destMapY, Thing thing); // @ F0266_MOVE_IsKilledByProjectileImpact
+ void addEvent(byte type, byte mapX, byte mapY, byte cell, byte effect, int32 time); // @ F0268_SENSOR_AddEvent
+ int16 getSound(byte creatureType); // @ F0514_MOVE_GetSound
+ int16 getTeleporterRotatedGroupResult(Teleporter *teleporter, Thing thing, uint16 mapIndex);// @ F0262_MOVE_GetTeleporterRotatedGroupResult
+ Thing getTeleporterRotatedProjectileThing(Teleporter *teleporter, Thing projectileThing); // @ F0263_MOVE_GetTeleporterRotatedProjectileThing
+ void processThingAdditionOrRemoval(uint16 mapX, uint16 mapY, Thing thing, bool partySquare, bool addThing);// @ F0276_SENSOR_ProcessThingAdditionOrRemoval
+ bool isObjectInPartyPossession(int16 objectType); // @ F0274_SENSOR_IsObjectInPartyPossession
+ void triggerEffect(Sensor *sensor, int16 effect, int16 mapX, int16 mapY, uint16 cell); // @ F0272_SENSOR_TriggerEffect
+ void triggerLocalEffect(int16 localEffect, int16 effX, int16 effY, int16 effCell); // @ F0270_SENSOR_TriggerLocalEffect
+ void addSkillExperience(int16 skillIndex, uint16 exp, bool leaderOnly); // @ F0269_SENSOR_AddSkillExperience
+ void processRotationEffect();// @ F0271_SENSOR_ProcessRotationEffect
+ void createEventMoveGroup(Thing groupThing, int16 mapX, int16 mapY, int16 mapIndex, bool audible); // @ F0265_MOVE_CreateEvent60To61_MoveGroup
+ Thing getObjectOfTypeInCell(int16 mapX, int16 mapY, int16 cell, int16 objectType); // @ F0273_SENSOR_GetObjectOfTypeInCell
+};
+
+}
+
+#endif
diff --git a/engines/dm/objectman.cpp b/engines/dm/objectman.cpp
new file mode 100644
index 0000000000..7e403be6fe
--- /dev/null
+++ b/engines/dm/objectman.cpp
@@ -0,0 +1,278 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "dm/objectman.h"
+#include "dm/dungeonman.h"
+#include "dm/text.h"
+
+namespace DM {
+
+void ObjectMan::initConstants() {
+ int16 iconGraphicHeight[7] = {32, 32, 32, 32, 32, 32, 32}; // @ K0077_ai_IconGraphicHeight
+ int16 iconGraphicFirstIndex[7] = { // G0026_ai_Graphic562_IconGraphicFirstIconIndex
+ 0, /* First icon index in graphic #42 */
+ 32, /* First icon index in graphic #43 */
+ 64, /* First icon index in graphic #44 */
+ 96, /* First icon index in graphic #45 */
+ 128, /* First icon index in graphic #46 */
+ 160, /* First icon index in graphic #47 */
+ 192 /* First icon index in graphic #48 */
+ };
+
+ /* 8 for champion hands in status boxes, 30 for champion inventory, 8 for chest */
+ _slotBoxes[0] = SlotBox(4, 10, 0); /* Champion Status Box 0 Ready Hand */
+ _slotBoxes[1] = SlotBox(24, 10, 0); /* Champion Status Box 0 Action Hand */
+ _slotBoxes[2] = SlotBox(73, 10, 0); /* Champion Status Box 1 Ready Hand */
+ _slotBoxes[3] = SlotBox(93, 10, 0); /* Champion Status Box 1 Action Hand */
+ _slotBoxes[4] = SlotBox(142, 10, 0); /* Champion Status Box 2 Ready Hand */
+ _slotBoxes[5] = SlotBox(162, 10, 0); /* Champion Status Box 2 Action Hand */
+ _slotBoxes[6] = SlotBox(211, 10, 0); /* Champion Status Box 3 Ready Hand */
+ _slotBoxes[7] = SlotBox(231, 10, 0); /* Champion Status Box 3 Action Hand */
+ _slotBoxes[8] = SlotBox(6, 53, 0); /* Ready Hand */
+ _slotBoxes[9] = SlotBox(62, 53, 0); /* Action Hand */
+ _slotBoxes[10] = SlotBox(34, 26, 0); /* Head */
+ _slotBoxes[11] = SlotBox(34, 46, 0); /* Torso */
+ _slotBoxes[12] = SlotBox(34, 66, 0); /* Legs */
+ _slotBoxes[13] = SlotBox(34, 86, 0); /* Feet */
+ _slotBoxes[14] = SlotBox(6, 90, 0); /* Pouch 2 */
+ _slotBoxes[15] = SlotBox(79, 73, 0); /* Quiver Line2 1 */
+ _slotBoxes[16] = SlotBox(62, 90, 0); /* Quiver Line1 2 */
+ _slotBoxes[17] = SlotBox(79, 90, 0); /* Quiver Line2 2 */
+ _slotBoxes[18] = SlotBox(6, 33, 0); /* Neck */
+ _slotBoxes[19] = SlotBox(6, 73, 0); /* Pouch 1 */
+ _slotBoxes[20] = SlotBox(62, 73, 0); /* Quiver Line1 1 */
+ _slotBoxes[21] = SlotBox(66, 33, 0); /* Backpack Line1 1 */
+ _slotBoxes[22] = SlotBox(83, 16, 0); /* Backpack Line2 2 */
+ _slotBoxes[23] = SlotBox(100, 16, 0); /* Backpack Line2 3 */
+ _slotBoxes[24] = SlotBox(117, 16, 0); /* Backpack Line2 4 */
+ _slotBoxes[25] = SlotBox(134, 16, 0); /* Backpack Line2 5 */
+ _slotBoxes[26] = SlotBox(151, 16, 0); /* Backpack Line2 6 */
+ _slotBoxes[27] = SlotBox(168, 16, 0); /* Backpack Line2 7 */
+ _slotBoxes[28] = SlotBox(185, 16, 0); /* Backpack Line2 8 */
+ _slotBoxes[29] = SlotBox(202, 16, 0); /* Backpack Line2 9 */
+ _slotBoxes[30] = SlotBox(83, 33, 0); /* Backpack Line1 2 */
+ _slotBoxes[31] = SlotBox(100, 33, 0); /* Backpack Line1 3 */
+ _slotBoxes[32] = SlotBox(117, 33, 0); /* Backpack Line1 4 */
+ _slotBoxes[33] = SlotBox(134, 33, 0); /* Backpack Line1 5 */
+ _slotBoxes[34] = SlotBox(151, 33, 0); /* Backpack Line1 6 */
+ _slotBoxes[35] = SlotBox(168, 33, 0); /* Backpack Line1 7 */
+ _slotBoxes[36] = SlotBox(185, 33, 0); /* Backpack Line1 8 */
+ _slotBoxes[37] = SlotBox(202, 33, 0); /* Backpack Line1 9 */
+ _slotBoxes[38] = SlotBox(117, 59, 0); /* Chest 1 */
+ _slotBoxes[39] = SlotBox(106, 76, 0); /* Chest 2 */
+ _slotBoxes[40] = SlotBox(111, 93, 0); /* Chest 3 */
+ _slotBoxes[41] = SlotBox(128, 98, 0); /* Chest 4 */
+ _slotBoxes[42] = SlotBox(145, 101, 0); /* Chest 5 */
+ _slotBoxes[43] = SlotBox(162, 103, 0); /* Chest 6 */
+ _slotBoxes[44] = SlotBox(179, 104, 0); /* Chest 7 */
+ _slotBoxes[45] = SlotBox(196, 105, 0); /* Chest 8 */
+
+ for (int i = 0; i < 7; i++) {
+ _iconGraphicHeight[i] = iconGraphicHeight[i];
+ _iconGraphicFirstIndex[i] = iconGraphicFirstIndex[i];
+ }
+}
+
+ObjectMan::ObjectMan(DMEngine *vm) : _vm(vm) {
+ for (uint16 i = 0; i < k199_ObjectNameCount; ++i)
+ _objectNames[i] = nullptr;
+
+ _objectIconForMousePointer = nullptr;
+
+ initConstants();
+}
+
+ObjectMan::~ObjectMan() {
+ delete[] _objectIconForMousePointer;
+ delete[] _objectNames[0];
+}
+
+void ObjectMan::loadObjectNames() {
+ DisplayMan &dispMan = *_vm->_displayMan;
+
+ _objectIconForMousePointer = new byte[16 * 16];
+
+ char *objectNames = new char[dispMan.getCompressedDataSize(k556_ObjectNamesGraphicIndice) + k199_ObjectNameCount];
+ Common::MemoryReadStream stream = dispMan.getCompressedData(k556_ObjectNamesGraphicIndice);
+
+ for (uint16 objNameIndex = 0; objNameIndex < k199_ObjectNameCount; ++objNameIndex) {
+ _objectNames[objNameIndex] = objectNames;
+
+ byte tmpByte;
+ for (tmpByte = stream.readByte(); !(tmpByte & 0x80); tmpByte = stream.readByte()) // last char of object name has 7th bit on
+ *objectNames++ = tmpByte; // write while not last char
+
+ *objectNames++ = tmpByte & 0x7F; // write without the 7th bit
+ *objectNames++ = '\0'; // terminate string
+ }
+}
+
+IconIndice ObjectMan::getObjectType(Thing thing) {
+ if (thing == Thing::_none)
+ return kDMIconIndiceNone;
+
+ int16 objectInfoIndex = _vm->_dungeonMan->getObjectInfoIndex(thing);
+ if (objectInfoIndex != -1)
+ objectInfoIndex = _vm->_dungeonMan->_objectInfos[objectInfoIndex]._type;
+
+ return (IconIndice)objectInfoIndex;
+}
+
+IconIndice ObjectMan::getIconIndex(Thing thing) {
+ static byte chargeCountToTorchType[16] = {0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3}; // @ G0029_auc_Graphic562_ChargeCountToTorchType
+
+ int16 iconIndex = getObjectType(thing);
+ if (iconIndex != kDMIconIndiceNone) {
+ if (((iconIndex < kDMIconIndiceWeaponDagger) && (iconIndex >= kDMIconIndiceJunkCompassNorth)) ||
+ ((iconIndex >= kDMIconIndicePotionMaPotionMonPotion) && (iconIndex <= kDMIconIndicePotionWaterFlask)) ||
+ (iconIndex == kDMIconIndicePotionEmptyFlask)) {
+ Junk *junkThing = (Junk*)_vm->_dungeonMan->getThingData(thing);
+ switch (iconIndex) {
+ case kDMIconIndiceJunkCompassNorth:
+ iconIndex += _vm->_dungeonMan->_partyDir;
+ break;
+ case kDMIconIndiceWeaponTorchUnlit:
+ if (((Weapon*)junkThing)->isLit())
+ iconIndex += chargeCountToTorchType[((Weapon*)junkThing)->getChargeCount()];
+ break;
+ case kDMIconIndiceScrollOpen:
+ if (((Scroll*)junkThing)->getClosed())
+ iconIndex++;
+ break;
+ case kDMIconIndiceJunkWater:
+ case kDMIconIndiceJunkIllumuletUnequipped:
+ case kDMIconIndiceJunkJewelSymalUnequipped:
+ if (junkThing->getChargeCount())
+ iconIndex++;
+ break;
+ case kDMIconIndiceWeaponBoltBladeStormEmpty:
+ case kDMIconIndiceWeaponFlamittEmpty:
+ case kDMIconIndiceWeaponStormringEmpty:
+ case kDMIconIndiceWeaponFuryRaBladeEmpty:
+ case kDMIconIndiceWeaponEyeOfTimeEmpty:
+ case kDMIconIndiceWeaponStaffOfClawsEmpty:
+ if (((Weapon*)junkThing)->getChargeCount())
+ iconIndex++;
+ break;
+ }
+ }
+ }
+ return (IconIndice)iconIndex;
+}
+
+void ObjectMan::extractIconFromBitmap(uint16 iconIndex, byte *destBitmap) {
+ uint16 counter;
+ for (counter = 0; counter < 7; counter++) {
+ if (_iconGraphicFirstIndex[counter] > iconIndex)
+ break;
+ }
+ --counter;
+ byte *iconBitmap = _vm->_displayMan->getNativeBitmapOrGraphic(k42_ObjectIcons_000_TO_031 + counter);
+ iconIndex -= _iconGraphicFirstIndex[counter];
+ _vm->_displayMan->_useByteBoxCoordinates = true;
+ Box blitBox(0, 15, 0, 15);
+ _vm->_displayMan->blitToBitmap(iconBitmap, destBitmap, blitBox, (iconIndex & 0x000F) << 4, iconIndex & 0x0FF0, 128, 8, kM1_ColorNoTransparency, _iconGraphicHeight[counter], 16);
+}
+
+void ObjectMan::drawIconInSlotBox(uint16 slotBoxIndex, int16 iconIndex) {
+ SlotBox *slotBox = &_slotBoxes[slotBoxIndex];
+ slotBox->_iconIndex = iconIndex;
+ if (slotBox->_iconIndex == kDMIconIndiceNone)
+ return;
+
+ Box blitBox;
+ blitBox._x1 = slotBox->_x;
+ blitBox._x2 = blitBox._x1 + 15;
+ blitBox._y1 = slotBox->_y;
+ blitBox._y2 = blitBox._y1 + 15;
+
+ uint16 iconGraphicIndex;
+ for (iconGraphicIndex = 0; iconGraphicIndex < 7; iconGraphicIndex++) {
+ if (_iconGraphicFirstIndex[iconGraphicIndex] > iconIndex)
+ break;
+ }
+ iconGraphicIndex--;
+ byte *iconBitmap = _vm->_displayMan->getNativeBitmapOrGraphic(iconGraphicIndex + k42_ObjectIcons_000_TO_031);
+ iconIndex -= _iconGraphicFirstIndex[iconGraphicIndex];
+ int16 byteWidth;
+ byte* blitDestination;
+ int16 destHeight;
+ if (slotBoxIndex >= k8_SlotBoxInventoryFirstSlot) {
+ blitDestination = _vm->_displayMan->_bitmapViewport;
+ byteWidth = k112_byteWidthViewport;
+ destHeight = 136;
+ } else {
+ blitDestination = (unsigned char*)_vm->_displayMan->_bitmapScreen;
+ byteWidth = k160_byteWidthScreen;
+ destHeight = 200;
+ }
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ _vm->_displayMan->blitToBitmap(iconBitmap, blitDestination, blitBox, (iconIndex & 0x000F) << 4, iconIndex & 0x0FF0, k128_byteWidth, byteWidth, kM1_ColorNoTransparency, _iconGraphicHeight[iconGraphicIndex], destHeight);
+}
+
+void ObjectMan::drawLeaderObjectName(Thing thing) {
+ char *objectName = nullptr;
+ int16 iconIndex = getIconIndex(thing);
+ if (iconIndex == kDMIconIndiceJunkChampionBones) {
+ Junk *junk = (Junk*)_vm->_dungeonMan->getThingData(thing);
+ char champBonesName[16];
+
+ switch (_vm->getGameLanguage()) { // localized
+ case Common::FR_FRA:
+ // Fix original bug: strcpy was coming after strcat
+ strcpy(champBonesName, _objectNames[iconIndex]);
+ strcat(champBonesName, _vm->_championMan->_champions[junk->getChargeCount()]._name);
+ break;
+ default: // English and German version are the same
+ strcpy(champBonesName, _vm->_championMan->_champions[junk->getChargeCount()]._name);
+ strcat(champBonesName, _objectNames[iconIndex]);
+ break;
+ }
+
+ objectName = champBonesName;
+ } else
+ objectName = _objectNames[iconIndex];
+
+ _vm->_textMan->printWithTrailingSpaces(_vm->_displayMan->_bitmapScreen, k160_byteWidthScreen, 233, 37, k4_ColorCyan, k0_ColorBlack, objectName, k14_ObjectNameMaximumLength, k200_heightScreen);
+}
+
+IconIndice ObjectMan::getIconIndexInSlotBox(uint16 slotBoxIndex) {
+ return (IconIndice)_slotBoxes[slotBoxIndex]._iconIndex;
+}
+
+void ObjectMan::clearLeaderObjectName() {
+ static Box boxLeaderHandObjectName(233, 319, 33, 38); // @ G0028_s_Graphic562_Box_LeaderHandObjectName
+ _vm->_displayMan->fillScreenBox(boxLeaderHandObjectName, k0_ColorBlack);
+}
+
+void ObjectMan::drawIconToScreen(int16 iconIndex, int16 posX, int16 posY) {
+ static byte iconBitmap[16 * 16];
+ Box blitBox(posX, posX + 15, posY, posY + 15);
+ extractIconFromBitmap(iconIndex, iconBitmap);
+ _vm->_displayMan->blitToScreen(iconBitmap, &blitBox, k8_byteWidth, kM1_ColorNoTransparency, 16);
+}
+}
diff --git a/engines/dm/objectman.h b/engines/dm/objectman.h
new file mode 100644
index 0000000000..bb7d06c606
--- /dev/null
+++ b/engines/dm/objectman.h
@@ -0,0 +1,82 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_OBJECTMAN_H
+#define DM_OBJECTMAN_H
+
+#include "dm/dm.h"
+#include "dm/champion.h"
+
+namespace DM {
+
+#define k8_SlotBoxInventoryFirstSlot 8 // @ C08_SLOT_BOX_INVENTORY_FIRST_SLOT
+#define k9_SlotBoxInventoryActionHand 9 // @ C09_SLOT_BOX_INVENTORY_ACTION_HAND
+#define k38_SlotBoxChestFirstSlot 38 // @ C38_SLOT_BOX_CHEST_FIRST_SLOT
+
+#define k14_ObjectNameMaximumLength 14 // @ C014_OBJECT_NAME_MAXIMUM_LENGTH
+#define k199_ObjectNameCount 199 // @ C199_OBJECT_NAME_COUNT
+#define k556_ObjectNamesGraphicIndice 556 // @ C556_GRAPHIC_OBJECT_NAMES
+
+class SlotBox {
+public:
+ int16 _x;
+ int16 _y;
+ int16 _iconIndex;
+ SlotBox(int16 x, int16 y, int16 iconIndex): _x(x), _y(y), _iconIndex(iconIndex) {}
+ SlotBox(): _x(-1), _y(-1), _iconIndex(-1) {}
+}; // @ SLOT_BOX
+
+class ObjectMan {
+ DMEngine *_vm;
+
+public:
+ explicit ObjectMan(DMEngine *vm);
+ ~ObjectMan();
+ void loadObjectNames(); // @ F0031_OBJECT_LoadNames
+
+ SlotBox _slotBoxes[46]; // @ G0030_as_Graphic562_SlotBoxes;
+ char *_objectNames[k199_ObjectNameCount]; // @ G0352_apc_ObjectNames
+ byte *_objectIconForMousePointer; // @ G0412_puc_Bitmap_ObjectIconForMousePointer
+
+ IconIndice getObjectType(Thing thing); // @ F0032_OBJECT_GetType
+ IconIndice getIconIndex(Thing thing); // @ F0033_OBJECT_GetIconIndex
+ void extractIconFromBitmap(uint16 iconIndex, byte *destBitmap); // @ F0036_OBJECT_ExtractIconFromBitmap
+ void drawIconInSlotBox(uint16 slotBoxIndex, int16 iconIndex); // @ F0038_OBJECT_DrawIconInSlotBox
+ void drawLeaderObjectName(Thing thing); // @ F0034_OBJECT_DrawLeaderHandObjectName
+ IconIndice getIconIndexInSlotBox(uint16 slotBoxIndex); // @ F0039_OBJECT_GetIconIndexInSlotBox
+ void clearLeaderObjectName(); // @ F0035_OBJECT_ClearLeaderHandObjectName
+ void drawIconToScreen(int16 iconIndex, int16 posX, int16 posY); // @ F0037_OBJECT_DrawIconToScreen
+
+ int16 _iconGraphicHeight[7]; // @ K0077_ai_IconGraphicHeight
+ int16 _iconGraphicFirstIndex[7]; // G0026_ai_Graphic562_IconGraphicFirstIconIndex
+
+ void initConstants();
+};
+
+}
+
+#endif
diff --git a/engines/dm/projexpl.cpp b/engines/dm/projexpl.cpp
new file mode 100644
index 0000000000..c9682a18e1
--- /dev/null
+++ b/engines/dm/projexpl.cpp
@@ -0,0 +1,562 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "dm/projexpl.h"
+#include "dm/dungeonman.h"
+#include "dm/timeline.h"
+#include "dm/group.h"
+#include "dm/objectman.h"
+#include "dm/movesens.h"
+#include "dm/sounds.h"
+
+namespace DM {
+
+ProjExpl::ProjExpl(DMEngine *vm) : _vm(vm) {
+ _creatureDamageOutcome = 0;
+ _secondaryDirToOrFromParty = 0;
+ _lastCreatureAttackTime = -200;
+ _createLauncherProjectile = false;
+ _projectilePoisonAttack = 0;
+ _projectileAttackType = 0;
+ _lastPartyMovementTime = 0;
+}
+
+void ProjExpl::createProjectile(Thing thing, int16 mapX, int16 mapY, uint16 cell, Direction dir, byte kineticEnergy, byte attack, byte stepEnergy) {
+ Thing projectileThing = _vm->_dungeonMan->getUnusedThing(k14_ProjectileThingType);
+ if (projectileThing == Thing::_none) /* BUG0_16 If the game cannot create a projectile thing because it has run out of such things (60 maximum) then the object being thrown/shot/launched is orphaned. If the game has run out of projectile things it will try to remove a projectile from elsewhere in the dungeon, except in an area of 11x11 squares centered around the party (to make sure the player cannot actually see the thing disappear on screen) */
+ return;
+
+ projectileThing = thingWithNewCell(projectileThing, cell);
+ Projectile *projectilePtr = (Projectile *)_vm->_dungeonMan->getThingData(projectileThing);
+ projectilePtr->_slot = thing;
+ projectilePtr->_kineticEnergy = MIN((int16)kineticEnergy, (int16)255);
+ projectilePtr->_attack = attack;
+ _vm->_dungeonMan->linkThingToList(projectileThing, Thing(0), mapX, mapY); /* Projectiles are added on the square and not 'moved' onto the square. In the case of a projectile launcher sensor, this means that the new projectile traverses the square in front of the launcher without any trouble: there is no impact if it is a wall, the projectile direction is not changed if it is a teleporter. Impacts with creatures and champions are still processed */
+ TimelineEvent newEvent;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_currMapIndex, _vm->_gameTime + 1);
+ if (_createLauncherProjectile)
+ newEvent._type = k49_TMEventTypeMoveProjectile; /* Launcher projectiles can impact immediately */
+ else
+ newEvent._type = k48_TMEventTypeMoveProjectileIgnoreImpacts; /* Projectiles created by champions or creatures ignore impacts on their first movement */
+
+ newEvent._priority = 0;
+ newEvent._B._slot = projectileThing.toUint16();
+ newEvent._C._projectile.setMapX(mapX);
+ newEvent._C._projectile.setMapY(mapY);
+ newEvent._C._projectile.setStepEnergy(stepEnergy);
+ newEvent._C._projectile.setDir(dir);
+ projectilePtr->_eventIndex = _vm->_timeline->addEventGetEventIndex(&newEvent);
+}
+
+bool ProjExpl::hasProjectileImpactOccurred(int16 impactType, int16 mapXCombo, int16 mapYCombo, int16 cell, Thing projectileThing) {
+ Projectile *projectileThingData = (Projectile *)_vm->_dungeonMan->getThingData(Thing(projectileThing));
+ bool removePotion = false;
+ int16 potionPower = 0;
+ _creatureDamageOutcome = k0_outcomeKilledNoCreaturesInGroup;
+ Thing projectileAssociatedThing = projectileThingData->_slot;
+ int16 projectileAssociatedThingType = projectileAssociatedThing.getType();
+ Potion *potion = nullptr;
+ Thing explosionThing = Thing::_none;
+ if (projectileAssociatedThingType == k8_PotionThingType) {
+ Group *projectileAssociatedGroup = (Group *)_vm->_dungeonMan->getThingData(projectileAssociatedThing);
+ PotionType potionType = ((Potion *)projectileAssociatedGroup)->getType();
+ if ((potionType == k3_PotionTypeVen) || (potionType == k19_PotionTypeFulBomb)) {
+ explosionThing = (potionType == k3_PotionTypeVen) ? Thing::_explPoisonCloud: Thing::_explFireBall;
+ removePotion = true;
+ potionPower = ((Potion *)projectileAssociatedGroup)->getPower();
+ potion = (Potion *)projectileAssociatedGroup;
+ }
+ }
+ bool createExplosionOnImpact = (projectileAssociatedThingType == k15_ExplosionThingType) && (projectileAssociatedThing != Thing::_explSlime) && (projectileAssociatedThing != Thing::_explPoisonBolt);
+ Thing *curGroupSlot = nullptr;
+ int16 projectileMapX;
+ int16 projectileMapY;
+ int16 projectileTargetMapX = mapXCombo;
+ int16 projectileTargetMapY = mapYCombo;
+
+ if (mapXCombo <= 255) {
+ projectileMapX = mapXCombo;
+ projectileMapY = mapYCombo;
+ } else {
+ projectileMapX = (mapXCombo >> 8) - 1;
+ projectileMapY = (mapYCombo >> 8);
+ projectileTargetMapX &= 0x00FF;
+ projectileTargetMapY &= 0x00FF;
+ }
+
+ int16 championAttack = 0;
+ int16 attack = 0;
+ int16 championIndex = 0;
+ switch (impactType) {
+ case k4_DoorElemType: {
+ byte curSquare = _vm->_dungeonMan->_currMapData[projectileTargetMapX][projectileTargetMapY];
+ int16 curDoorState = Square(curSquare).getDoorState();
+ Door *curDoor = (Door *)_vm->_dungeonMan->getSquareFirstThingData(projectileTargetMapX, projectileTargetMapY);
+ if ((curDoorState != k5_doorState_DESTROYED) && (projectileAssociatedThing == Thing::_explOpenDoor)) {
+ if (curDoor->hasButton())
+ _vm->_moveSens->addEvent(k10_TMEventTypeDoor, projectileTargetMapX, projectileTargetMapY, 0, k2_SensorEffToggle, _vm->_gameTime + 1);
+ break;
+ }
+
+ if ((curDoorState == k5_doorState_DESTROYED) || (curDoorState <= k1_doorState_FOURTH))
+ return false;
+
+ DoorInfo curDoorInfo = _vm->_dungeonMan->_currMapDoorInfo[curDoor->getType()];
+ if (getFlag(curDoorInfo._attributes, k0x0002_MaskDoorInfo_ProjectilesCanPassThrough)) {
+ if (projectileAssociatedThingType == k15_ExplosionThingType) {
+ if (projectileAssociatedThing.toUint16() >= Thing::_explHarmNonMaterial.toUint16())
+ return false;
+ } else {
+ int16 associatedThingIndex = _vm->_dungeonMan->getObjectInfoIndex(projectileAssociatedThing);
+ uint16 associatedAllowedSlots = _vm->_dungeonMan->_objectInfos[associatedThingIndex].getAllowedSlots();
+ int16 iconIndex = _vm->_objectMan->getIconIndex(projectileAssociatedThing);
+
+ if ((projectileThingData->_attack > _vm->getRandomNumber(128))
+ && getFlag(associatedAllowedSlots, k0x0100_ObjectAllowedSlotPouchPassAndThroughDoors)
+ && ( (projectileAssociatedThingType != k10_JunkThingType)
+ || (iconIndex < kDMIconIndiceJunkIronKey)
+ || (iconIndex > kDMIconIndiceJunkMasterKey)
+ )) {
+ return false;
+ }
+ }
+ }
+ attack = getProjectileImpactAttack(projectileThingData, projectileAssociatedThing) + 1;
+ _vm->_groupMan->groupIsDoorDestoryedByAttack(projectileTargetMapX, projectileTargetMapY, attack + _vm->getRandomNumber(attack), false, 0);
+ }
+ break;
+ case kM2_ChampionElemType:
+ championIndex = _vm->_championMan->getIndexInCell(cell);
+ if (championIndex < 0)
+ return false;
+
+ championAttack = attack = getProjectileImpactAttack(projectileThingData, projectileAssociatedThing);
+ break;
+ case kM1_CreatureElemType: {
+ Group *curGroup = (Group *)_vm->_dungeonMan->getThingData(_vm->_groupMan->groupGetThing(projectileTargetMapX, projectileTargetMapY));
+ uint16 curCreatureIndex = _vm->_groupMan->getCreatureOrdinalInCell(curGroup, cell);
+ if (!curCreatureIndex)
+ return false;
+
+ curCreatureIndex--;
+ uint16 curCreatureType = curGroup->_type;
+ CreatureInfo *curCreatureInfo = &_vm->_dungeonMan->_creatureInfos[curCreatureType];
+ if ((projectileAssociatedThing == Thing::_explFireBall) && (curCreatureType == k11_CreatureTypeBlackFlame)) {
+ uint16 *curCreatureHealth = &curGroup->_health[curCreatureIndex];
+ *curCreatureHealth = MIN(1000, *curCreatureHealth + getProjectileImpactAttack(projectileThingData, projectileAssociatedThing));
+ goto T0217044;
+ }
+ if (getFlag(curCreatureInfo->_attributes, k0x0040_MaskCreatureInfo_nonMaterial) && (projectileAssociatedThing != Thing::_explHarmNonMaterial))
+ return false;
+
+ attack = (uint16)((unsigned long)getProjectileImpactAttack(projectileThingData, projectileAssociatedThing) << 6) / curCreatureInfo->_defense;
+ if (attack) {
+ int16 outcome = _vm->_groupMan->groupGetDamageCreatureOutcome(curGroup, curCreatureIndex, projectileTargetMapX, projectileTargetMapY, attack + _vm->_groupMan->groupGetResistanceAdjustedPoisonAttack(curCreatureType, _projectilePoisonAttack), true);
+ if (outcome != k0_outcomeKilledNoCreaturesInGroup)
+ _vm->_groupMan->processEvents29to41(projectileTargetMapX, projectileTargetMapY, kM2_TMEventTypeCreateReactionEvent30HitByProjectile, 0);
+
+ _creatureDamageOutcome = outcome;
+ if (!createExplosionOnImpact && (outcome == k0_outcomeKilledNoCreaturesInGroup)
+ && (projectileAssociatedThingType == k5_WeaponThingType)
+ && getFlag(curCreatureInfo->_attributes, k0x0400_MaskCreatureInfo_keepThrownSharpWeapon)) {
+ Weapon *weapon = (Weapon *)_vm->_dungeonMan->getThingData(projectileAssociatedThing);
+ WeaponType weaponType = weapon->getType();
+ if ((weaponType == k8_WeaponTypeDagger) || (weaponType == k27_WeaponTypeArrow)
+ || (weaponType == k28_WeaponTypeSlayer) || (weaponType == k31_WeaponTypePoisonDart)
+ || (weaponType == k32_WeaponTypeThrowingStar))
+ curGroupSlot = &curGroup->_slot;
+ }
+ }
+ }
+ break;
+ }
+ if (championAttack && _projectilePoisonAttack && _vm->getRandomNumber(2)
+ && _vm->_championMan->addPendingDamageAndWounds_getDamage(championIndex, attack, kDMWoundHead | kDMWoundTorso, _projectileAttackType))
+ _vm->_championMan->championPoison(championIndex, _projectilePoisonAttack);
+
+ if (createExplosionOnImpact || removePotion) {
+ uint16 explosionAttack;
+ if (removePotion) {
+ projectileAssociatedThing = explosionThing;
+ explosionAttack = potionPower;
+ } else {
+ explosionAttack = projectileThingData->_kineticEnergy;
+ }
+ if ((projectileAssociatedThing == Thing::_explLightningBolt) && !(explosionAttack >>= 1))
+ goto T0217044;
+ createExplosion(projectileAssociatedThing, explosionAttack, mapXCombo, mapYCombo, (projectileAssociatedThing == Thing::_explPoisonCloud) ? k255_CreatureTypeSingleCenteredCreature : cell);
+ } else {
+ uint16 soundIndex;
+ if ((projectileAssociatedThing).getType() == k5_WeaponThingType)
+ soundIndex = k00_soundMETALLIC_THUD;
+ else if (projectileAssociatedThing == Thing::_explPoisonBolt)
+ soundIndex = k13_soundSPELL;
+ else
+ soundIndex = k04_soundWOODEN_THUD_ATTACK_TROLIN_ANTMAN_STONE_GOLEM;
+
+ _vm->_sound->requestPlay(soundIndex, projectileMapX, projectileMapY, k1_soundModePlayIfPrioritized);
+ }
+T0217044:
+ if (removePotion) {
+ potion->_nextThing = Thing::_none;
+ projectileThingData->_slot = explosionThing;
+ }
+ _vm->_dungeonMan->unlinkThingFromList(projectileThing, Thing(0), projectileMapX, projectileMapY);
+ projectileDelete(projectileThing, curGroupSlot, projectileMapX, projectileMapY);
+ return true;
+}
+
+uint16 ProjExpl::getProjectileImpactAttack(Projectile *projectile, Thing thing) {
+ _projectilePoisonAttack = 0;
+ _projectileAttackType = kDMAttackTypeBlunt;
+
+ uint16 kineticEnergy = projectile->_kineticEnergy;
+ ThingType thingType = thing.getType();
+ uint16 attack;
+ if (thingType != k15_ExplosionThingType) {
+ if (thingType == k5_WeaponThingType) {
+ WeaponInfo *weaponInfo = _vm->_dungeonMan->getWeaponInfo(thing);
+ attack = weaponInfo->_kineticEnergy;
+ _projectileAttackType = kDMAttackTypeBlunt;
+ } else
+ attack = _vm->getRandomNumber(4);
+
+ attack += _vm->_dungeonMan->getObjectWeight(thing) >> 1;
+ } else if (thing == Thing::_explSlime) {
+ attack = _vm->getRandomNumber(16);
+ _projectilePoisonAttack = attack + 10;
+ attack += _vm->getRandomNumber(32);
+ } else {
+ if (thing.toUint16() >= Thing::_explHarmNonMaterial.toUint16()) {
+ _projectileAttackType = kDMAttackTypeMagic;
+ if (thing == Thing::_explPoisonBolt) {
+ _projectilePoisonAttack = kineticEnergy;
+ return 1;
+ }
+ return 0;
+ }
+ _projectileAttackType = kDMAttackTypeFire;
+ attack = _vm->getRandomNumber(16) + _vm->getRandomNumber(16) + 10;
+ if (thing == Thing::_explLightningBolt) {
+ _projectileAttackType = kDMAttackTypeLightning;
+ attack *= 5;
+ }
+ }
+ attack = ((attack + kineticEnergy) >> 4) + 1;
+ attack += _vm->getRandomNumber((attack >> 1) + 1) + _vm->getRandomNumber(4);
+ attack = MAX(attack >> 1, attack - (32 - (projectile->_attack >> 3)));
+ return attack;
+}
+
+void ProjExpl::createExplosion(Thing explThing, uint16 attack, uint16 mapXCombo, uint16 mapYCombo, uint16 cell) {
+ Thing unusedThing = _vm->_dungeonMan->getUnusedThing(k15_ExplosionThingType);
+ if (unusedThing == Thing::_none)
+ return;
+
+ Explosion *explosion = &((Explosion *)_vm->_dungeonMan->_thingData[k15_ExplosionThingType])[(unusedThing).getIndex()];
+ int16 projectileTargetMapX;
+ int16 projectileTargetMapY;
+ uint16 projectileMapX = mapXCombo;
+ uint16 projectileMapY = mapYCombo;
+
+ if (mapXCombo <= 255) {
+ projectileTargetMapX = mapXCombo;
+ projectileTargetMapY = mapYCombo;
+ } else {
+ projectileTargetMapX = mapXCombo & 0x00FF;
+ projectileTargetMapY = mapYCombo & 0x00FF;
+ projectileMapX >>= 8;
+ projectileMapX--;
+ projectileMapY >>= 8;
+ }
+
+ if (cell == k255_CreatureTypeSingleCenteredCreature)
+ explosion->setCentered(true);
+ else {
+ explosion->setCentered(false);
+ unusedThing = thingWithNewCell(unusedThing, cell);
+ }
+
+ explosion->setType(explThing.toUint16() - Thing::_firstExplosion.toUint16());
+ explosion->setAttack(attack);
+ if (explThing.toUint16() < Thing::_explHarmNonMaterial.toUint16()) {
+ uint16 soundIndex = (attack > 80) ? k05_soundSTRONG_EXPLOSION : k20_soundWEAK_EXPLOSION;
+ _vm->_sound->requestPlay(soundIndex, projectileMapX, projectileMapY, k1_soundModePlayIfPrioritized);
+ } else if (explThing != Thing::_explSmoke)
+ _vm->_sound->requestPlay(k13_soundSPELL, projectileMapX, projectileMapY, k1_soundModePlayIfPrioritized);
+
+ _vm->_dungeonMan->linkThingToList(unusedThing, Thing(0), projectileMapX, projectileMapY);
+ TimelineEvent newEvent;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_currMapIndex, _vm->_gameTime + ((explThing == Thing::_explRebirthStep1) ? 5 : 1));
+ newEvent._type = k25_TMEventTypeExplosion;
+ newEvent._priority = 0;
+ newEvent._C._slot = unusedThing.toUint16();
+ newEvent._B._location._mapX = projectileMapX;
+ newEvent._B._location._mapY = projectileMapY;
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ if ((explThing == Thing::_explLightningBolt) || (explThing == Thing::_explFireBall)) {
+ projectileMapX = projectileTargetMapX;
+ projectileMapY = projectileTargetMapY;
+ attack = (attack >> 1) + 1;
+ attack += _vm->getRandomNumber(attack) + 1;
+ if ((explThing == Thing::_explFireBall) || (attack >>= 1)) {
+ if ((_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (projectileMapX == _vm->_dungeonMan->_partyMapX) && (projectileMapY == _vm->_dungeonMan->_partyMapY)) {
+ int16 wounds = kDMWoundReadHand | kDMWoundActionHand | kDMWoundHead | kDMWoundTorso | kDMWoundLegs | kDMWoundFeet;
+ _vm->_championMan->getDamagedChampionCount(attack, wounds, kDMAttackTypeFire);
+ } else {
+ unusedThing = _vm->_groupMan->groupGetThing(projectileMapX, projectileMapY);
+ if (unusedThing != Thing::_endOfList) {
+ Group *creatureGroup = (Group *)_vm->_dungeonMan->getThingData(unusedThing);
+ CreatureInfo *creatureInfo = &_vm->_dungeonMan->_creatureInfos[creatureGroup->_type];
+ int16 creatureFireResistance = creatureInfo->getFireResistance();
+ if (creatureFireResistance != k15_immuneToFire) {
+ if (getFlag(creatureInfo->_attributes, k0x0040_MaskCreatureInfo_nonMaterial))
+ attack >>= 2;
+
+ if ((attack -= _vm->getRandomNumber((creatureFireResistance << 1) + 1)) > 0)
+ _creatureDamageOutcome = _vm->_groupMan->getDamageAllCreaturesOutcome(creatureGroup, projectileMapX, projectileMapY, attack, true);
+
+ }
+ }
+ }
+ }
+ }
+}
+
+int16 ProjExpl::projectileGetImpactCount(int16 impactType, int16 mapX, int16 mapY, int16 cell) {
+ int16 impactCount = 0;
+ _creatureDamageOutcome = k0_outcomeKilledNoCreaturesInGroup;
+
+ for (Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY); curThing != Thing::_endOfList; ) {
+ if (((curThing).getType() == k14_ProjectileThingType) && ((curThing).getCell() == cell) &&
+ hasProjectileImpactOccurred(impactType, mapX, mapY, cell, curThing)) {
+ projectileDeleteEvent(curThing);
+ impactCount++;
+ if ((impactType == kM1_CreatureElemType) && (_creatureDamageOutcome == k2_outcomeKilledAllCreaturesInGroup))
+ break;
+
+ Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
+ } else
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+ return impactCount;
+}
+
+void ProjExpl::projectileDeleteEvent(Thing thing) {
+ Projectile *projectile = (Projectile *)_vm->_dungeonMan->getThingData(thing);
+ _vm->_timeline->deleteEvent(projectile->_eventIndex);
+}
+
+void ProjExpl::projectileDelete(Thing projectileThing, Thing *groupSlot, int16 mapX, int16 mapY) {
+ Projectile *projectile = (Projectile *)_vm->_dungeonMan->getThingData(projectileThing);
+ Thing projectileSlotThing = projectile->_slot;
+ if (projectileSlotThing.getType() != k15_ExplosionThingType) {
+ if (groupSlot != NULL) {
+ Thing previousThing = *groupSlot;
+ if (previousThing == Thing::_endOfList) {
+ Thing *genericThing = (Thing *)_vm->_dungeonMan->getThingData(projectileSlotThing);
+ *genericThing = Thing::_endOfList;
+ *groupSlot = projectileSlotThing;
+ } else
+ _vm->_dungeonMan->linkThingToList(projectileSlotThing, previousThing, kM1_MapXNotOnASquare, 0);
+ } else
+ _vm->_moveSens->getMoveResult(Thing((projectileSlotThing).getTypeAndIndex() | getFlag(projectileThing.toUint16(), 0xC)), -2, 0, mapX, mapY);
+ }
+ projectile->_nextThing = Thing::_none;
+}
+
+void ProjExpl::processEvents48To49(TimelineEvent *event) {
+ int16 sourceMapX = -1;
+ int16 sourceMapY = -1;
+ TimelineEvent firstEvent = *event;
+ TimelineEvent *curEvent = &firstEvent;
+ Thing projectileThingNewCell = Thing(curEvent->_B._slot);
+ Thing projectileThing = projectileThingNewCell;
+ Projectile *projectile = (Projectile *)_vm->_dungeonMan->getThingData(projectileThing);
+ int16 destinationMapX = curEvent->_C._projectile.getMapX();
+ int16 destinationMapY = curEvent->_C._projectile.getMapY();
+
+ if (curEvent->_type == k48_TMEventTypeMoveProjectileIgnoreImpacts)
+ curEvent->_type = k49_TMEventTypeMoveProjectile;
+ else {
+ uint16 projectileCurCell = projectileThingNewCell.getCell();
+ if ((_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (destinationMapX == _vm->_dungeonMan->_partyMapX) && (destinationMapY == _vm->_dungeonMan->_partyMapY) && hasProjectileImpactOccurred(kM2_ChampionElemType, destinationMapX, destinationMapY, projectileCurCell, projectileThingNewCell))
+ return;
+
+ if ((_vm->_groupMan->groupGetThing(destinationMapX, destinationMapY) != Thing::_endOfList) && hasProjectileImpactOccurred(kM1_CreatureElemType, destinationMapX, destinationMapY, projectileCurCell, projectileThing))
+ return;
+
+ uint16 stepEnergy = curEvent->_C._projectile.getStepEnergy();
+ if (projectile->_kineticEnergy <= stepEnergy) {
+ _vm->_dungeonMan->unlinkThingFromList(projectileThingNewCell = projectileThing, Thing(0), destinationMapX, destinationMapY);
+ projectileDelete(projectileThingNewCell, NULL, destinationMapX, destinationMapY);
+ return;
+ }
+ projectile->_kineticEnergy -= stepEnergy;
+ if (projectile->_attack < stepEnergy)
+ projectile->_attack = 0;
+ else
+ projectile->_attack -= stepEnergy;
+ }
+ uint16 projectileDirection = curEvent->_C._projectile.getDir();
+ projectileThingNewCell = Thing(curEvent->_B._slot);
+ uint16 projectileNewCell = projectileThingNewCell.getCell();
+ bool projectileMovesToOtherSquare = (projectileDirection == projectileNewCell) || (returnNextVal(projectileDirection) == projectileNewCell);
+ if (projectileMovesToOtherSquare) {
+ sourceMapX = destinationMapX;
+ sourceMapY = destinationMapY;
+ destinationMapX += _vm->_dirIntoStepCountEast[projectileDirection], destinationMapY += _vm->_dirIntoStepCountNorth[projectileDirection];
+ Square destSquare = _vm->_dungeonMan->getSquare(destinationMapX, destinationMapY);
+ SquareType destSquareType = destSquare.getType();
+ if ((destSquareType == k0_WallElemType) ||
+ ((destSquareType == k6_FakeWallElemType) && !getFlag(destSquare.toByte(), (k0x0001_FakeWallImaginary | k0x0004_FakeWallOpen))) ||
+ ((destSquareType == k3_StairsElemType) && (Square(_vm->_dungeonMan->_currMapData[sourceMapX][sourceMapY]).getType() == k3_StairsElemType))) {
+ if (hasProjectileImpactOccurred(destSquare.getType(), sourceMapX, sourceMapY, projectileNewCell, projectileThingNewCell)) {
+ return;
+ }
+ }
+ }
+
+ if ((projectileDirection & 0x0001) == (projectileNewCell & 0x0001))
+ projectileNewCell--;
+ else
+ projectileNewCell++;
+
+ projectileThingNewCell = thingWithNewCell(projectileThingNewCell, projectileNewCell &= 0x0003);
+ if (projectileMovesToOtherSquare) {
+ _vm->_moveSens->getMoveResult(projectileThingNewCell, sourceMapX, sourceMapY, destinationMapX, destinationMapY);
+ curEvent->_C._projectile.setMapX(_vm->_moveSens->_moveResultMapX);
+ curEvent->_C._projectile.setMapY(_vm->_moveSens->_moveResultMapY);
+ curEvent->_C._projectile.setDir((Direction)_vm->_moveSens->_moveResultDir);
+ projectileThingNewCell = thingWithNewCell(projectileThingNewCell, _vm->_moveSens->_moveResultCell);
+ M31_setMap(curEvent->_mapTime, _vm->_moveSens->_moveResultMapIndex);
+ } else {
+ if ((Square(_vm->_dungeonMan->getSquare(destinationMapX, destinationMapY)).getType() == k4_DoorElemType) && hasProjectileImpactOccurred(k4_DoorElemType, destinationMapX, destinationMapY, projectileNewCell, projectileThing))
+ return;
+
+ _vm->_dungeonMan->unlinkThingFromList(projectileThingNewCell, Thing(0), destinationMapX, destinationMapY);
+ _vm->_dungeonMan->linkThingToList(projectileThingNewCell, Thing(0), destinationMapX, destinationMapY);
+ }
+
+ // This code is from CSB20. The projectiles move at the same speed on all maps instead of moving slower on maps other than the party map */
+ curEvent->_mapTime++;
+
+ curEvent->_B._slot = projectileThingNewCell.toUint16();
+ projectile->_eventIndex = _vm->_timeline->addEventGetEventIndex(curEvent);
+}
+
+void ProjExpl::processEvent25(TimelineEvent *event) {
+ uint16 mapX = event->_B._location._mapX;
+ uint16 mapY = event->_B._location._mapY;
+ Explosion *explosion = &((Explosion *)_vm->_dungeonMan->_thingData[k15_ExplosionThingType])[Thing((event->_C._slot)).getIndex()];
+ int16 curSquareType = Square(_vm->_dungeonMan->_currMapData[mapX][mapY]).getType();
+ bool explosionOnPartySquare = (_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (mapX == _vm->_dungeonMan->_partyMapX) && (mapY == _vm->_dungeonMan->_partyMapY);
+ Thing groupThing = _vm->_groupMan->groupGetThing(mapX, mapY);
+
+ Group *group = nullptr;
+ CreatureInfo *creatureInfo = nullptr;
+
+ uint16 creatureType = 0;
+ if (groupThing != Thing::_endOfList) {
+ group = (Group *)_vm->_dungeonMan->getThingData(groupThing);
+ creatureType = group->_type;
+ creatureInfo = &_vm->_dungeonMan->_creatureInfos[creatureType];
+ }
+
+ Thing explosionThing = Thing(Thing::_firstExplosion.toUint16() + explosion->getType());
+ int16 attack;
+ if (explosionThing == Thing::_explPoisonCloud)
+ attack = MAX(1, MIN(explosion->getAttack() >> 5, 4) + _vm->getRandomNumber(2)); /* Value between 1 and 5 */
+ else {
+ attack = (explosion->getAttack() >> 1) + 1;
+ attack += _vm->getRandomNumber(attack) + 1;
+ }
+
+ bool AddEventFl = false;
+
+ switch (explosionThing.toUint16()) {
+ case 0xFF82:
+ if (!(attack >>= 1))
+ break;
+ case 0xFF80:
+ if (curSquareType == k4_DoorElemType)
+ _vm->_groupMan->groupIsDoorDestoryedByAttack(mapX, mapY, attack, true, 0);
+
+ break;
+ case 0xFF83:
+ if ((groupThing != Thing::_endOfList) && getFlag(creatureInfo->_attributes, k0x0040_MaskCreatureInfo_nonMaterial)) {
+ if ((creatureType == k19_CreatureTypeMaterializerZytaz) && (_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex)) {
+ int16 nonMaterialAdditionalAttack = attack >> 3;
+ attack -= nonMaterialAdditionalAttack;
+ nonMaterialAdditionalAttack <<= 1;
+ nonMaterialAdditionalAttack++;
+ int16 creatureCount = group->getCount();
+ do {
+ if (getFlag(_vm->_groupMan->_activeGroups[group->getActiveGroupIndex()]._aspect[creatureCount], k0x0080_MaskActiveGroupIsAttacking)) /* Materializer / Zytaz can only be damaged while they are attacking */
+ _vm->_groupMan->groupGetDamageCreatureOutcome(group, creatureCount, mapX, mapY, attack + _vm->getRandomNumber(nonMaterialAdditionalAttack) + _vm->getRandomNumber(4), true);
+ } while (--creatureCount >= 0);
+ } else
+ _vm->_groupMan->getDamageAllCreaturesOutcome(group, mapX, mapY, attack, true);
+ }
+ break;
+ case 0xFFE4:
+ explosion->setType(explosion->getType() + 1);
+ _vm->_sound->requestPlay(k05_soundSTRONG_EXPLOSION, mapX, mapY, k1_soundModePlayIfPrioritized);
+ AddEventFl = true;
+ break;
+ case 0xFFA8:
+ if (explosion->getAttack() > 55) {
+ explosion->setAttack(explosion->getAttack() - 40);
+ AddEventFl = true;
+ }
+ break;
+ case 0xFF87:
+ if (explosionOnPartySquare)
+ _vm->_championMan->getDamagedChampionCount(attack, kDMWoundNone, kDMAttackTypeNormal);
+ else if ((groupThing != Thing::_endOfList)
+ && (attack = _vm->_groupMan->groupGetResistanceAdjustedPoisonAttack(creatureType, attack))
+ && (_vm->_groupMan->getDamageAllCreaturesOutcome(group, mapX, mapY, attack, true) != k2_outcomeKilledAllCreaturesInGroup)
+ && (attack > 2)) {
+ _vm->_groupMan->processEvents29to41(mapX, mapY, kM3_TMEventTypeCreateReactionEvent29DangerOnSquare, 0);
+ }
+ if (explosion->getAttack() >= 6) {
+ explosion->setAttack(explosion->getAttack() - 3);
+ AddEventFl = true;
+ }
+ break;
+ }
+ if (AddEventFl) {
+ TimelineEvent newEvent;
+ newEvent = *event;
+ newEvent._mapTime++;
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ } else {
+ _vm->_dungeonMan->unlinkThingFromList(Thing(event->_C._slot), Thing(0), mapX, mapY);
+ explosion->setNextThing(Thing::_none);
+ }
+}
+}
diff --git a/engines/dm/projexpl.h b/engines/dm/projexpl.h
new file mode 100644
index 0000000000..7559fef361
--- /dev/null
+++ b/engines/dm/projexpl.h
@@ -0,0 +1,107 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+#ifndef DM_PROJEXPL_H
+#define DM_PROJEXPL_H
+
+#include "dm/dm.h"
+
+namespace DM {
+
+#define k0_outcomeKilledNoCreaturesInGroup 0 // @ C0_OUTCOME_KILLED_NO_CREATURES_IN_GROUP
+#define k1_outcomeKilledSomeCreaturesInGroup 1 // @ C1_OUTCOME_KILLED_SOME_CREATURES_IN_GROUP
+#define k2_outcomeKilledAllCreaturesInGroup 2 // @ C2_OUTCOME_KILLED_ALL_CREATURES_IN_GROUP
+
+#define k00_soundMETALLIC_THUD 0 // @ C00_SOUND_METALLIC_THUD
+#define k01_soundSWITCH 1 // @ C01_SOUND_SWITCH
+#define k02_soundDOOR_RATTLE 2 // @ C02_SOUND_DOOR_RATTLE
+#define k03_soundATTACK_PAIN_RAT_HELLHOUND_RED_DRAGON 3 // @ C03_SOUND_ATTACK_PAIN_RAT_HELLHOUND_RED_DRAGON
+#define k04_soundWOODEN_THUD_ATTACK_TROLIN_ANTMAN_STONE_GOLEM 4 // @ C04_SOUND_WOODEN_THUD_ATTACK_TROLIN_ANTMAN_STONE_GOLEM
+#define k05_soundSTRONG_EXPLOSION 5 // @ C05_SOUND_STRONG_EXPLOSION
+#define k06_soundSCREAM 6 // @ C06_SOUND_SCREAM
+#define k07_soundATTACK_MUMMY_GHOST_RIVE 7 // @ C07_SOUND_ATTACK_MUMMY_GHOST_RIVE
+#define k08_soundSWALLOW 8 // @ C08_SOUND_SWALLOW
+#define k09_soundCHAMPION_0_DAMAGED 9 // @ C09_SOUND_CHAMPION_0_DAMAGED
+#define k10_soundCHAMPION_1_DAMAGED 10 // @ C10_SOUND_CHAMPION_1_DAMAGED
+#define k11_soundCHAMPION_2_DAMAGED 11 // @ C11_SOUND_CHAMPION_2_DAMAGED
+#define k12_soundCHAMPION_3_DAMAGED 12 // @ C12_SOUND_CHAMPION_3_DAMAGED
+#define k13_soundSPELL 13 // @ C13_SOUND_SPELL
+#define k14_soundATTACK_SCREAMER_OITU 14 // @ C14_SOUND_ATTACK_SCREAMER_OITU
+#define k15_soundATTACK_GIANT_SCORPION_SCORPION 15 // @ C15_SOUND_ATTACK_GIANT_SCORPION_SCORPION
+#define k16_soundCOMBAT_ATTACK_SKELETON_ANIMATED_ARMOUR_DETH_KNIGHT 16 // @ C16_SOUND_COMBAT_ATTACK_SKELETON_ANIMATED_ARMOUR_DETH_KNIGHT
+#define k17_soundBUZZ 17 // @ C17_SOUND_BUZZ
+#define k18_soundPARTY_DAMAGED 18 // @ C18_SOUND_PARTY_DAMAGED
+#define k19_soundATTACK_MAGENTA_WORM_WORM 19 // @ C19_SOUND_ATTACK_MAGENTA_WORM_WORM
+#define k20_soundWEAK_EXPLOSION 20 // @ C20_SOUND_WEAK_EXPLOSION
+#define k21_soundATTACK_GIGGLER 21 // @ C21_SOUND_ATTACK_GIGGLER
+#define k22_soundMOVE_ANIMATED_ARMOUR_DETH_KNIGHT 22 // @ C22_SOUND_MOVE_ANIMATED_ARMOUR_DETH_KNIGHT
+#define k23_soundMOVE_COUATL_GIANT_WASP_MUNCHER 23 // @ C23_SOUND_MOVE_COUATL_GIANT_WASP_MUNCHER
+#define k24_soundMOVE_MUMMY_TROLIN_ANTMAN_STONE_GOLEM_GIGGLER_VEXIRK_DEMON 24 // @ C24_SOUND_MOVE_MUMMY_TROLIN_ANTMAN_STONE_GOLEM_GIGGLER_VEXIRK_DEMON
+#define k25_soundBLOW_HORN 25 // @ C25_SOUND_BLOW_HORN
+#define k26_soundMOVE_SCREAMER_ROCK_ROCKPILE_MAGENTA_WORM_WORM_PAIN_RAT_HELLHOUND_RUSTER_GIANT_SCORPION_SCORPION_OITU 26 // @ C26_SOUND_MOVE_SCREAMER_ROCK_ROCKPILE_MAGENTA_WORM_WORM_PAIN_RAT_HELLHOUND_RUSTER_GIANT_SCORPION_SCORPION_OITU
+#define k27_soundMOVE_SWAMP_SLIME_SLIME_DEVIL_WATER_ELEMENTAL 27 // @ C27_SOUND_MOVE_SWAMP_SLIME_SLIME_DEVIL_WATER_ELEMENTAL
+#define k28_soundWAR_CRY 28 // @ C28_SOUND_WAR_CRY
+#define k29_soundATTACK_ROCK_ROCKPILE 29 // @ C29_SOUND_ATTACK_ROCK_ROCKPILE
+#define k30_soundATTACK_WATER_ELEMENTAL 30 // @ C30_SOUND_ATTACK_WATER_ELEMENTAL
+#define k31_soundATTACK_COUATL 31 // @ C31_SOUND_ATTACK_COUATL
+#define k32_soundMOVE_RED_DRAGON 32 // @ C32_SOUND_MOVE_RED_DRAGON
+#define k33_soundMOVE_SKELETON 33 // @ C33_SOUND_MOVE_SKELETON
+
+#define M31_setMap(map_time, map) ((map_time) = (((map_time) & 0x00FFFFFF) | (((int32)(map)) << 24)))
+
+class TimelineEvent;
+class Projectile;
+
+class ProjExpl {
+ DMEngine *_vm;
+public:
+ int16 _creatureDamageOutcome; // @ G0364_i_CreatureDamageOutcome
+ int16 _secondaryDirToOrFromParty; // @ G0363_i_SecondaryDirectionToOrFromParty
+ int32 _lastCreatureAttackTime; // @ G0361_l_LastCreatureAttackTime
+ bool _createLauncherProjectile; // @ G0365_B_CreateLauncherProjectile
+ int16 _projectilePoisonAttack; // @ G0366_i_ProjectilePoisonAttack
+ int16 _projectileAttackType; // @ G0367_i_ProjectileAttackType
+ int32 _lastPartyMovementTime; // @ G0362_l_LastPartyMovementTime
+
+ explicit ProjExpl(DMEngine *vm);
+
+ void createProjectile(Thing thing, int16 mapX, int16 mapY, uint16 cell, Direction dir,
+ byte kineticEnergy, byte attack, byte stepEnergy); // @ F0212_PROJECTILE_Create
+ bool hasProjectileImpactOccurred(int16 impactType, int16 mapXCombo, int16 mapYCombo,
+ int16 cell, Thing projectileThing); // @ F0217_PROJECTILE_HasImpactOccured
+ uint16 getProjectileImpactAttack(Projectile *projectile, Thing thing); // @ F0216_PROJECTILE_GetImpactAttack
+ void createExplosion(Thing explThing, uint16 attack, uint16 mapXCombo,
+ uint16 mapYCombo, uint16 cell); // @ F0213_EXPLOSION_Create
+ int16 projectileGetImpactCount(int16 impactType, int16 mapX, int16 mapY, int16 cell); // @ F0218_PROJECTILE_GetImpactCount
+ void projectileDeleteEvent(Thing thing); // @ F0214_PROJECTILE_DeleteEvent
+ void projectileDelete(Thing projectileThing, Thing *groupSlot, int16 mapX, int16 mapY); // @ F0215_PROJECTILE_Delete
+ void processEvents48To49(TimelineEvent *event); // @ F0219_PROJECTILE_ProcessEvents48To49_Projectile
+ void processEvent25(TimelineEvent *event); // @ F0220_EXPLOSION_ProcessEvent25_Explosion
+};
+
+}
+
+#endif
diff --git a/engines/dm/sounds.cpp b/engines/dm/sounds.cpp
new file mode 100644
index 0000000000..bcc333c7b0
--- /dev/null
+++ b/engines/dm/sounds.cpp
@@ -0,0 +1,226 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+#include "audio/mixer.h"
+#include "advancedDetector.h"
+
+#include "dm/dm.h"
+#include "dm/gfx.h"
+#include "dm/timeline.h"
+#include "dm/dungeonman.h"
+#include "dm/sounds.h"
+
+namespace DM {
+
+SoundMan *SoundMan::getSoundMan(DMEngine *vm, const DMADGameDescription *gameVersion) {
+ switch (gameVersion->_desc.platform) {
+ default:
+ warning("Unknown platform, using default Amiga SoundMan");
+ // No break on purpose
+ case Common::kPlatformAmiga:
+ return new SoundMan(vm);
+ case Common::kPlatformAtariST:
+ return new SoundMan_Atari(vm);
+ }
+}
+
+void SoundMan::initConstants() {
+ Sound sounds[k34_D13_soundCount] = {
+ Sound(533, 112, 11, 3, 6), /* k00_soundMETALLIC_THUD 0 */
+ Sound(534, 112, 15, 0, 3), /* k01_soundSWITCH 1 */
+ Sound(535, 112, 72, 3, 6), /* k02_soundDOOR_RATTLE 2 */
+ Sound(550, 112, 60, 3, 5), /* k03_soundATTACK_PAIN_RAT_HELLHOUND_RED_DRAGON 3 */
+ Sound(536, 112, 10, 3, 6), /* k04_soundWOODEN_THUD_ATTACK_TROLIN_ANTMAN_STONE_GOLEM 4 */
+ Sound(537, 112, 99, 3, 7), /* k05_soundSTRONG_EXPLOSION 5 */
+ Sound(539, 112, 110, 3, 6), /* k06_soundSCREAM 6 */
+ Sound(551, 112, 55, 3, 5), /* k07_soundATTACK_MUMMY_GHOST_RIVE 7 */
+ Sound(540, 112, 2, 3, 6), /* k08_soundSWALLOW 8 */
+ Sound(541, 112, 80, 3, 6), /* k09_soundCHAMPION_0_DAMAGED 9 */
+ Sound(542, 112, 82, 3, 6), /* k10_soundCHAMPION_1_DAMAGED 10 */
+ Sound(543, 112, 84, 3, 6), /* k11_soundCHAMPION_2_DAMAGED 11 */
+ Sound(544, 112, 86, 3, 6), /* k12_soundCHAMPION_3_DAMAGED 12 */
+ Sound(545, 112, 95, 3, 6), /* k13_soundSPELL 13 */
+ Sound(552, 112, 57, 3, 5), /* k14_soundATTACK_SCREAMER_OITU 14 */
+ Sound(553, 112, 52, 3, 5), /* k15_soundATTACK_GIANT_SCORPION_SCORPION 15 */
+ Sound(546, 112, 40, 2, 4), /* k16_soundCOMBAT_ATTACK_SKELETON_ANIMATED_ARMOUR_DETH_KNIGHT 16 */
+ Sound(547, 112, 70, 1, 4), /* k17_soundBUZZ 17 */
+ Sound(549, 138, 75, 3, 6), /* k18_soundPARTY_DAMAGED 18 */
+ Sound(554, 112, 50, 3, 5), /* k19_soundATTACK_MAGENTA_WORM_WORM 19 */
+ Sound(537, 112, 98, 0, 4), /* k20_soundWEAK_EXPLOSION 20 */
+ Sound(555, 112, 96, 2, 4), /* k21_soundATTACK_GIGGLER 21 */
+ Sound(563, 138, 24, 0, 4), /* k22_soundMOVE_ANIMATED_ARMOUR_DETH_KNIGHT 22 Atari ST: not present */
+ Sound(564, 138, 21, 0, 4), /* k23_soundMOVE_COUATL_GIANT_WASP_MUNCHER 23 Atari ST: not present */
+ Sound(565, 138, 23, 0, 4), /* k24_soundMOVE_MUMMY_TROLIN_ANTMAN_STONE_GOLEM_GIGGLER_VEXIRK_DEMON 24 Atari ST: not present */
+ Sound(566, 138, 105, 0, 4), /* k25_soundBLOW_HORN 25 Atari ST: not present */
+ Sound(567, 138, 27, 0, 4), /* k26_soundMOVE_SCREAMER_ROCK_ROCKPILE_MAGENTA_WORM_WORM_PAIN_RAT_HELLHOUND_RUSTER_GIANT_SCORPION_SCORPION_OITU 26 Atari ST: not present */
+ Sound(568, 138, 28, 0, 4), /* k27_soundMOVE_SWAMP_SLIME_SLIME_DEVIL_WATER_ELEMENTAL 27 Atari ST: not present */
+ Sound(569, 138, 106, 0, 4), /* k28_soundWAR_CRY 28 Atari ST: not present */
+ Sound(570, 138, 56, 0, 4), /* k29_soundATTACK_ROCK_ROCKPILE 29 Atari ST: not present */
+ Sound(571, 138, 58, 0, 4), /* k30_soundATTACK_WATER_ELEMENTAL 30 Atari ST: not present */
+ Sound(572, 112, 53, 0, 4), /* k31_soundATTACK_COUATL 31 Atari ST: not present */
+ Sound(573, 138, 29, 0, 4), /* k32_soundMOVE_RED_DRAGON 32 Atari ST: not present */
+ Sound(574, 150, 22, 0, 4) /* k33_soundMOVE_SKELETON 33 Atari ST: not present */
+ };
+ for (int i = 0; i < k34_D13_soundCount; i++)
+ _sounds[i] = sounds[i];
+}
+
+SoundMan::SoundMan(DMEngine *vm) : _vm(vm) {
+ initConstants();
+}
+
+SoundMan::~SoundMan() {
+ for (uint16 i = 0; i < k34_D13_soundCount; ++i)
+ delete[] _soundData[i]._firstSample;
+}
+
+void SoundMan::loadSounds() {
+ for (uint16 soundIndex = 0; soundIndex < k34_D13_soundCount; ++soundIndex) {
+ SoundData *soundData = _soundData + soundIndex;
+
+ uint16 graphicIndex = _sounds[soundIndex]._graphicIndex;
+ soundData->_byteCount = _vm->_displayMan->getCompressedDataSize(graphicIndex) - 2; // the header is 2 bytes long
+ soundData->_firstSample = new byte[soundData->_byteCount];
+
+ Common::MemoryReadStream stream = _vm->_displayMan->getCompressedData(graphicIndex);
+ soundData->_sampleCount = stream.readUint16BE();
+ stream.read(soundData->_firstSample, soundData->_byteCount);
+ }
+}
+
+void SoundMan::play(uint16 soundIndex, uint16 period, uint8 leftVolume, uint8 rightVolume) {
+ SoundData *sound = &_soundData[soundIndex];
+ Audio::AudioStream *stream = Audio::makeRawStream(sound->_firstSample, sound->_byteCount, (72800 / period) * 8, 0, DisposeAfterUse::NO);
+
+ signed char balance = ((int16)rightVolume - (int16)leftVolume) / 2;
+
+ Audio::SoundHandle handle;
+ _vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &handle, stream, -1, 127, balance);
+}
+
+void SoundMan::playPendingSound() {
+ while (!_pendingSounds.empty()) {
+ PendingSound pendingSound = _pendingSounds.pop();
+ play(pendingSound._soundIndex, _sounds[pendingSound._soundIndex]._period, pendingSound._leftVolume, pendingSound._rightVolume);
+ }
+}
+
+bool SoundMan::soundGetVolume(int16 mapX, int16 mapY, uint8 *leftVolume, uint8 *rightVolume) {
+ static byte distanceToSoundVolume[25][25] = {
+ {1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4},
+ {1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 5, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 4, 4, 4},
+ {1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 5, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 4, 4},
+ {1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 5, 7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 5, 4},
+ {1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 5, 8, 8, 7, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4},
+ {1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 4, 6, 9, 9, 8, 8, 8, 7, 7, 6, 6, 6, 5, 5, 5},
+ {1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 6, 10, 10, 10, 9, 8, 8, 7, 7, 6, 6, 5, 5, 5},
+ {1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 7, 12, 12, 11, 10, 9, 9, 8, 7, 7, 6, 6, 5, 5},
+ {1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 7, 15, 14, 13, 12, 11, 9, 8, 8, 7, 6, 6, 5, 5},
+ {1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 8, 20, 19, 16, 14, 12, 10, 9, 8, 7, 7, 6, 6, 5},
+ {1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 8, 29, 26, 21, 16, 13, 11, 10, 8, 7, 7, 6, 6, 5},
+ {1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 4, 8, 58, 41, 26, 19, 14, 12, 10, 9, 8, 7, 6, 6, 5},
+ {1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 6, 64, 58, 29, 20, 15, 12, 10, 9, 8, 7, 6, 6, 5},
+ {0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 3, 6, 41, 29, 19, 13, 10, 8, 7, 6, 6, 5, 5, 4, 4},
+ {0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 3, 6, 21, 19, 15, 12, 10, 8, 7, 6, 5, 5, 4, 4, 4},
+ {0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 3, 6, 14, 13, 12, 10, 9, 7, 7, 6, 5, 5, 4, 4, 4},
+ {0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 5, 11, 10, 10, 9, 8, 7, 6, 6, 5, 5, 4, 4, 4},
+ {0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 5, 9, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 4, 4},
+ {0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 5, 7, 7, 7, 7, 6, 6, 5, 5, 5, 4, 4, 4, 4},
+ {0, 1, 1, 1, 1, 1, 1, 2, 2, 1, 3, 4, 6, 6, 6, 6, 6, 5, 5, 5, 4, 4, 4, 4, 3},
+ {1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 6, 6, 5, 5, 5, 5, 5, 4, 4, 4, 4, 3, 3},
+ {1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 3, 3, 3},
+ {1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 5, 5, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3},
+ {1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3},
+ {1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3}};
+
+ int16 lineIndex = 0;
+ int16 rightVolumeColumnIndex = 0;
+
+ switch (_vm->_dungeonMan->_partyDir) {
+ case kDirNorth:
+ rightVolumeColumnIndex = mapX - _vm->_dungeonMan->_partyMapX;
+ lineIndex = mapY - _vm->_dungeonMan->_partyMapY;
+ break;
+ case kDirEast:
+ rightVolumeColumnIndex = mapY - _vm->_dungeonMan->_partyMapY;
+ lineIndex = -(mapX - _vm->_dungeonMan->_partyMapX);
+ break;
+ case kDirSouth:
+ rightVolumeColumnIndex = -(mapX - _vm->_dungeonMan->_partyMapX);
+ lineIndex = -(mapY - _vm->_dungeonMan->_partyMapY);
+ break;
+ case kDirWest:
+ rightVolumeColumnIndex = -(mapY - _vm->_dungeonMan->_partyMapY);
+ lineIndex = mapX - _vm->_dungeonMan->_partyMapX;
+ break;
+ }
+
+ if ((rightVolumeColumnIndex < -12) || (rightVolumeColumnIndex > 12)) /* Sound is not audible if source is more than 12 squares away from the party */
+ return false;
+
+ if ((lineIndex < -12) || (lineIndex > 12)) /* Sound is not audible if source is more than 12 squares away from the party */
+ return false;
+
+ int16 leftVolumeColumnIndex = -rightVolumeColumnIndex + 12;
+ rightVolumeColumnIndex += 12;
+ lineIndex += 12;
+ *rightVolume = distanceToSoundVolume[lineIndex][rightVolumeColumnIndex];
+ *leftVolume = distanceToSoundVolume[lineIndex][leftVolumeColumnIndex];
+ return true;
+}
+
+void SoundMan::requestPlay(uint16 soundIndex, int16 mapX, int16 mapY, uint16 mode) {
+ if (mode && (_vm->_dungeonMan->_currMapIndex != _vm->_dungeonMan->_partyMapIndex))
+ return;
+
+ Sound *sound = &_sounds[soundIndex];
+ if (mode > k1_soundModePlayIfPrioritized) { /* Add an event in the timeline to play the sound (mode - 1) ticks later */
+ TimelineEvent newEvent;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_currMapIndex, _vm->_gameTime + mode - 1);
+ newEvent._type = k20_TMEventTypePlaySound;
+ newEvent._priority = sound->_priority;
+ newEvent._C._soundIndex = soundIndex;
+ newEvent._B._location._mapX = mapX;
+ newEvent._B._location._mapY = mapY;
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ return;
+ }
+
+ uint8 leftVolume, rightVolume;
+ if (!soundGetVolume(mapX, mapY, &leftVolume, &rightVolume))
+ return;
+
+ if (!mode) { /* Play the sound immediately */
+ play(soundIndex, sound->_period, leftVolume, rightVolume);
+ return;
+ }
+ _pendingSounds.push(PendingSound(leftVolume, rightVolume, soundIndex));
+}
+
+}
diff --git a/engines/dm/sounds.h b/engines/dm/sounds.h
new file mode 100644
index 0000000000..fc9561a0c8
--- /dev/null
+++ b/engines/dm/sounds.h
@@ -0,0 +1,101 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_SOUND_H
+#define DM_SOUND_H
+
+#include "dm/dm.h"
+
+namespace DM {
+
+#define k34_D13_soundCount 34 // @ D13_SOUND_COUNT
+
+class SoundData {
+public:
+ uint32 _byteCount;
+ byte *_firstSample;
+ uint32 _sampleCount;
+ SoundData() : _byteCount(0), _firstSample(nullptr), _sampleCount(0) {}
+}; // @ SOUND_DATA
+
+class Sound {
+public:
+ int16 _graphicIndex;
+ byte _period;
+ byte _priority;
+ byte _loudDistance;
+ byte _softDistance;
+ Sound(int16 index, byte period, byte priority, byte loudDist, byte softDist) :
+ _graphicIndex(index), _period(period), _priority(priority), _loudDistance(loudDist), _softDistance(softDist) {}
+ Sound() : _graphicIndex(0), _period(0), _priority(0), _loudDistance(0), _softDistance(0) {}
+}; // @ Sound
+
+class PendingSound {
+public:
+ uint8 _leftVolume;
+ uint8 _rightVolume;
+ int16 _soundIndex;
+ PendingSound(uint8 leftVolume, uint8 rightVolume, int16 soundIndex) :
+ _leftVolume(leftVolume), _rightVolume(rightVolume), _soundIndex(soundIndex) {}
+};
+
+class SoundMan {
+ DMEngine *_vm;
+
+protected:
+ SoundMan(DMEngine *vm);
+public:
+ virtual ~SoundMan();
+
+ static SoundMan *getSoundMan(DMEngine *vm, const DMADGameDescription *gameVersion);
+
+ SoundData _soundData[k34_D13_soundCount]; // @ K0024_as_SoundData
+ Common::Queue<PendingSound> _pendingSounds;
+
+ virtual void loadSounds(); // @ F0503_SOUND_LoadAll
+ virtual void requestPlay(uint16 P0088_ui_SoundIndex, int16 P0089_i_MapX, int16 P0090_i_MapY, uint16 P0091_ui_Mode); // @ F0064_SOUND_RequestPlay_CPSD
+ virtual void play(uint16 P0921_ui_SoundIndex, uint16 P0085_i_Period, uint8 leftVol, uint8 rightVol); // @ F0060_SOUND_Play
+ void playPendingSound(); // @ F0065_SOUND_PlayPendingSound_CPSD
+ bool soundGetVolume(int16 mapX, int16 mapY, uint8 *leftVolume, uint8 *rightVolume); // @ F0505_SOUND_GetVolume
+
+ Sound _sounds[k34_D13_soundCount];
+ void initConstants();
+};
+
+class SoundMan_Atari: public SoundMan {
+ friend class SoundMan;
+
+ SoundMan_Atari(DMEngine *vm): SoundMan(vm) {};
+public:
+ void loadSounds() override {} // @ F0503_SOUND_LoadAll
+ void requestPlay(uint16 P0088_ui_SoundIndex, int16 P0089_i_MapX, int16 P0090_i_MapY, uint16 P0091_ui_Mode) override {} // @ F0064_SOUND_RequestPlay_CPSD
+ void play(uint16 P0921_ui_SoundIndex, uint16 P0085_i_Period, uint8 leftVol, uint8 rightVol) override {} // @ F0060_SOUND_Play
+};
+
+}
+
+#endif
diff --git a/engines/dm/text.cpp b/engines/dm/text.cpp
new file mode 100644
index 0000000000..b92b43477d
--- /dev/null
+++ b/engines/dm/text.cpp
@@ -0,0 +1,254 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "common/system.h"
+
+#include "dm/text.h"
+
+namespace DM {
+
+TextMan::TextMan(DMEngine *vm) : _vm(vm) {
+ _messageAreaCursorColumn = 0;
+ _messageAreaCursorRow = 0;
+ for (uint16 i = 0; i < 4; ++i)
+ _messageAreaRowExpirationTime[i] = 0;
+
+ _bitmapMessageAreaNewRow = new byte[320 * 7];
+ _isScrolling = false;
+ _startedScrollingAt = -1;
+ _messageAreaCopy = new byte[320 * 7 * 4];
+}
+
+TextMan::~TextMan() {
+ delete[] _bitmapMessageAreaNewRow;
+ delete[] _messageAreaCopy;
+}
+
+void TextMan::printTextToBitmap(byte *destBitmap, uint16 destByteWidth, int16 destX, int16 destY,
+ Color textColor, Color bgColor, const char *text, uint16 destHeight) {
+ if ((destX -= 1) < 0) // fixes missalignment, to be checked
+ destX = 0;
+ if ((destY -= 4) < 0) // fixes missalignment, to be checked
+ destY = 0;
+
+ uint16 destPixelWidth = destByteWidth * 2;
+
+ uint16 textLength = strlen(text);
+ uint16 nextX = destX;
+ uint16 nextY = destY;
+ byte *srcBitmap = _vm->_displayMan->getNativeBitmapOrGraphic(k557_FontGraphicIndice);
+
+ byte *tmp = _vm->_displayMan->_tmpBitmap;
+ for (uint16 i = 0; i < (k5_LetterWidth + 1) * k6_LetterHeight * 128; ++i)
+ tmp[i] = srcBitmap[i] ? textColor : bgColor;
+
+ srcBitmap = tmp;
+
+ for (const char *begin = text, *end = text + textLength; begin != end; ++begin) {
+ if (nextX + k5_LetterWidth + 1 >= destPixelWidth || (*begin == '\n')) {
+ nextX = destX;
+ nextY += k6_LetterHeight + 1;
+ }
+ if (nextY + k6_LetterHeight >= destHeight)
+ break;
+
+ uint16 srcX = (1 + 5) * *begin; // 1 + 5 is not the letter width, arbitrary choice of the unpacking code
+
+ Box box((nextX == destX) ? (nextX + 1) : nextX, nextX + k5_LetterWidth + 1, nextY, nextY + k6_LetterHeight - 1);
+ _vm->_displayMan->blitToBitmap(srcBitmap, destBitmap, box, (nextX == destX) ? (srcX + 1) : srcX, 0, 6 * 128 / 2, destByteWidth, kM1_ColorNoTransparency,
+ k6_LetterHeight, destHeight);
+
+ nextX += k5_LetterWidth + 1;
+ }
+}
+
+void TextMan::printToLogicalScreen(uint16 destX, uint16 destY, Color textColor, Color bgColor, const char *text) {
+ printTextToBitmap(_vm->_displayMan->_bitmapScreen, _vm->_displayMan->_screenWidth / 2, destX, destY, textColor, bgColor, text, _vm->_displayMan->_screenHeight);
+}
+
+void TextMan::printToViewport(int16 posX, int16 posY, Color textColor, const char *text, Color bgColor) {
+ printTextToBitmap(_vm->_displayMan->_bitmapViewport, k112_byteWidthViewport, posX, posY, textColor, bgColor, text, k136_heightViewport);
+}
+
+void TextMan::printWithTrailingSpaces(byte *destBitmap, int16 destByteWidth, int16 destX, int16 destY, Color textColor,
+ Color bgColor, const char *text, int16 requiredTextLength, int16 destHeight) {
+ Common::String str = text;
+ for (int16 i = str.size(); i < requiredTextLength; ++i)
+ str += ' ';
+ printTextToBitmap(destBitmap, destByteWidth, destX, destY, textColor, bgColor, str.c_str(), destHeight);
+}
+
+void TextMan::printLineFeed() {
+ printMessage(k0_ColorBlack, "\n");
+}
+
+void TextMan::printMessage(Color color, const char *string, bool printWithScroll) {
+ uint16 characterIndex;
+ Common::String wrkString;
+
+ while (*string) {
+ if (*string == '\n') { /* New line */
+ string++;
+ if ((_messageAreaCursorColumn != 0) || (_messageAreaCursorRow != 0)) {
+ _messageAreaCursorColumn = 0;
+ createNewRow();
+ }
+ } else if (*string == ' ') {
+ string++;
+ if (_messageAreaCursorColumn != 53) {
+ printString(color, " "); // I'm not sure if this is like the original
+ }
+ } else {
+ characterIndex = 0;
+ do {
+ wrkString += *string++;
+ characterIndex++;
+ } while (*string && (*string != ' ') && (*string != '\n')); /* End of string, space or New line */
+ wrkString += '\0';
+ if (_messageAreaCursorColumn + characterIndex > 53) {
+ _messageAreaCursorColumn = 2;
+ createNewRow();
+ }
+ printString(color, wrkString.c_str());
+ }
+ }
+}
+
+void TextMan::createNewRow() {
+ if (_messageAreaCursorRow == 3) {
+ isTextScrolling(&_textScroller, true);
+ memset(_bitmapMessageAreaNewRow, k0_ColorBlack, 320 * 7);
+ _isScrolling = true;
+ setScrollerCommand(&_textScroller, 1);
+
+ for (uint16 rowIndex = 0; rowIndex < 3; rowIndex++)
+ _messageAreaRowExpirationTime[rowIndex] = _messageAreaRowExpirationTime[rowIndex + 1];
+
+ _messageAreaRowExpirationTime[3] = -1;
+ } else
+ _messageAreaCursorRow++;
+}
+
+void TextMan::printString(Color color, const char* string) {
+ int16 stringLength = strlen(string);
+ if (isTextScrolling(&_textScroller, false))
+ printToLogicalScreen(_messageAreaCursorColumn * 6, (_messageAreaCursorRow * 7 - 1) + 177, color, k0_ColorBlack, string);
+ else {
+ printTextToBitmap(_bitmapMessageAreaNewRow, k160_byteWidthScreen, _messageAreaCursorColumn * 6, 0, color, k0_ColorBlack, string, 7);
+ _isScrolling = true;
+ if (isTextScrolling(&_textScroller, false))
+ setScrollerCommand(&_textScroller, 1);
+ }
+ _messageAreaCursorColumn += stringLength;
+ _messageAreaRowExpirationTime[_messageAreaCursorRow] = _vm->_gameTime + 200;
+
+}
+
+void TextMan::initialize() {
+ moveCursor(0, 3);
+ for (uint16 i = 0; i < 4; ++i)
+ _messageAreaRowExpirationTime[i] = -1;
+}
+
+void TextMan::moveCursor(int16 column, int16 row) {
+ if (column < 0)
+ column = 0;
+ else if (column >= 53)
+ column = 52;
+
+ _messageAreaCursorColumn = column;
+ if (row < 0)
+ row = 0;
+ else if (row >= 4)
+ row = 3;
+
+ _messageAreaCursorRow = row;
+}
+
+void TextMan::clearExpiredRows() {
+ _vm->_displayMan->_useByteBoxCoordinates = false;
+ Box displayBox;
+ displayBox._x1 = 0;
+ displayBox._x2 = 319;
+ for (uint16 rowIndex = 0; rowIndex < 4; rowIndex++) {
+ int32 expirationTime = _messageAreaRowExpirationTime[rowIndex];
+ if ((expirationTime == -1) || (expirationTime > _vm->_gameTime) || _isScrolling)
+ continue;
+ displayBox._y2 = (displayBox._y1 = 172 + (rowIndex * 7)) + 6;
+ isTextScrolling(&_textScroller, true);
+ _vm->_displayMan->fillBoxBitmap(_vm->_displayMan->_bitmapScreen, displayBox, k0_ColorBlack, k160_byteWidthScreen, k200_heightScreen);
+ _messageAreaRowExpirationTime[rowIndex] = -1;
+ }
+}
+
+void TextMan::printEndGameString(int16 x, int16 y, Color textColor, char* text) {
+ char modifiedString[50];
+
+ char *wrkStringPtr = modifiedString;
+ *wrkStringPtr = *text++;
+ while (*wrkStringPtr) {
+ if ((*wrkStringPtr >= 'A') && (*wrkStringPtr <= 'Z'))
+ *wrkStringPtr -= 64; /* Use the same font as the one used for scrolls */
+
+ wrkStringPtr++;
+ *wrkStringPtr = *text++;
+ }
+ printToLogicalScreen(x, y, textColor, k12_ColorDarkestGray, modifiedString);
+}
+
+void TextMan::clearAllRows() {
+ isTextScrolling(&_textScroller, true);
+
+ Box tmpBox(0, 319, 169, 199);
+ _vm->_displayMan->fillScreenBox(tmpBox, k0_ColorBlack);
+
+ _messageAreaCursorRow = 3;
+ _messageAreaCursorColumn = 0;
+ for (int16 rowIndex = 0; rowIndex < 4; rowIndex++)
+ _messageAreaRowExpirationTime[rowIndex] = -1;
+}
+
+void TextMan::updateMessageArea() {
+ if (_isScrolling) {
+ if (_startedScrollingAt == -1) {
+ _startedScrollingAt = g_system->getMillis();
+ memcpy(_messageAreaCopy, _vm->_displayMan->_bitmapScreen + (200 - 7 * 4) * 320, 320 * 7 * 4);
+ }
+
+ int linesToCopy = (g_system->getMillis() - _startedScrollingAt) / 50;
+ if (linesToCopy >= 7) {
+ linesToCopy = 7;
+ _startedScrollingAt = -1;
+ _isScrolling = false;
+ }
+ memcpy(_vm->_displayMan->_bitmapScreen + (200 - 7 * 4) * 320, _messageAreaCopy + linesToCopy * 320,
+ 320 * (7 * 4 - linesToCopy));
+ memcpy(_vm->_displayMan->_bitmapScreen + (200 - linesToCopy) * 320, _bitmapMessageAreaNewRow, 320 * linesToCopy);
+ }
+}
+
+}
diff --git a/engines/dm/text.h b/engines/dm/text.h
new file mode 100644
index 0000000000..6a934247d8
--- /dev/null
+++ b/engines/dm/text.h
@@ -0,0 +1,82 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_TEXT_H
+#define DM_TEXT_H
+
+#include "dm/dm.h"
+#include "dm/gfx.h"
+
+namespace DM {
+
+struct TextScroller {
+ // Placeholder, empty for now
+}; // @ Text_Scroller
+
+#define k5_LetterWidth 5
+#define k6_LetterHeight 6
+
+class TextMan {
+ DMEngine *_vm;
+ int16 _messageAreaCursorColumn; // @ G0359_i_MessageAreaCursorColumn
+ int16 _messageAreaCursorRow; // @ G0358_i_MessageAreaCursorRow
+ int32 _messageAreaRowExpirationTime[4]; // @ G0360_al_MessageAreaRowExpirationTime
+ byte *_bitmapMessageAreaNewRow; // @ G0356_puc_Bitmap_MessageAreaNewRow
+
+ // for scrolling 'em messages
+ bool _isScrolling;
+ int64 _startedScrollingAt;
+ byte *_messageAreaCopy;
+public:
+ TextScroller _textScroller;
+
+ explicit TextMan(DMEngine *vm);
+ ~TextMan();
+
+ void printTextToBitmap(byte *destBitmap, uint16 destByteWidth, int16 destX, int16 destY,
+ Color textColor, Color bgColor, const char *text, uint16 destHeight); // @ F0040_TEXT_Print
+ void printToLogicalScreen(uint16 destX, uint16 destY, Color textColor, Color bgColor, const char *text); // @ F0053_TEXT_PrintToLogicalScreen
+ void printToViewport(int16 posX, int16 posY, Color textColor, const char *text, Color bgColor = k12_ColorDarkestGray); // @ F0052_TEXT_PrintToViewport
+ void printWithTrailingSpaces(byte *destBitmap, int16 destByteWidth, int16 destX, int16 destY, Color textColor, Color bgColor,
+ const char *text, int16 strLenght, int16 destHeight); // @ F0041_TEXT_PrintWithTrailingSpaces
+ void printLineFeed(); // @ F0051_TEXT_MESSAGEAREA_PrintLineFeed
+ void printMessage(Color color, const char *string, bool printWithScroll = true); // @ F0047_TEXT_MESSAGEAREA_PrintMessage
+ void createNewRow(); // @ F0045_TEXT_MESSAGEAREA_CreateNewRow
+ void printString(Color color, const char* string);// @ F0046_TEXT_MESSAGEAREA_PrintString
+ void initialize(); // @ F0054_TEXT_Initialize
+ void moveCursor(int16 column, int16 row); // @ F0042_TEXT_MESSAGEAREA_MoveCursor
+ void clearExpiredRows(); // @ F0044_TEXT_MESSAGEAREA_ClearExpiredRows
+ void printEndGameString(int16 x, int16 y, Color textColor, char *text); // @ F0443_STARTEND_EndgamePrintString
+ bool isTextScrolling(TextScroller *scroller, bool waitEndOfScrolling) { return false; } // @ F0561_SCROLLER_IsTextScrolling
+ void setScrollerCommand(TextScroller *scroller, int16 command) { } // @ F0560_SCROLLER_SetCommand
+ void clearAllRows(); // @ F0043_TEXT_MESSAGEAREA_ClearAllRows
+
+ void updateMessageArea();
+};
+
+}
+#endif
diff --git a/engines/dm/timeline.cpp b/engines/dm/timeline.cpp
new file mode 100644
index 0000000000..8ec70f7f53
--- /dev/null
+++ b/engines/dm/timeline.cpp
@@ -0,0 +1,988 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#include "dm/timeline.h"
+#include "dm/dungeonman.h"
+#include "dm/champion.h"
+#include "dm/inventory.h"
+#include "dm/group.h"
+#include "dm/projexpl.h"
+#include "dm/movesens.h"
+#include "dm/text.h"
+#include "dm/eventman.h"
+#include "dm/objectman.h"
+#include "dm/sounds.h"
+
+
+namespace DM {
+
+void Timeline::initConstants() {
+ static signed char actionDefense[44] = { // @ G0495_ac_Graphic560_ActionDefense
+ 0, /* N */
+ 36, /* BLOCK */
+ 0, /* CHOP */
+ 0, /* X */
+ -4, /* BLOW HORN */
+ -10, /* FLIP */
+ -10, /* PUNCH */
+ -5, /* KICK */
+ 4, /* WAR CRY */
+ -20, /* STAB */
+ -15, /* CLIMB DOWN */
+ -10, /* FREEZE LIFE */
+ 16, /* HIT */
+ 5, /* SWING */
+ -15, /* STAB */
+ -17, /* THRUST */
+ -5, /* JAB */
+ 29, /* PARRY */
+ 10, /* HACK */
+ -10, /* BERZERK */
+ -7, /* FIREBALL */
+ -7, /* DISPELL */
+ -7, /* CONFUSE */
+ -7, /* LIGHTNING */
+ -7, /* DISRUPT */
+ -5, /* MELEE */
+ -15, /* X */
+ -9, /* INVOKE */
+ 4, /* SLASH */
+ 0, /* CLEAVE */
+ 0, /* BASH */
+ 5, /* STUN */
+ -15, /* SHOOT */
+ -7, /* SPELLSHIELD */
+ -7, /* FIRESHIELD */
+ 8, /* FLUXCAGE */
+ -20, /* HEAL */
+ -5, /* CALM */
+ 0, /* LIGHT */
+ -15, /* WINDOW */
+ -7, /* SPIT */
+ -4, /* BRANDISH */
+ 0, /* THROW */
+ 8 /* FUSE */
+ };
+
+ for (int i = 0; i < 44; i++)
+ _actionDefense[i] = actionDefense[i];
+}
+
+Timeline::Timeline(DMEngine *vm) : _vm(vm) {
+ _eventMaxCount = 0;
+ _events = nullptr;
+ _eventCount = 0;
+ _timeline = nullptr;
+ _firstUnusedEventIndex = 0;
+
+ initConstants();
+}
+
+Timeline::~Timeline() {
+ delete[] _events;
+ delete[] _timeline;
+}
+
+void Timeline::initTimeline() {
+ _events = new TimelineEvent[_eventMaxCount];
+ _timeline = new uint16[_eventMaxCount];
+ if (_vm->_newGameFl) {
+ for (int16 i = 0; i < _eventMaxCount; ++i)
+ _events[i]._type = k0_TMEventTypeNone;
+ _eventCount = 0;
+ _firstUnusedEventIndex = 0;
+ }
+}
+
+void Timeline::deleteEvent(uint16 eventIndex) {
+ _events[eventIndex]._type = k0_TMEventTypeNone;
+ if (eventIndex < _firstUnusedEventIndex)
+ _firstUnusedEventIndex = eventIndex;
+
+ _eventCount--;
+
+ uint16 eventCount = _eventCount;
+ if (eventCount == 0)
+ return;
+
+ uint16 timelineIndex = getIndex(eventIndex);
+ if (timelineIndex == eventCount)
+ return;
+
+ _timeline[timelineIndex] = _timeline[eventCount];
+ fixChronology(timelineIndex);
+}
+
+void Timeline::fixChronology(uint16 timelineIndex) {
+ uint16 eventCount = _eventCount;
+ if (eventCount == 1)
+ return;
+
+ uint16 eventIndex = _timeline[timelineIndex];
+ TimelineEvent *timelineEvent = &_events[eventIndex];
+ bool chronologyFixed = false;
+ while (timelineIndex > 0) { /* Check if the event should be moved earlier in the timeline */
+ uint16 altTimelineIndex = (timelineIndex - 1) >> 1;
+ if (isEventABeforeB(timelineEvent, &_events[_timeline[altTimelineIndex]])) {
+ _timeline[timelineIndex] = _timeline[altTimelineIndex];
+ timelineIndex = altTimelineIndex;
+ chronologyFixed = true;
+ } else
+ break;
+ }
+ if (!chronologyFixed) {
+ eventCount = ((eventCount - 1) - 1) >> 1;
+ while (timelineIndex <= eventCount) { /* Check if the event should be moved later in the timeline */
+ uint16 altTimelineIndex = (timelineIndex << 1) + 1;
+ if (((altTimelineIndex + 1) < _eventCount) && (isEventABeforeB(&_events[_timeline[altTimelineIndex + 1]], &_events[_timeline[altTimelineIndex]])))
+ altTimelineIndex++;
+
+ if (isEventABeforeB(&_events[_timeline[altTimelineIndex]], timelineEvent)) {
+ _timeline[timelineIndex] = _timeline[altTimelineIndex];
+ timelineIndex = altTimelineIndex;
+ } else
+ break;
+ }
+ }
+
+ _timeline[timelineIndex] = eventIndex;
+}
+
+bool Timeline::isEventABeforeB(TimelineEvent *eventA, TimelineEvent *eventB) {
+ bool simultaneousFl = (filterTime(eventA->_mapTime) == filterTime(eventB->_mapTime));
+
+ return (filterTime(eventA->_mapTime) < filterTime(eventB->_mapTime)) ||
+ (simultaneousFl && (eventA->getTypePriority() > eventB->getTypePriority())) ||
+ (simultaneousFl && (eventA->getTypePriority() == eventB->getTypePriority()) && (eventA <= eventB));
+}
+
+uint16 Timeline::getIndex(uint16 eventIndex) {
+ uint16 timelineIndex;
+ uint16 *timelineEntry = _timeline;
+
+ for (timelineIndex = 0; timelineIndex < _eventMaxCount; timelineIndex++) {
+ if (*timelineEntry++ == eventIndex)
+ break;
+ }
+
+ if (timelineIndex >= _eventMaxCount) /* BUG0_00 Useless code. The function is always called with event indices that are in the timeline */
+ timelineIndex = 0; /* BUG0_01 Coding error without consequence. Wrong return value. If the specified event index is not found in the timeline the function returns 0 which is the same value that is returned if the event index is found in the first timeline entry. No consequence because this code is never executed */
+
+ return timelineIndex;
+}
+
+uint16 Timeline::addEventGetEventIndex(TimelineEvent *event) {
+ if (_eventCount == _eventMaxCount)
+ error("Too many events");
+
+ if ((event->_type >= k5_TMEventTypeCorridor) && (event->_type <= k10_TMEventTypeDoor)) {
+ TimelineEvent *curEvent = _events;
+ for (uint16 eventIndex = 0; eventIndex < _eventMaxCount; eventIndex++, curEvent++) {
+ if ((curEvent->_type >= k5_TMEventTypeCorridor) && (curEvent->_type <= k10_TMEventTypeDoor)) {
+ if ((event->_mapTime == curEvent->_mapTime) && (event->getMapXY() == curEvent->getMapXY()) && ((curEvent->_type != k6_TMEventTypeWall) || (curEvent->_C.A._cell == event->_C.A._cell))) {
+ curEvent->_C.A._effect = event->_C.A._effect;
+ return eventIndex;
+ }
+ continue;
+ } else if ((curEvent->_type == k1_TMEventTypeDoorAnimation) && (event->_mapTime == curEvent->_mapTime) && (event->getMapXY() == curEvent->getMapXY())) {
+ if (event->_C.A._effect == k2_SensorEffToggle)
+ event->_C.A._effect = 1 - curEvent->_C.A._effect;
+
+ deleteEvent(eventIndex);
+ break;
+ }
+ }
+ } else if (event->_type == k1_TMEventTypeDoorAnimation) {
+ TimelineEvent *curEvent = _events;
+ for (uint16 eventIndex = 0; eventIndex < _eventMaxCount; eventIndex++, curEvent++) {
+ if ((event->_mapTime == curEvent->_mapTime) && (event->getMapXY() == curEvent->getMapXY())) {
+ if (curEvent->_type == k10_TMEventTypeDoor) {
+ if (curEvent->_C.A._effect == k2_SensorEffToggle)
+ curEvent->_C.A._effect = 1 - event->_C.A._effect;
+
+ return eventIndex;
+ }
+ if (curEvent->_type == k1_TMEventTypeDoorAnimation) {
+ curEvent->_C.A._effect = event->_C.A._effect;
+ return eventIndex;
+ }
+ }
+ }
+ } else if (event->_type == k2_TMEventTypeDoorDestruction) {
+ TimelineEvent *curEvent = _events;
+ for (uint16 eventIndex = 0; eventIndex < _eventMaxCount; eventIndex++, curEvent++) {
+ if ((event->getMapXY() == curEvent->getMapXY()) && (getMap(event->_mapTime) == getMap(curEvent->_mapTime))) {
+ if ((curEvent->_type == k1_TMEventTypeDoorAnimation) || (curEvent->_type == k10_TMEventTypeDoor))
+ deleteEvent(eventIndex);
+ }
+ }
+ }
+
+ uint16 newEventIndex = _firstUnusedEventIndex;
+ _events[newEventIndex] = *event; /* Copy the event data (Megamax C can assign structures) */
+ do {
+ if (_firstUnusedEventIndex == _eventMaxCount)
+ break;
+ _firstUnusedEventIndex++;
+ } while ((_events[_firstUnusedEventIndex])._type != k0_TMEventTypeNone);
+ _timeline[_eventCount] = newEventIndex;
+ fixChronology(_eventCount++);
+ return newEventIndex;
+}
+
+void Timeline::processTimeline() {
+ while (isFirstEventExpiered()) {
+ TimelineEvent newEvent;
+ TimelineEvent *curEvent = &newEvent;
+ extractFirstEvent(curEvent);
+ _vm->_dungeonMan->setCurrentMap(getMap(newEvent._mapTime));
+ uint16 curEventType = newEvent._type;
+ if ((curEventType > (k29_TMEventTypeGroupReactionDangerOnSquare - 1)) && (curEventType < (k41_TMEventTypeUpdateBehaviour_3 + 1)))
+ _vm->_groupMan->processEvents29to41(newEvent._B._location._mapX, newEvent._B._location._mapY, curEventType, newEvent._C._ticks);
+ else {
+ switch (curEventType) {
+ case k48_TMEventTypeMoveProjectileIgnoreImpacts:
+ case k49_TMEventTypeMoveProjectile:
+ _vm->_projexpl->processEvents48To49(curEvent);
+ break;
+ case k1_TMEventTypeDoorAnimation:
+ processEventDoorAnimation(curEvent);
+ break;
+ case k25_TMEventTypeExplosion:
+ _vm->_projexpl->processEvent25(curEvent);
+ break;
+ case k7_TMEventTypeFakeWall:
+ processEventSquareFakewall(curEvent);
+ break;
+ case k2_TMEventTypeDoorDestruction:
+ processEventDoorDestruction(curEvent);
+ break;
+ case k10_TMEventTypeDoor:
+ processEventSquareDoor(curEvent);
+ break;
+ case k9_TMEventTypePit:
+ processEventSquarePit(curEvent);
+ break;
+ case k8_TMEventTypeTeleporter:
+ processEventSquareTeleporter(curEvent);
+ break;
+ case k6_TMEventTypeWall:
+ processEventSquareWall(curEvent);
+ break;
+ case k5_TMEventTypeCorridor:
+ processEventSquareCorridor(curEvent);
+ break;
+ case k60_TMEventTypeMoveGroupSilent:
+ case k61_TMEventTypeMoveGroupAudible:
+ processEventsMoveGroup(curEvent);
+ break;
+ case k65_TMEventTypeEnableGroupGenerator:
+ procesEventEnableGroupGenerator(curEvent);
+ break;
+ case k20_TMEventTypePlaySound:
+ _vm->_sound->requestPlay(newEvent._C._soundIndex, newEvent._B._location._mapX, newEvent._B._location._mapY, k1_soundModePlayIfPrioritized);
+ break;
+ case k24_TMEventTypeRemoveFluxcage:
+ if (!_vm->_gameWon) {
+ _vm->_dungeonMan->unlinkThingFromList(Thing(newEvent._C._slot), Thing(0), newEvent._B._location._mapX, newEvent._B._location._mapY);
+ curEvent = (TimelineEvent *)_vm->_dungeonMan->getThingData(Thing(newEvent._C._slot));
+ ((Explosion *)curEvent)->setNextThing(Thing::_none);
+ }
+ break;
+ case k11_TMEventTypeEnableChampionAction:
+ processEventEnableChampionAction(newEvent._priority);
+ if (newEvent._B._slotOrdinal)
+ processEventMoveWeaponFromQuiverToSlot(newEvent._priority, _vm->ordinalToIndex(newEvent._B._slotOrdinal));
+
+ _vm->_championMan->drawChampionState((ChampionIndex)newEvent._priority);
+ break;
+ case k12_TMEventTypeHideDamageReceived:
+ processEventHideDamageReceived(newEvent._priority);
+ break;
+ case k70_TMEventTypeLight:
+ _vm->_dungeonMan->setCurrentMap(_vm->_dungeonMan->_partyMapIndex);
+ processEventLight(curEvent);
+ _vm->_inventoryMan->setDungeonViewPalette();
+ break;
+ case k71_TMEventTypeInvisibility:
+ _vm->_championMan->_party._event71Count_Invisibility--;
+ break;
+ case k72_TMEventTypeChampionShield:
+ _vm->_championMan->_champions[newEvent._priority]._shieldDefense -= newEvent._B._defense;
+ setFlag(_vm->_championMan->_champions[newEvent._priority]._attributes, kDMAttributeStatusBox);
+ _vm->_championMan->drawChampionState((ChampionIndex)newEvent._priority);
+ break;
+ case k73_TMEventTypeThievesEye:
+ _vm->_championMan->_party._event73Count_ThievesEye--;
+ break;
+ case k74_TMEventTypePartyShield:
+ _vm->_championMan->_party._shieldDefense -= newEvent._B._defense;
+ refreshAllChampionStatusBoxes();
+ break;
+ case k77_TMEventTypeSpellShield:
+ _vm->_championMan->_party._spellShieldDefense -= newEvent._B._defense;
+ refreshAllChampionStatusBoxes();
+ break;
+ case k78_TMEventTypeFireShield:
+ _vm->_championMan->_party._fireShieldDefense -= newEvent._B._defense;
+ refreshAllChampionStatusBoxes();
+ break;
+ case k75_TMEventTypePoisonChampion: {
+ uint16 championIndex = newEvent._priority;
+ _vm->_championMan->_champions[championIndex = newEvent._priority]._poisonEventCount--;
+ _vm->_championMan->championPoison(championIndex, newEvent._B._attack);
+ }
+ break;
+ case k13_TMEventTypeViAltarRebirth:
+ processEventViAltarRebirth(curEvent);
+ break;
+ case k79_TMEventTypeFootprints:
+ _vm->_championMan->_party._event79Count_Footprints--;
+ }
+ }
+ _vm->_dungeonMan->setCurrentMap(_vm->_dungeonMan->_partyMapIndex);
+ }
+}
+
+bool Timeline::isFirstEventExpiered() {
+ return (_eventCount && (filterTime(_events[_timeline[0]]._mapTime) <= _vm->_gameTime));
+}
+
+void Timeline::extractFirstEvent(TimelineEvent *event) {
+ uint16 eventIndex = _timeline[0];
+
+ *event = _events[eventIndex];
+ deleteEvent(eventIndex);
+}
+
+void Timeline::processEventDoorAnimation(TimelineEvent *event) {
+ uint16 mapX = event->_B._location._mapX;
+ uint16 mapY = event->_B._location._mapY;
+ Square *curSquare = (Square *)&_vm->_dungeonMan->_currMapData[mapX][mapY];
+ int16 doorState = Square(*curSquare).getDoorState();
+ if (doorState == k5_doorState_DESTROYED)
+ return;
+
+ event->_mapTime++;
+ int16 sensorEffect = event->_C.A._effect;
+ if (sensorEffect == k1_SensorEffClear) {
+ Door *curDoor = (Door *)_vm->_dungeonMan->getSquareFirstThingData(mapX, mapY);
+ bool verticalDoorFl = curDoor->opensVertically();
+ if ((_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (mapX == _vm->_dungeonMan->_partyMapX)
+ && (mapY == _vm->_dungeonMan->_partyMapY) && (doorState != k0_doorState_OPEN)) {
+ if (_vm->_championMan->_partyChampionCount > 0) {
+ curSquare->setDoorState(k0_doorState_OPEN);
+
+ // Strangerke
+ // Original bug fixed - A closing horizontal door wounds champions to the head instead of to the hands. Missing parenthesis in the condition cause all doors to wound the head in addition to the torso
+ // See BUG0_78
+ int16 wounds = kDMWoundTorso | (verticalDoorFl ? kDMWoundHead : kDMWoundReadHand | kDMWoundActionHand);
+ if (_vm->_championMan->getDamagedChampionCount(5, wounds, kDMAttackTypeSelf))
+ _vm->_sound->requestPlay(k18_soundPARTY_DAMAGED, mapX, mapY, k1_soundModePlayIfPrioritized);
+ }
+ event->_mapTime++;
+ addEventGetEventIndex(event);
+ return;
+ }
+ Thing groupThing = _vm->_groupMan->groupGetThing(mapX, mapY);
+ uint16 creatureAttributes = _vm->_dungeonMan->getCreatureAttributes(groupThing);
+ if ((groupThing != Thing::_endOfList) && !getFlag(creatureAttributes, k0x0040_MaskCreatureInfo_nonMaterial)) {
+ if (doorState >= (verticalDoorFl ? CreatureInfo::getHeight(creatureAttributes) : 1)) { /* Creature height or 1 */
+ if (_vm->_groupMan->getDamageAllCreaturesOutcome((Group *)_vm->_dungeonMan->getThingData(groupThing), mapX, mapY, 5, true) != k2_outcomeKilledAllCreaturesInGroup)
+ _vm->_groupMan->processEvents29to41(mapX, mapY, kM3_TMEventTypeCreateReactionEvent29DangerOnSquare, 0);
+
+ doorState = (doorState == k0_doorState_OPEN) ? k0_doorState_OPEN : (doorState - 1);
+ curSquare->setDoorState(doorState);
+ _vm->_sound->requestPlay(k04_soundWOODEN_THUD_ATTACK_TROLIN_ANTMAN_STONE_GOLEM, mapX, mapY, k1_soundModePlayIfPrioritized);
+ event->_mapTime++;
+ addEventGetEventIndex(event);
+ return;
+ }
+ }
+ }
+ if ((sensorEffect == k0_SensorEffSet) && (doorState == k0_doorState_OPEN))
+ return;
+
+ if ((sensorEffect == k1_SensorEffClear) && (doorState == k4_doorState_CLOSED))
+ return;
+
+ doorState += (sensorEffect == k0_SensorEffSet) ? -1 : 1;
+ curSquare->setDoorState(doorState);
+ _vm->_sound->requestPlay(k02_soundDOOR_RATTLE, mapX, mapY, k1_soundModePlayIfPrioritized);
+
+ if (sensorEffect == k0_SensorEffSet) {
+ if (doorState == k0_doorState_OPEN)
+ return;
+ } else if (doorState == k4_doorState_CLOSED)
+ return;
+
+ addEventGetEventIndex(event);
+}
+
+void Timeline::processEventSquareFakewall(TimelineEvent *event) {
+ uint16 mapX = event->_B._location._mapX;
+ uint16 mapY = event->_B._location._mapY;
+ byte *curSquare = &_vm->_dungeonMan->_currMapData[mapX][mapY];
+ int16 effect = event->_C.A._effect;
+ if (effect == k2_SensorEffToggle)
+ effect = getFlag(*curSquare, k0x0004_FakeWallOpen) ? k1_SensorEffClear : k0_SensorEffSet;
+
+ if (effect == k1_SensorEffClear) {
+ if ((_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (mapX == _vm->_dungeonMan->_partyMapX) && (mapY == _vm->_dungeonMan->_partyMapY)) {
+ event->_mapTime++;
+ addEventGetEventIndex(event);
+ } else {
+ Thing groupThing = _vm->_groupMan->groupGetThing(mapX, mapY);
+ if ((groupThing != Thing::_endOfList) && !getFlag(_vm->_dungeonMan->getCreatureAttributes(groupThing), k0x0040_MaskCreatureInfo_nonMaterial)) {
+ event->_mapTime++;
+ addEventGetEventIndex(event);
+ } else
+ clearFlag(*curSquare, k0x0004_FakeWallOpen);
+ }
+ } else
+ setFlag(*curSquare, k0x0004_FakeWallOpen);
+}
+
+void Timeline::processEventDoorDestruction(TimelineEvent *event) {
+ Square *square = (Square *)&_vm->_dungeonMan->_currMapData[event->_B._location._mapX][event->_B._location._mapY];
+ square->setDoorState(k5_doorState_DESTROYED);
+}
+
+void Timeline::processEventSquareDoor(TimelineEvent *event) {
+ int16 doorState = Square(_vm->_dungeonMan->_currMapData[event->_B._location._mapX][event->_B._location._mapY]).getDoorState();
+ if (doorState == k5_doorState_DESTROYED)
+ return;
+
+ if (event->_C.A._effect == k2_SensorEffToggle)
+ event->_C.A._effect = (doorState == k0_doorState_OPEN) ? k1_SensorEffClear : k0_SensorEffSet;
+ else if (event->_C.A._effect == k0_SensorEffSet) {
+ if ((doorState == k0_doorState_OPEN) || (doorState == k4_doorState_CLOSED))
+ return;
+ }
+ event->_type = k1_TMEventTypeDoorAnimation;
+ addEventGetEventIndex(event);
+}
+
+void Timeline::processEventSquarePit(TimelineEvent *event) {
+ uint16 mapX = event->_B._location._mapX;
+ uint16 mapY = event->_B._location._mapY;
+
+ byte *square = &_vm->_dungeonMan->_currMapData[mapX][mapY];
+ if (event->_C.A._effect == k2_SensorEffToggle)
+ event->_C.A._effect = getFlag(*square, k0x0008_PitOpen) ? k1_SensorEffClear : k0_SensorEffSet;
+
+ if (event->_C.A._effect == k0_SensorEffSet) {
+ setFlag(*square, k0x0008_PitOpen);
+ moveTeleporterOrPitSquareThings(mapX, mapY);
+ } else
+ clearFlag(*square, k0x0008_PitOpen);
+}
+
+void Timeline::moveTeleporterOrPitSquareThings(uint16 mapX, uint16 mapY) {
+ if ((_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex)
+ && (mapX == _vm->_dungeonMan->_partyMapX) && (mapY == _vm->_dungeonMan->_partyMapY)) {
+ _vm->_moveSens->getMoveResult(Thing::_party, mapX, mapY, mapX, mapY);
+ _vm->_championMan->drawChangedObjectIcons();
+ }
+
+ Thing curThing = _vm->_groupMan->groupGetThing(mapX, mapY);
+ if (curThing != Thing::_endOfList)
+ _vm->_moveSens->getMoveResult(curThing, mapX, mapY, mapX, mapY);
+
+ curThing = _vm->_dungeonMan->getSquareFirstObject(mapX, mapY);
+ Thing nextThing = curThing;
+ int16 thingsToMoveCount = 0;
+ while (curThing != Thing::_endOfList) {
+ if (curThing.getType() > k4_GroupThingType)
+ thingsToMoveCount++;
+
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+ curThing = nextThing;
+ while ((curThing != Thing::_endOfList) && thingsToMoveCount) {
+ thingsToMoveCount--;
+ nextThing = _vm->_dungeonMan->getNextThing(curThing);
+ uint16 curThingType = curThing.getType();
+ if (curThingType > k4_GroupThingType)
+ _vm->_moveSens->getMoveResult(curThing, mapX, mapY, mapX, mapY);
+
+ if (curThingType == k14_ProjectileThingType) {
+ Projectile *projectile = (Projectile *)_vm->_dungeonMan->getThingData(curThing);
+ TimelineEvent *newEvent;
+ newEvent = &_events[projectile->_eventIndex];
+ newEvent->_C._projectile.setMapX(_vm->_moveSens->_moveResultMapX);
+ newEvent->_C._projectile.setMapY(_vm->_moveSens->_moveResultMapY);
+ newEvent->_C._projectile.setDir((Direction)_vm->_moveSens->_moveResultDir);
+ newEvent->_B._slot = thingWithNewCell(curThing, _vm->_moveSens->_moveResultCell).toUint16();
+ M31_setMap(newEvent->_mapTime, _vm->_moveSens->_moveResultMapIndex);
+ } else if (curThingType == k15_ExplosionThingType) {
+ TimelineEvent *newEvent = _events;
+ for (uint16 i = 0; i < _eventMaxCount; newEvent++, i++) {
+ if ((newEvent->_type == k25_TMEventTypeExplosion) && (newEvent->_C._slot == curThing.toUint16())) { /* BUG0_23 A Fluxcage explosion remains on a square forever. If you open a pit or teleporter on a square where there is a Fluxcage explosion, the Fluxcage explosion is moved but the associated event is not updated (because Fluxcage explosions do not use k25_TMEventTypeExplosion but rather k24_TMEventTypeRemoveFluxcage) causing the Fluxcage explosion to remain in the dungeon forever on its destination square. When the k24_TMEventTypeRemoveFluxcage expires the explosion thing is not removed, but it is marked as unused. Consequently, any objects placed on the Fluxcage square after it was moved but before it expires become orphans upon expiration. After expiration, any object placed on the fluxcage square is cloned when picked up */
+ newEvent->_B._location._mapX = _vm->_moveSens->_moveResultMapX;
+ newEvent->_B._location._mapY = _vm->_moveSens->_moveResultMapY;
+ newEvent->_C._slot = thingWithNewCell(curThing, _vm->_moveSens->_moveResultCell).toUint16();
+ M31_setMap(newEvent->_mapTime, _vm->_moveSens->_moveResultMapIndex);
+ }
+ }
+ }
+ curThing = nextThing;
+ }
+}
+
+void Timeline::processEventSquareTeleporter(TimelineEvent *event) {
+ uint16 mapX = event->_B._location._mapX;
+ uint16 mapY = event->_B._location._mapY;
+
+ byte *curSquare = &_vm->_dungeonMan->_currMapData[mapX][mapY];
+ if (event->_C.A._effect == k2_SensorEffToggle)
+ event->_C.A._effect = getFlag(*curSquare, k0x0008_TeleporterOpen) ? k1_SensorEffClear : k0_SensorEffSet;
+
+ if (event->_C.A._effect == k0_SensorEffSet) {
+ setFlag(*curSquare, k0x0008_TeleporterOpen);
+ moveTeleporterOrPitSquareThings(mapX, mapY);
+ } else
+ clearFlag(*curSquare, k0x0008_TeleporterOpen);
+}
+
+void Timeline::processEventSquareWall(TimelineEvent *event) {
+ int16 mapX = event->_B._location._mapX;
+ int16 mapY = event->_B._location._mapY;
+ Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
+ uint16 curCell = event->_C.A._cell;
+ while (curThing != Thing::_endOfList) {
+ int16 curThingType = curThing.getType();
+ if ((curThingType == k2_TextstringType) && (curThing.getCell() == event->_C.A._cell)) {
+ TextString *textString = (TextString *)_vm->_dungeonMan->getThingData(curThing);
+ if (event->_C.A._effect == k2_SensorEffToggle)
+ textString->setVisible(!textString->isVisible());
+ else
+ textString->setVisible(event->_C.A._effect == k0_SensorEffSet);
+ } else if (curThingType == k3_SensorThingType) {
+ Sensor *curThingSensor = (Sensor *)_vm->_dungeonMan->getThingData(curThing);
+ uint16 curSensorType = curThingSensor->getType();
+ uint16 curSensorData = curThingSensor->getData();
+ if (curSensorType == k6_SensorWallCountdown) {
+ if (curSensorData > 0) {
+ if (event->_C.A._effect == k0_SensorEffSet) {
+ if (curSensorData < 511)
+ curSensorData++;
+ } else
+ curSensorData--;
+
+ curThingSensor->setData(curSensorData);
+ if (curThingSensor->getAttrEffectA() == k3_SensorEffHold) {
+ int16 triggerSetEffect = ((curSensorData == 0) != curThingSensor->getAttrRevertEffectA());
+ _vm->_moveSens->triggerEffect(curThingSensor, triggerSetEffect ? k0_SensorEffSet : k1_SensorEffClear, mapX, mapY, curCell);
+ } else if (curSensorData == 0)
+ _vm->_moveSens->triggerEffect(curThingSensor, curThingSensor->getAttrEffectA(), mapX, mapY, curCell);
+ }
+ } else if (curSensorType == k5_SensorWallAndOrGate) {
+ int16 bitMask = 1 << (event->_C.A._cell);
+ if (event->_C.A._effect == k2_SensorEffToggle) {
+ if (getFlag(curSensorData, bitMask))
+ clearFlag(curSensorData, bitMask);
+ else
+ setFlag(curSensorData, bitMask);
+ } else if (event->_C.A._effect)
+ clearFlag(curSensorData, bitMask);
+ else
+ setFlag(curSensorData, bitMask);
+
+ curThingSensor->setData(curSensorData);
+ bool triggerSetEffect = (Sensor::getDataMask1(curSensorData) == Sensor::getDataMask2(curSensorData)) != curThingSensor->getAttrRevertEffectA();
+ if (curThingSensor->getAttrEffectA() == k3_SensorEffHold)
+ _vm->_moveSens->triggerEffect(curThingSensor, triggerSetEffect ? k0_SensorEffSet : k1_SensorEffClear, mapX, mapY, curCell);
+ else if (triggerSetEffect)
+ _vm->_moveSens->triggerEffect(curThingSensor, curThingSensor->getAttrEffectA(), mapX, mapY, curCell);
+ } else if ((((curSensorType >= k7_SensorWallSingleProjLauncherNewObj) && (curSensorType <= k10_SensorWallDoubleProjLauncherExplosion)) || (curSensorType == k14_SensorWallSingleProjLauncherSquareObj) || (curSensorType == k15_SensorWallDoubleProjLauncherSquareObj)) && (curThing.getCell() == event->_C.A._cell)) {
+ triggerProjectileLauncher(curThingSensor, event);
+ if (curThingSensor->getAttrOnlyOnce())
+ curThingSensor->setTypeDisabled();
+ } else if (curSensorType == k18_SensorWallEndGame) {
+ _vm->delay(60 * curThingSensor->getAttrValue());
+ _vm->_restartGameAllowed = false;
+ _vm->_gameWon = true;
+ _vm->endGame(true);
+ }
+ }
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+ _vm->_moveSens->processRotationEffect();
+}
+
+void Timeline::triggerProjectileLauncher(Sensor *sensor, TimelineEvent *event) {
+ int16 mapX = event->_B._location._mapX;
+ int16 mapY = event->_B._location._mapY;
+ uint16 cell = event->_C.A._cell;
+ uint16 projectileCell = returnOppositeDir((Direction)cell);
+ int16 sensorType = sensor->getType();
+ int16 sensorData = sensor->getData();
+ int16 kineticEnergy = sensor->getActionKineticEnergy();
+ int16 stepEnergy = sensor->getActionStepEnergy();
+ bool launchSingleProjectile = (sensorType == k7_SensorWallSingleProjLauncherNewObj) ||
+ (sensorType == k8_SensorWallSingleProjLauncherExplosion) ||
+ (sensorType == k14_SensorWallSingleProjLauncherSquareObj);
+
+ Thing firstProjectileAssociatedThing;
+ Thing secondProjectileAssociatedThing;
+ if ((sensorType == k8_SensorWallSingleProjLauncherExplosion) || (sensorType == k10_SensorWallDoubleProjLauncherExplosion))
+ firstProjectileAssociatedThing = secondProjectileAssociatedThing = Thing(sensorData + Thing::_firstExplosion.toUint16());
+ else if ((sensorType == k14_SensorWallSingleProjLauncherSquareObj) || (sensorType == k15_SensorWallDoubleProjLauncherSquareObj)) {
+ firstProjectileAssociatedThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
+ while (firstProjectileAssociatedThing != Thing::_none) { /* BUG0_19 The game crashes when an object launcher sensor is triggered. Thing::_none should be Thing::_endOfList. If there are no more objects on the square then this loop may return an undefined value, this can crash the game. In the original DM and CSB dungeons, the number of times that these sensors are triggered is always controlled to be equal to the number of available objects (with a countdown sensor or a number of once only sensors) */
+ uint16 projectiveThingCell = firstProjectileAssociatedThing.getCell();
+ if ((firstProjectileAssociatedThing.getType() > k3_SensorThingType) && ((projectiveThingCell == cell) || (projectiveThingCell == returnNextVal(cell))))
+ break;
+ firstProjectileAssociatedThing = _vm->_dungeonMan->getNextThing(firstProjectileAssociatedThing);
+ }
+ if (firstProjectileAssociatedThing == Thing::_none) /* BUG0_19 The game crashes when an object launcher sensor is triggered. Thing::_none should be Thing::_endOfList */
+ return;
+
+ _vm->_dungeonMan->unlinkThingFromList(firstProjectileAssociatedThing, Thing(0), mapX, mapY); /* The object is removed without triggering any sensor effects */
+ if (!launchSingleProjectile) {
+ secondProjectileAssociatedThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
+ while (secondProjectileAssociatedThing != Thing::_none) { /* BUG0_19 The game crashes when an object launcher sensor is triggered. Thing::_none should be Thing::_endOfList. If there are no more objects on the square then this loop may return an undefined value, this can crash the game */
+ uint16 projectiveThingCell = secondProjectileAssociatedThing.getCell();
+ if ((secondProjectileAssociatedThing.getType() > k3_SensorThingType) && ((projectiveThingCell == cell) || (projectiveThingCell == returnNextVal(cell))))
+ break;
+ secondProjectileAssociatedThing = _vm->_dungeonMan->getNextThing(secondProjectileAssociatedThing);
+ }
+ if (secondProjectileAssociatedThing == Thing::_none) /* BUG0_19 The game crashes when an object launcher sensor is triggered. Thing::_none should be Thing::_endOfList */
+ launchSingleProjectile = true;
+ else
+ _vm->_dungeonMan->unlinkThingFromList(secondProjectileAssociatedThing, Thing::_none, mapX, mapY); /* The object is removed without triggering any sensor effects */
+ }
+ } else {
+ firstProjectileAssociatedThing = _vm->_dungeonMan->getObjForProjectileLaucherOrObjGen(sensorData);
+ if ((firstProjectileAssociatedThing) == Thing::_none)
+ return;
+
+ secondProjectileAssociatedThing = _vm->_dungeonMan->getObjForProjectileLaucherOrObjGen(sensorData);
+ if (!launchSingleProjectile && (secondProjectileAssociatedThing == Thing::_none))
+ launchSingleProjectile = true;
+ }
+ if (launchSingleProjectile)
+ projectileCell = normalizeModulo4(projectileCell + _vm->getRandomNumber(2));
+
+ /* BUG0_20 The game crashes if the launcher sensor is on a map boundary and shoots in a direction outside the map */
+ mapX += _vm->_dirIntoStepCountEast[cell];
+ mapY += _vm->_dirIntoStepCountNorth[cell];
+ _vm->_projexpl->_createLauncherProjectile = true;
+ _vm->_projexpl->createProjectile(firstProjectileAssociatedThing, mapX, mapY, projectileCell, (Direction)cell, kineticEnergy, 100, stepEnergy);
+ if (!launchSingleProjectile)
+ _vm->_projexpl->createProjectile(secondProjectileAssociatedThing, mapX, mapY, returnNextVal(projectileCell), (Direction)cell, kineticEnergy, 100, stepEnergy);
+
+ _vm->_projexpl->_createLauncherProjectile = false;
+}
+
+void Timeline::processEventSquareCorridor(TimelineEvent *event) {
+ uint16 mapX = event->_B._location._mapX;
+ uint16 mapY = event->_B._location._mapY;
+ Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
+ while (curThing != Thing::_endOfList) {
+ int16 curThingType = curThing.getType();
+ if (curThingType == k2_TextstringType) {
+ TextString *textString = (TextString *)_vm->_dungeonMan->getThingData(curThing);
+ bool textCurrentlyVisible = textString->isVisible();
+ if (event->_C.A._effect == k2_SensorEffToggle)
+ textString->setVisible(!textCurrentlyVisible);
+ else
+ textString->setVisible((event->_C.A._effect == k0_SensorEffSet));
+
+ if (!textCurrentlyVisible && textString->isVisible() && (_vm->_dungeonMan->_currMapIndex == _vm->_dungeonMan->_partyMapIndex) && (mapX == _vm->_dungeonMan->_partyMapX) && (mapY == _vm->_dungeonMan->_partyMapY)) {
+ _vm->_dungeonMan->decodeText(_vm->_stringBuildBuffer, curThing, k1_TextTypeMessage);
+ _vm->_textMan->printMessage(k15_ColorWhite, _vm->_stringBuildBuffer);
+ }
+ } else if (curThingType == k3_SensorThingType) {
+ Sensor *curSensor = (Sensor *)_vm->_dungeonMan->getThingData(curThing);
+ if (curSensor->getType() == k6_SensorFloorGroupGenerator) {
+ int16 creatureCount = curSensor->getAttrValue();
+ if (getFlag(creatureCount, k0x0008_randomizeGeneratedCreatureCount))
+ creatureCount = _vm->getRandomNumber(getFlag(creatureCount, k0x0007_generatedCreatureCount));
+ else
+ creatureCount--;
+
+ uint16 healthMultiplier = curSensor->getActionHealthMultiplier();
+ if (healthMultiplier == 0)
+ healthMultiplier = _vm->_dungeonMan->_currMap->_difficulty;
+
+ _vm->_groupMan->groupGetGenerated(curSensor->getData(), healthMultiplier, creatureCount, (Direction)_vm->getRandomNumber(4), mapX, mapY);
+ if (curSensor->getAttrAudibleA())
+ _vm->_sound->requestPlay(k17_soundBUZZ, mapX, mapY, k1_soundModePlayIfPrioritized);
+
+ if (curSensor->getAttrOnlyOnce())
+ curSensor->setTypeDisabled();
+ else {
+ uint16 actionTicks = curSensor->getActionTicks();
+ if (actionTicks != 0) {
+ curSensor->setTypeDisabled();
+ if (actionTicks > 127)
+ actionTicks = (actionTicks - 126) << 6;
+
+ TimelineEvent newEvent;
+ newEvent._type = k65_TMEventTypeEnableGroupGenerator;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_currMapIndex, _vm->_gameTime + actionTicks);
+ newEvent._priority = 0;
+ newEvent._B._location._mapX = mapX;
+ newEvent._B._location._mapY = mapY;
+ newEvent._B._location._mapY = mapY;
+ addEventGetEventIndex(&newEvent);
+ }
+ }
+ }
+ }
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+}
+
+void Timeline::processEventsMoveGroup(TimelineEvent *event) {
+ bool randomDirectionMoveRetried = false;
+ uint16 mapX = event->_B._location._mapX;
+ uint16 mapY = event->_B._location._mapY;
+
+T0252001:
+ if (((_vm->_dungeonMan->_currMapIndex != _vm->_dungeonMan->_partyMapIndex) || (mapX != _vm->_dungeonMan->_partyMapX) || (mapY != _vm->_dungeonMan->_partyMapY)) && (_vm->_groupMan->groupGetThing(mapX, mapY) == Thing::_endOfList)) { /* BUG0_24 Lord Chaos may teleport into one of the Black Flames and become invisible until the Black Flame is killed. In this case, _vm->_groupMan->f175_groupGetThing returns the Black Flame thing and the Lord Chaos thing is not moved into the dungeon until the Black Flame is killed */
+ if (event->_type == k61_TMEventTypeMoveGroupAudible)
+ _vm->_sound->requestPlay(k17_soundBUZZ, mapX, mapY, k1_soundModePlayIfPrioritized);
+
+ _vm->_moveSens->getMoveResult(Thing(event->_C._slot), kM1_MapXNotOnASquare, 0, mapX, mapY);
+ } else {
+ if (!randomDirectionMoveRetried) {
+ randomDirectionMoveRetried = true;
+ Group *group = (Group *)_vm->_dungeonMan->getThingData(Thing(event->_C._slot));
+ if ((group->_type == k23_CreatureTypeLordChaos) && !_vm->getRandomNumber(4)) {
+ switch (_vm->getRandomNumber(4)) {
+ case 0:
+ mapX--;
+ break;
+ case 1:
+ mapX++;
+ break;
+ case 2:
+ mapY--;
+ break;
+ case 3:
+ mapY++;
+ }
+ if (_vm->_groupMan->isSquareACorridorTeleporterPitOrDoor(mapX, mapY))
+ goto T0252001;
+ }
+ }
+ event->_mapTime += 5;
+ addEventGetEventIndex(event);
+ }
+}
+
+void Timeline::procesEventEnableGroupGenerator(TimelineEvent *event) {
+ Thing curThing = _vm->_dungeonMan->getSquareFirstThing(event->_B._location._mapX, event->_B._location._mapY);
+ while (curThing != Thing::_none) {
+ if ((curThing.getType()) == k3_SensorThingType) {
+ Sensor *curSensor = (Sensor *)_vm->_dungeonMan->getThingData(curThing);
+ if (curSensor->getType() == k0_SensorDisabled) {
+ curSensor->setDatAndTypeWithOr(k6_SensorFloorGroupGenerator);
+ return;
+ }
+ }
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+}
+
+void Timeline::processEventEnableChampionAction(uint16 champIndex) {
+ Champion *curChampion = &_vm->_championMan->_champions[champIndex];
+ curChampion->_enableActionEventIndex = -1;
+ clearFlag(curChampion->_attributes, kDMAttributeDisableAction);
+ if (curChampion->_actionIndex != kDMActionNone) {
+ curChampion->_actionDefense -= _actionDefense[curChampion->_actionDefense];
+ }
+ if (curChampion->_currHealth) {
+ if ((curChampion->_actionIndex == kDMActionShoot) && (curChampion->_slots[kDMSlotReadyHand] == Thing::_none)) {
+ int16 slotIndex = kDMSlotQuiverLine1_1;
+ if (_vm->_championMan->isAmmunitionCompatibleWithWeapon(champIndex, kDMSlotActionHand, slotIndex))
+ _vm->_championMan->addObjectInSlot((ChampionIndex)champIndex, _vm->_championMan->getObjectRemovedFromSlot(champIndex, slotIndex), kDMSlotReadyHand);
+ else {
+ for (int16 quiverSlotIndex = 0; quiverSlotIndex < 3; quiverSlotIndex++) {
+ slotIndex = quiverSlotIndex + kDMSlotQuiverLine2_1;
+ if (_vm->_championMan->isAmmunitionCompatibleWithWeapon(champIndex, kDMSlotActionHand, slotIndex))
+ _vm->_championMan->addObjectInSlot((ChampionIndex)champIndex, _vm->_championMan->getObjectRemovedFromSlot(champIndex, slotIndex), kDMSlotReadyHand);
+ }
+ }
+ }
+ setFlag(curChampion->_attributes, kDMAttributeActionHand);
+ _vm->_championMan->drawChampionState((ChampionIndex)champIndex);
+ }
+ curChampion->_actionIndex = kDMActionNone;
+}
+
+void Timeline::processEventMoveWeaponFromQuiverToSlot(uint16 champIndex, uint16 slotIndex) {
+ Champion *curChampion = &_vm->_championMan->_champions[champIndex];
+ if (curChampion->_slots[slotIndex] != Thing::_none)
+ return;
+
+ if (hasWeaponMovedSlot(champIndex, curChampion, kDMSlotQuiverLine1_1, slotIndex))
+ return;
+
+ for (uint16 srcSlotIndex = kDMSlotQuiverLine2_1; srcSlotIndex <= kDMSlotQuiverLine2_2; srcSlotIndex++) {
+ if (hasWeaponMovedSlot(champIndex, curChampion, srcSlotIndex, slotIndex))
+ break;
+ }
+}
+
+bool Timeline::hasWeaponMovedSlot(int16 champIndex, Champion *champ, uint16 sourceSlotIndex, int16 destSlotIndex) {
+ if (Thing(champ->_slots[sourceSlotIndex]).getType() == k5_WeaponThingType) {
+ _vm->_championMan->addObjectInSlot((ChampionIndex)champIndex, _vm->_championMan->getObjectRemovedFromSlot(champIndex, sourceSlotIndex),
+ (ChampionSlot)destSlotIndex);
+ return true;
+ }
+ return false;
+}
+
+void Timeline::processEventHideDamageReceived(uint16 champIndex) {
+ Champion *curChampion = &_vm->_championMan->_champions[champIndex];
+ curChampion->_hideDamageReceivedIndex = -1;
+ if (!curChampion->_currHealth)
+ return;
+
+ if (_vm->indexToOrdinal(champIndex) == _vm->_inventoryMan->_inventoryChampionOrdinal) {
+ _vm->_eventMan->showMouse();
+ _vm->_inventoryMan->drawStatusBoxPortrait((ChampionIndex)champIndex);
+ _vm->_eventMan->hideMouse();
+ } else {
+ setFlag(curChampion->_attributes, kDMAttributeNameTitle);
+ _vm->_championMan->drawChampionState((ChampionIndex)champIndex);
+ }
+}
+
+void Timeline::processEventLight(TimelineEvent *event) {
+ int16 lightPower = event->_B._lightPower;
+ if (lightPower == 0)
+ return;
+
+ bool negativeLightPower = (lightPower < 0);
+ if (negativeLightPower)
+ lightPower = -lightPower;
+
+ int16 weakerLightPower = lightPower - 1;
+ int16 lightAmount = _vm->_championMan->_lightPowerToLightAmount[lightPower] - _vm->_championMan->_lightPowerToLightAmount[weakerLightPower];
+ if (negativeLightPower) {
+ lightAmount = -lightAmount;
+ weakerLightPower = -weakerLightPower;
+ }
+ _vm->_championMan->_party._magicalLightAmount += lightAmount;
+ if (weakerLightPower) {
+ TimelineEvent newEvent;
+ newEvent._type = k70_TMEventTypeLight;
+ newEvent._B._lightPower = weakerLightPower;
+ setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + 4);
+ newEvent._priority = 0;
+ addEventGetEventIndex(&newEvent);
+ }
+}
+
+void Timeline::refreshAllChampionStatusBoxes() {
+ for (uint16 idx = kDMChampionFirst; idx < _vm->_championMan->_partyChampionCount; idx++)
+ setFlag(_vm->_championMan->_champions[idx]._attributes, kDMAttributeStatusBox);
+
+ _vm->_championMan->drawAllChampionStates();
+}
+
+void Timeline::processEventViAltarRebirth(TimelineEvent *event) {
+ int16 mapX = event->_B._location._mapX;
+ int16 mapY = event->_B._location._mapY;
+ uint16 cell = event->_C.A._cell;
+ uint16 championIndex = event->_priority;
+ uint16 rebirthStep = event->_C.A._effect;
+ switch (rebirthStep) { /* Rebirth is a 3 steps process (Step 2 -> Step 1 -> Step 0). Step is stored in the Effect value of the event */
+ case 2:
+ _vm->_projexpl->createExplosion(Thing::_explRebirthStep1, 0, mapX, mapY, cell);
+ event->_mapTime += 5;
+T0255002:
+ rebirthStep--;
+ event->_C.A._effect = rebirthStep;
+ addEventGetEventIndex(event);
+ break;
+ case 1: {
+ Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
+ while (curThing != Thing::_endOfList) {
+ if ((curThing.getCell() == cell) && (curThing.getType() == k10_JunkThingType)) {
+ int16 iconIndex = _vm->_objectMan->getIconIndex(curThing);
+ if (iconIndex == kDMIconIndiceJunkChampionBones) {
+ Junk *junkData = (Junk *)_vm->_dungeonMan->getThingData(curThing);
+ if (junkData->getChargeCount() == championIndex) {
+ _vm->_dungeonMan->unlinkThingFromList(curThing, Thing(0), mapX, mapY); /* BUG0_25 When a champion dies, no bones object is created so it is not possible to bring the champion back to life at an altar of Vi. Each time a champion is brought back to life, the bones object is removed from the dungeon but it is not marked as unused and thus becomes an orphan. After a large number of champion deaths, all JUNK things are exhausted and the game cannot create any more. This also affects the creation of JUNK things dropped by some creatures when they die (Screamer, Rockpile, Magenta Worm, Pain Rat, Red Dragon) */
+ junkData->setNextThing(Thing::_none);
+ event->_mapTime += 1;
+ goto T0255002;
+ }
+ }
+ }
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+ }
+ break;
+ case 0:
+ _vm->_championMan->viAltarRebirth(event->_priority);
+ }
+}
+
+void Timeline::saveEventsPart(Common::OutSaveFile *file) {
+ for (uint16 i = 0; i < _eventMaxCount; ++i) {
+ TimelineEvent *event = &_events[i];
+ file->writeSint32BE(event->_mapTime);
+ file->writeByte(event->_type);
+ file->writeByte(event->_priority);
+ file->writeByte(event->_B._location._mapX); // writing bytes of the union I think should preserve the union's identity
+ file->writeByte(event->_B._location._mapY);
+ file->writeUint16BE(event->_C.A._cell); // writing bytes of the union I think should preserve the union's identity
+ file->writeUint16BE(event->_C.A._effect);
+ }
+}
+
+void Timeline::saveTimelinePart(Common::OutSaveFile *file) {
+ for (uint16 i = 0; i < _eventMaxCount; ++i)
+ file->writeUint16BE(_timeline[i]);
+}
+
+void Timeline::loadEventsPart(Common::InSaveFile *file) {
+ for (uint16 i = 0; i < _eventMaxCount; ++i) {
+ TimelineEvent *event = &_events[i];
+ event->_mapTime = file->readSint32BE();
+ event->_type = file->readByte();
+ event->_priority = file->readByte();
+ event->_B._location._mapX = file->readByte();
+ event->_B._location._mapY = file->readByte();
+ event->_C.A._cell = file->readUint16BE();
+ event->_C.A._effect = file->readUint16BE();
+ }
+}
+
+void Timeline::loadTimelinePart(Common::InSaveFile *file) {
+ for (uint16 i = 0; i < _eventMaxCount; ++i)
+ _timeline[i] = file->readUint16BE();
+}
+
+}
diff --git a/engines/dm/timeline.h b/engines/dm/timeline.h
new file mode 100644
index 0000000000..727ed14c8f
--- /dev/null
+++ b/engines/dm/timeline.h
@@ -0,0 +1,202 @@
+/* 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 2
+* 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, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*
+*/
+
+/*
+* Based on the Reverse Engineering work of Christophe Fontanel,
+* maintainer of the Dungeon Master Encyclopaedia (http://dmweb.free.fr/)
+*/
+
+#ifndef DM_TIMELINE_H
+#define DM_TIMELINE_H
+
+#include "dm/dm.h"
+
+namespace DM {
+ class Champion;
+ class Sensor;
+
+/* Event types */
+enum TimelineEventType {
+/* Used when a creature in a group was damaged or killed by a Poison Cloud or by a closing door or if Lord Chaos is surrounded by = 3, Fluxcages */
+kM3_TMEventTypeCreateReactionEvent29DangerOnSquare = -3, // @ CM3_EVENT_CREATE_REACTION_EVENT_29_DANGER_ON_SQUARE
+/* Used when a projectile impacts with a creature in a group */
+kM2_TMEventTypeCreateReactionEvent30HitByProjectile = -2, // @ CM2_EVENT_CREATE_REACTION_EVENT_30_HIT_BY_PROJECTILE
+/* Used when the party bumps into a group or performs a melee attack */
+kM1_TMEventTypeCreateReactionEvent31ParyIsAdjacent = -1, // @ CM1_EVENT_CREATE_REACTION_EVENT_31_PARTY_IS_ADJACENT
+k0_TMEventTypeNone = 0, // @ C00_EVENT_NONE
+k1_TMEventTypeDoorAnimation = 1, // @ C01_EVENT_DOOR_ANIMATION
+k2_TMEventTypeDoorDestruction = 2, // @ C02_EVENT_DOOR_DESTRUCTION
+k5_TMEventTypeCorridor = 5, // @ C05_EVENT_CORRIDOR
+k6_TMEventTypeWall = 6, // @ C06_EVENT_WALL
+k7_TMEventTypeFakeWall = 7, // @ C07_EVENT_FAKEWALL
+k8_TMEventTypeTeleporter = 8, // @ C08_EVENT_TELEPORTER
+k9_TMEventTypePit = 9, // @ C09_EVENT_PIT
+k10_TMEventTypeDoor = 10, // @ C10_EVENT_DOOR
+k11_TMEventTypeEnableChampionAction = 11, // @ C11_EVENT_ENABLE_CHAMPION_ACTION
+k12_TMEventTypeHideDamageReceived = 12, // @ C12_EVENT_HIDE_DAMAGE_RECEIVED
+k13_TMEventTypeViAltarRebirth = 13, // @ C13_EVENT_VI_ALTAR_REBIRTH
+k20_TMEventTypePlaySound = 20, // @ C20_EVENT_PLAY_SOUND
+k22_TMEventTypeCPSE = 22, // @ C22_EVENT_CPSE
+k24_TMEventTypeRemoveFluxcage = 24, // @ C24_EVENT_REMOVE_FLUXCAGE
+k25_TMEventTypeExplosion = 25, // @ C25_EVENT_EXPLOSION
+k29_TMEventTypeGroupReactionDangerOnSquare = 29, // @ C29_EVENT_GROUP_REACTION_DANGER_ON_SQUARE
+k30_TMEventTypeGroupReacionHitByProjectile = 30, // @ C30_EVENT_GROUP_REACTION_HIT_BY_PROJECTILE
+k31_TMEventTypeGroupReactionPartyIsAdjecent = 31, // @ C31_EVENT_GROUP_REACTION_PARTY_IS_ADJACENT
+k32_TMEventTypeUpdateAspectGroup = 32, // @ C32_EVENT_UPDATE_ASPECT_GROUP
+/* Events = 33,-36 and = 38,-41 are used for individual creatures only while the group is attacking the party */
+k33_TMEventTypeUpdateAspectCreature_0 = 33, // @ C33_EVENT_UPDATE_ASPECT_CREATURE_0
+k34_TMEventTypeUpdateAspectCreature_1 = 34, // @ C34_EVENT_UPDATE_ASPECT_CREATURE_1
+k35_TMEventTypeUpdateAspectCreature_2 = 35, // @ C35_EVENT_UPDATE_ASPECT_CREATURE_2
+k36_TMEventTypeUpdateAspectCreature_3 = 36, // @ C36_EVENT_UPDATE_ASPECT_CREATURE_3
+k37_TMEventTypeUpdateBehaviourGroup = 37, // @ C37_EVENT_UPDATE_BEHAVIOR_GROUP
+k38_TMEventTypeUpdateBehaviour_0 = 38, // @ C38_EVENT_UPDATE_BEHAVIOR_CREATURE_0
+k39_TMEventTypeUpdateBehaviour_1 = 39, // @ C39_EVENT_UPDATE_BEHAVIOR_CREATURE_1
+k40_TMEventTypeUpdateBehaviour_2 = 40, // @ C40_EVENT_UPDATE_BEHAVIOR_CREATURE_2
+k41_TMEventTypeUpdateBehaviour_3 = 41, // @ C41_EVENT_UPDATE_BEHAVIOR_CREATURE_3
+/* Projectiles created by a champion (by casting a spell, shooting a weapon or throwing an object) or by a creature (by casting a spell) ignore impacts during their first movement otherwise an impact would always occur immediately as these projectiles are created on the champion or creature square */
+k48_TMEventTypeMoveProjectileIgnoreImpacts = 48, // @ C48_EVENT_MOVE_PROJECTILE_IGNORE_IMPACTS
+/* Projectiles created by projectile launcher sensors never ignore impacts as well as all other projectiles after their first movement */
+k49_TMEventTypeMoveProjectile = 49, // @ C49_EVENT_MOVE_PROJECTILE
+k53_TMEventTypeWatchdoge = 53, // @ C53_EVENT_WATCHDOG
+k60_TMEventTypeMoveGroupSilent = 60, // @ C60_EVENT_MOVE_GROUP_SILENT
+k61_TMEventTypeMoveGroupAudible = 61, // @ C61_EVENT_MOVE_GROUP_AUDIBLE
+k65_TMEventTypeEnableGroupGenerator = 65, // @ C65_EVENT_ENABLE_GROUP_GENERATOR
+k70_TMEventTypeLight = 70, // @ C70_EVENT_LIGHT
+k71_TMEventTypeInvisibility = 71, // @ C71_EVENT_INVISIBILITY
+k72_TMEventTypeChampionShield = 72, // @ C72_EVENT_CHAMPION_SHIELD
+k73_TMEventTypeThievesEye = 73, // @ C73_EVENT_THIEVES_EYE
+k74_TMEventTypePartyShield = 74, // @ C74_EVENT_PARTY_SHIELD
+k75_TMEventTypePoisonChampion = 75, // @ C75_EVENT_POISON_CHAMPION
+k77_TMEventTypeSpellShield = 77, // @ C77_EVENT_SPELLSHIELD
+k78_TMEventTypeFireShield = 78, // @ C78_EVENT_FIRESHIELD
+k79_TMEventTypeFootprints = 79, // @ C79_EVENT_FOOTPRINTS
+k80_TMEventTypeMagicMap_C80 = 80, // @ C80_EVENT_MAGIC_MAP
+k81_TMEventTypeMagicMap_C81 = 81, // @ C81_EVENT_MAGIC_MAP
+k82_TMEventTypeMagicMap_C82 = 82, // @ C82_EVENT_MAGIC_MAP
+k83_TMEventTypeMagicMap_C83 = 83 // @ C83_EVENT_MAGIC_MAP
+};
+
+#define k0x0007_generatedCreatureCount 0x0007 // @ MASK0x0007_GENERATED_CREATURE_COUNT
+#define k0x0008_randomizeGeneratedCreatureCount 0x0008 // @ MASK0x0008_RANDOMIZE_GENERATED_CREATURE_COUNT
+
+class TimelineEvent {
+public:
+ int32 _mapTime;
+ byte _type;
+ byte _priority; // CHECKME: byte? or int16? Inconsistency in the code
+
+ uint16 getTypePriority() { return (_type << 8) + _priority; }
+
+ union B_unionTimelineEvent {
+ struct {
+ byte _mapX;
+ byte _mapY;
+ } _location;
+ int16 _attack;
+ int16 _defense;
+ int16 _lightPower;
+ uint16 _slot; // Thing
+ int16 _slotOrdinal;
+ B_unionTimelineEvent() {}
+ } _B;
+
+ int16 getMapXY() { return (_B._location._mapX << 8) + _B._location._mapY; }
+
+ union C_uionTimelineEvent {
+ struct {
+ byte _cell;
+ byte _effect;
+ } A;
+
+ class {
+ uint16 _backing;
+ public:
+ uint16 getMapX() { return _backing & 0x1F; }
+ uint16 getMapY() { return (_backing >> 5) & 0x1F; }
+ Direction getDir() { return (Direction)((_backing >> 10) & 0x3); }
+ uint16 getStepEnergy() { return (_backing >> 12) & 0xF; }
+ void setMapX(uint16 val) { _backing = (_backing & ~0x1F) | (val & 0x1F); }
+ void setMapY(uint16 val) { _backing = (_backing & ~(0x1F << 5)) | ((val & 0x1F) << 5); }
+ void setDir(Direction val) { _backing = (_backing & ~(0x3 << 10)) | ((val & 0x3) << 10); }
+ void setStepEnergy(uint16 val) { _backing = (_backing & ~(0xF << 12)) | ((val & 0xF) << 12); }
+ } _projectile;
+
+ uint16 _slot;
+ int16 _soundIndex;
+ byte _ticks;
+ C_uionTimelineEvent() {}
+ } _C;
+}; // @ EVENT
+
+class Timeline {
+ DMEngine *_vm;
+public:
+ uint16 _eventMaxCount; // @ G0369_ui_EventMaximumCount
+ TimelineEvent *_events; // @ G0370_ps_Events
+ uint16 _eventCount; // @ G0372_ui_EventCount
+ uint16 *_timeline; // @ G0371_pui_Timeline
+ uint16 _firstUnusedEventIndex; // @ G0373_ui_FirstUnusedEventIndex
+
+ explicit Timeline(DMEngine *vm);
+ ~Timeline();
+ void initTimeline(); // @ F0233_TIMELINE_Initialize
+ void deleteEvent(uint16 eventIndex);// @ F0237_TIMELINE_DeleteEvent
+ void fixChronology(uint16 timelineIndex); // @ F0236_TIMELINE_FixChronology
+ bool isEventABeforeB(TimelineEvent *eventA, TimelineEvent *eventB); // @ F0234_TIMELINE_IsEventABeforeEventB
+ uint16 getIndex(uint16 eventIndex); // @ F0235_TIMELINE_GetIndex
+ uint16 addEventGetEventIndex(TimelineEvent *event); // @ F0238_TIMELINE_AddEvent_GetEventIndex_CPSE
+ void processTimeline(); // @ F0261_TIMELINE_Process_CPSEF
+ bool isFirstEventExpiered(); // @ F0240_TIMELINE_IsFirstEventExpired_CPSE
+ void extractFirstEvent(TimelineEvent *event); // @ F0239_TIMELINE_ExtractFirstEvent
+ void processEventDoorAnimation(TimelineEvent *event); // @ F0241_TIMELINE_ProcessEvent1_DoorAnimation
+ void processEventSquareFakewall(TimelineEvent *event); // @ F0242_TIMELINE_ProcessEvent7_Square_FakeWall
+ void processEventDoorDestruction(TimelineEvent *event); // @ F0243_TIMELINE_ProcessEvent2_DoorDestruction
+ void processEventSquareDoor(TimelineEvent *event); // @ F0244_TIMELINE_ProcessEvent10_Square_Door
+ void processEventSquarePit(TimelineEvent *event); // @ F0251_TIMELINE_ProcessEvent9_Square_Pit
+ void moveTeleporterOrPitSquareThings(uint16 mapX, uint16 mapY); // @ F0249_TIMELINE_MoveTeleporterOrPitSquareThings
+ void processEventSquareTeleporter(TimelineEvent *event); // @ F0250_TIMELINE_ProcessEvent8_Square_Teleporter
+ void processEventSquareWall(TimelineEvent *event); // @ F0248_TIMELINE_ProcessEvent6_Square_Wall
+ void triggerProjectileLauncher(Sensor *sensor, TimelineEvent *event); // @ F0247_TIMELINE_TriggerProjectileLauncher
+ void processEventSquareCorridor(TimelineEvent *event); // @ F0245_TIMELINE_ProcessEvent5_Square_Corridor
+ void processEventsMoveGroup(TimelineEvent *event); // @ F0252_TIMELINE_ProcessEvents60to61_MoveGroup
+ void procesEventEnableGroupGenerator(TimelineEvent *event); // @ F0246_TIMELINE_ProcessEvent65_EnableGroupGenerator
+ void processEventEnableChampionAction(uint16 champIndex); // @ F0253_TIMELINE_ProcessEvent11Part1_EnableChampionAction
+ void processEventMoveWeaponFromQuiverToSlot(uint16 champIndex, uint16 slotIndex);// @ F0259_TIMELINE_ProcessEvent11Part2_MoveWeaponFromQuiverToSlot
+ bool hasWeaponMovedSlot(int16 champIndex, Champion *champ,
+ uint16 sourceSlotIndex, int16 destSlotIndex); // @ F0258_TIMELINE_HasWeaponMovedToSlot
+ void processEventHideDamageReceived(uint16 champIndex); // @ F0254_TIMELINE_ProcessEvent12_HideDamageReceived
+ void processEventLight(TimelineEvent *event); // @ F0257_TIMELINE_ProcessEvent70_Light
+ void refreshAllChampionStatusBoxes(); // @ F0260_TIMELINE_RefreshAllChampionStatusBoxes
+ void processEventViAltarRebirth(TimelineEvent *event); // @ F0255_TIMELINE_ProcessEvent13_ViAltarRebirth
+ void saveEventsPart(Common::OutSaveFile *file);
+ void saveTimelinePart(Common::OutSaveFile *file);
+ void loadEventsPart(Common::InSaveFile *file);
+ void loadTimelinePart(Common::InSaveFile *file);
+
+ signed char _actionDefense[44]; // @ G0495_ac_Graphic560_ActionDefense
+
+ void initConstants();
+};
+
+}
+
+#endif