aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.common5
-rw-r--r--audio/softsynth/cms.h4
-rw-r--r--base/version.cpp2
-rwxr-xr-xconfigure37
-rw-r--r--devtools/create_titanic/create_titanic_dat.cpp68
-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.cpp290
-rw-r--r--engines/dm/console.h61
-rw-r--r--engines/dm/detection.cpp175
-rw-r--r--engines/dm/dialog.cpp257
-rw-r--r--engines/dm/dialog.h63
-rw-r--r--engines/dm/dm.cpp1030
-rw-r--r--engines/dm/dm.h328
-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.cpp1673
-rw-r--r--engines/dm/eventman.h326
-rw-r--r--engines/dm/gfx.cpp3865
-rw-r--r--engines/dm/gfx.h833
-rw-r--r--engines/dm/group.cpp2039
-rw-r--r--engines/dm/group.h254
-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.h204
-rw-r--r--engines/fullpipe/scenes/scene27.cpp1
-rw-r--r--engines/fullpipe/statics.cpp23
-rw-r--r--engines/titanic/carry/long_stick.cpp2
-rw-r--r--engines/titanic/carry/napkin.cpp2
-rw-r--r--engines/titanic/carry/vision_centre.cpp4
-rw-r--r--engines/titanic/core/game_object.cpp14
-rw-r--r--engines/titanic/core/game_object.h19
-rw-r--r--engines/titanic/core/saveable_object.cpp6
-rw-r--r--engines/titanic/detection_tables.h2
-rw-r--r--engines/titanic/game/chicken_dispensor.cpp6
-rw-r--r--engines/titanic/game/fan_control.cpp2
-rw-r--r--engines/titanic/game/hammer_dispensor_button.cpp2
-rw-r--r--engines/titanic/game/lemon_dispensor.cpp2
-rw-r--r--engines/titanic/game/light.cpp8
-rw-r--r--engines/titanic/game/long_stick_dispenser.cpp7
-rw-r--r--engines/titanic/game/nav_helmet.cpp2
-rw-r--r--engines/titanic/game/parrot/parrot_perch_holder.cpp2
-rw-r--r--engines/titanic/game/pet/pet_lift.cpp4
-rw-r--r--engines/titanic/game/phonograph_lid.cpp2
-rw-r--r--engines/titanic/game/pickup/pick_up_speech_centre.cpp2
-rw-r--r--engines/titanic/game/place_holder_item.cpp96
-rw-r--r--engines/titanic/game/place_holder_item.h63
-rw-r--r--engines/titanic/game/sauce_dispensor.cpp8
-rw-r--r--engines/titanic/game/seasonal_adjustment.cpp2
-rw-r--r--engines/titanic/game/sgt/sgt_nav.cpp6
-rw-r--r--engines/titanic/game/sgt/sgt_state_room.cpp2
-rw-r--r--engines/titanic/game/speech_dispensor.cpp8
-rw-r--r--engines/titanic/game/sweet_bowl.cpp2
-rw-r--r--engines/titanic/game/third_class_canal.cpp2
-rw-r--r--engines/titanic/game/transport/lift_indicator.cpp9
-rw-r--r--engines/titanic/game/transport/pellerator.cpp4
-rw-r--r--engines/titanic/game/variable_list.cpp73
-rw-r--r--engines/titanic/game/variable_list.h63
-rw-r--r--engines/titanic/game/wheel_hotspot.cpp2
-rw-r--r--engines/titanic/module.mk3
-rw-r--r--engines/titanic/moves/call_pellerator.cpp4
-rw-r--r--engines/titanic/moves/enter_exit_first_class_state.cpp5
-rw-r--r--engines/titanic/moves/enter_sec_class_state.cpp2
-rw-r--r--engines/titanic/moves/exit_pellerator.cpp4
-rw-r--r--engines/titanic/moves/exit_tiania.cpp3
-rw-r--r--engines/titanic/moves/restricted_move.cpp4
-rw-r--r--engines/titanic/moves/trip_down_canal.cpp2
-rw-r--r--engines/titanic/npcs/bilge_succubus.cpp10
-rw-r--r--engines/titanic/npcs/deskbot.cpp4
-rw-r--r--engines/titanic/npcs/succubus.cpp12
-rw-r--r--engines/titanic/pet_control/pet_control.cpp8
-rw-r--r--engines/titanic/pet_control/pet_control.h9
-rw-r--r--engines/titanic/pet_control/pet_conversations.cpp2
-rw-r--r--engines/titanic/pet_control/pet_load.cpp2
-rw-r--r--engines/titanic/pet_control/pet_save.cpp4
-rw-r--r--engines/titanic/pet_control/pet_starfield.cpp2
-rw-r--r--engines/titanic/support/strings.cpp35
-rw-r--r--engines/titanic/support/strings.h98
-rw-r--r--engines/xeen/character.cpp1840
-rw-r--r--engines/xeen/character.h423
-rw-r--r--engines/xeen/combat.cpp2065
-rw-r--r--engines/xeen/combat.h221
-rw-r--r--engines/xeen/configure.engine3
-rw-r--r--engines/xeen/debugger.cpp85
-rw-r--r--engines/xeen/debugger.h47
-rw-r--r--engines/xeen/detection.cpp196
-rw-r--r--engines/xeen/detection_tables.h67
-rw-r--r--engines/xeen/dialogs.cpp260
-rw-r--r--engines/xeen/dialogs.h115
-rw-r--r--engines/xeen/dialogs_automap.cpp428
-rw-r--r--engines/xeen/dialogs_automap.h45
-rw-r--r--engines/xeen/dialogs_char_info.cpp568
-rw-r--r--engines/xeen/dialogs_char_info.h70
-rw-r--r--engines/xeen/dialogs_control_panel.cpp42
-rw-r--r--engines/xeen/dialogs_control_panel.h43
-rw-r--r--engines/xeen/dialogs_dismiss.cpp95
-rw-r--r--engines/xeen/dialogs_dismiss.h47
-rw-r--r--engines/xeen/dialogs_error.cpp118
-rw-r--r--engines/xeen/dialogs_error.h65
-rw-r--r--engines/xeen/dialogs_exchange.cpp80
-rw-r--r--engines/xeen/dialogs_exchange.h47
-rw-r--r--engines/xeen/dialogs_fight_options.cpp39
-rw-r--r--engines/xeen/dialogs_fight_options.h43
-rw-r--r--engines/xeen/dialogs_info.cpp128
-rw-r--r--engines/xeen/dialogs_info.h47
-rw-r--r--engines/xeen/dialogs_input.cpp275
-rw-r--r--engines/xeen/dialogs_input.h89
-rw-r--r--engines/xeen/dialogs_items.cpp1065
-rw-r--r--engines/xeen/dialogs_items.h91
-rw-r--r--engines/xeen/dialogs_options.cpp232
-rw-r--r--engines/xeen/dialogs_options.h95
-rw-r--r--engines/xeen/dialogs_party.cpp1045
-rw-r--r--engines/xeen/dialogs_party.h111
-rw-r--r--engines/xeen/dialogs_query.cpp160
-rw-r--r--engines/xeen/dialogs_query.h54
-rw-r--r--engines/xeen/dialogs_quests.cpp254
-rw-r--r--engines/xeen/dialogs_quests.h50
-rw-r--r--engines/xeen/dialogs_quick_ref.cpp88
-rw-r--r--engines/xeen/dialogs_quick_ref.h43
-rw-r--r--engines/xeen/dialogs_spells.cpp1030
-rw-r--r--engines/xeen/dialogs_spells.h162
-rw-r--r--engines/xeen/dialogs_whowill.cpp105
-rw-r--r--engines/xeen/dialogs_whowill.h43
-rw-r--r--engines/xeen/events.cpp179
-rw-r--r--engines/xeen/events.h121
-rw-r--r--engines/xeen/files.cpp230
-rw-r--r--engines/xeen/files.h170
-rw-r--r--engines/xeen/font.cpp347
-rw-r--r--engines/xeen/font.h101
-rw-r--r--engines/xeen/interface.cpp2291
-rw-r--r--engines/xeen/interface.h183
-rw-r--r--engines/xeen/interface_map.cpp4445
-rw-r--r--engines/xeen/interface_map.h175
-rw-r--r--engines/xeen/items.cpp29
-rw-r--r--engines/xeen/items.h34
-rw-r--r--engines/xeen/map.cpp1581
-rw-r--r--engines/xeen/map.h437
-rw-r--r--engines/xeen/module.mk53
-rw-r--r--engines/xeen/party.cpp733
-rw-r--r--engines/xeen/party.h204
-rw-r--r--engines/xeen/resources.cpp1590
-rw-r--r--engines/xeen/resources.h569
-rw-r--r--engines/xeen/saves.cpp171
-rw-r--r--engines/xeen/saves.h96
-rw-r--r--engines/xeen/screen.cpp479
-rw-r--r--engines/xeen/screen.h194
-rw-r--r--engines/xeen/scripts.cpp1770
-rw-r--r--engines/xeen/scripts.h341
-rw-r--r--engines/xeen/sound.cpp100
-rw-r--r--engines/xeen/sound.h119
-rw-r--r--engines/xeen/spells.cpp1329
-rw-r--r--engines/xeen/spells.h191
-rw-r--r--engines/xeen/sprites.cpp334
-rw-r--r--engines/xeen/sprites.h116
-rw-r--r--engines/xeen/town.cpp1310
-rw-r--r--engines/xeen/town.h133
-rw-r--r--engines/xeen/worldofxeen/clouds_cutscenes.cpp43
-rw-r--r--engines/xeen/worldofxeen/clouds_cutscenes.h55
-rw-r--r--engines/xeen/worldofxeen/darkside_cutscenes.cpp258
-rw-r--r--engines/xeen/worldofxeen/darkside_cutscenes.h54
-rw-r--r--engines/xeen/worldofxeen/worldofxeen.cpp46
-rw-r--r--engines/xeen/worldofxeen/worldofxeen.h50
-rw-r--r--engines/xeen/xeen.cpp353
-rw-r--r--engines/xeen/xeen.h213
-rw-r--r--engines/xeen/xsurface.cpp31
-rw-r--r--engines/xeen/xsurface.h53
189 files changed, 59165 insertions, 114 deletions
diff --git a/Makefile.common b/Makefile.common
index a1f43128a2..4d31ab90e8 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -165,6 +165,11 @@ VER_MINOR = $(shell echo $(VERSION) | cut -d. -f 2)
VER_PATCH = $(shell echo $(VERSION) | cut -d. -f 3 | cut -c1)
VER_EXTRA = $(shell echo $(VERSION) | cut -d. -f 3 | cut -c2-)
+ifdef AMIGAOS
+# Amiga needs date in specific format for the version cookie
+AMIGA_DATE = $(shell gdate '+%d.%m.%Y')
+base/version.o: CXXFLAGS:=$(CXXFLAGS) -DAMIGA_DATE=\"$(AMIGA_DATE)\"
+endif
######################################################################
# Get git's working copy information
diff --git a/audio/softsynth/cms.h b/audio/softsynth/cms.h
index 8c0f980b0a..64df30e037 100644
--- a/audio/softsynth/cms.h
+++ b/audio/softsynth/cms.h
@@ -68,7 +68,9 @@ struct SAA1099 {
class CMSEmulator {
public:
CMSEmulator(uint32 sampleRate) {
- _sampleRate = sampleRate;
+ // In PCs the chips run at 7.15909 MHz instead of 8 MHz.
+ // Adjust sampling rate upwards to bring pitch down.
+ _sampleRate = (sampleRate * 352) / 315;
memset(_saa1099, 0, sizeof(SAA1099)*2);
}
diff --git a/base/version.cpp b/base/version.cpp
index d40dd573f0..43795294ba 100644
--- a/base/version.cpp
+++ b/base/version.cpp
@@ -57,7 +57,7 @@
*/
const char *gScummVMVersion = SCUMMVM_VERSION;
#ifdef __amigaos4__
-static const char *version_cookie __attribute__((used)) = "$VER: ScummVM " SCUMMVM_VERSION " (" __DATE__ ", " __TIME__ ")";
+static const char *version_cookie __attribute__((used)) = "$VER: ScummVM " SCUMMVM_VERSION " (" AMIGA_DATE ")";
#endif
#ifdef __PLAYSTATION2__
const char *gScummVMBuildDate = "Git Master"; /* ScummVM Git Master */
diff --git a/configure b/configure
index 2f4ed0b38e..8315ba27b3 100755
--- a/configure
+++ b/configure
@@ -70,13 +70,23 @@ get_var() {
}
append_var() {
- VAR=${1}
- shift
- if eval test -z \"\$${VAR}\" ; then
- eval ${VAR}='$@'
- else
- eval ${VAR}=\"\$${VAR} \"'$@'
- fi
+ VAR=${1}
+ shift
+ if eval test -z \"\$${VAR}\" ; then
+ eval ${VAR}='$@'
+ else
+ eval ${VAR}=\"\$${VAR} \"'$@'
+ fi
+}
+
+prepend_var() {
+ VAR=${1}
+ shift
+ if eval test -z \"\$${VAR}\" ; then
+ eval ${VAR}='$@'
+ else
+ eval ${VAR}='$@'\" \$${VAR}\"
+ fi
}
# Add an engine: id name build subengines base-games dependencies
@@ -3370,15 +3380,16 @@ case $_backend in
#include "SDL_net.h"
int main(int argc, char *argv[]) { SDLNet_Init(); return 0; }
EOF
- cc_check $LIBS $INCLUDES $SDL_NET_CFLAGS $SDL_NET_LIBS && _sdlnet=yes
+ cc_check $SDL_NET_LIBS $LIBS $INCLUDES $SDL_NET_CFLAGS && _sdlnet=yes
fi
if test "$_sdlnet" = yes ; then
- append_var LIBS "$SDL_NET_LIBS"
+ # Some platforms require SDL to be after SDL_Net, thus we prepend var
+ prepend_var LIBS "$SDL_NET_LIBS"
append_var INCLUDES "$SDL_NET_CFLAGS"
fi
define_in_config_if_yes "$_sdlnet" 'USE_SDL_NET'
echo "$_sdlnet"
-
+
;;
esac
@@ -4168,6 +4179,12 @@ if test "$_libcurl" != "no"; then
LIBCURL_LIBS=`$_libcurlconfig --libs`
LIBCURL_CFLAGS=`$_libcurlconfig --cflags`
+ case $_host_os in
+ amigaos*)
+ append_var LIBCURL_LIBS "-lpthread"
+ ;;
+ esac
+
if test "$_libcurl" = "auto"; then
_libcurl=no
diff --git a/devtools/create_titanic/create_titanic_dat.cpp b/devtools/create_titanic/create_titanic_dat.cpp
index 82285dc928..6bbf8b11b8 100644
--- a/devtools/create_titanic/create_titanic_dat.cpp
+++ b/devtools/create_titanic/create_titanic_dat.cpp
@@ -55,7 +55,7 @@
*/
#define VERSION_NUMBER 1
-#define HEADER_SIZE 0xD00
+#define HEADER_SIZE 0xD40
Common::File inputFile, outputFile;
Common::PEResources res;
@@ -414,6 +414,70 @@ static const BedheadEntry OFF_RESTING_D_WRONG[1] = {
{ "Any", "Any", "Any", "ClosedWrong", 59, 70 }
};
+static const char *const STRINGS_EN[] = {
+ "",
+ "You are standing outside the Pellerator.",
+ "I'm sorry, you cannot enter this pellerator at present as a bot is in the way.",
+ "The Succ-U-Bus is in Standby, or \"Off\" mode at present.",
+ "There is currently nothing to deliver.",
+ "There is currently nothing in the tray to send.",
+ "The Succ-U-Bus is a Single Entity Delivery Device.",
+ "Chickens are allocated on a one-per-customer basis.",
+ "Only one piece of chicken per passenger. Thank you.",
+ "You have been upgraded to 1st Class status. Enjoy hugely.",
+ "You have been upgraded to 2nd Class status. Enjoy.",
+ "This room is reserved for the exclusive use of first class passengers."
+ " That does not currently include you",
+ "No losers.",
+ "Passengers of your class are not permitted to enter this area.",
+ "Please exit from the other side.",
+ "For mysterious and unknowable reasons, this transport is temporarily out of order.",
+ "Unfortunately this fan controller has blown a fuse.",
+ "In case of emergency hammer requirement, poke with long stick.",
+ "This stick is too short to reach the branches.",
+ "You are standing outside Elevator %d",
+ "I'm sorry, you cannot enter this elevator at present as a bot is in the way.",
+ "This elevator is currently in an advanced state of non-functionality.",
+ "That light appears to be loose.",
+ "Lumi-Glow(tm) Lights. They glow in the dark!",
+ "You already have one.",
+ "'This glass is totally and utterly unbreakable.",
+ "For emergency long stick, smash glass.",
+ "This dispenser has suddenly been fitted with unbreakable glass "
+ "to prevent unseemly hoarding of sticks.",
+ "The Chicken is already quite clean enough, thank you.",
+ "Now would be an excellent opportunity to adjust your viewing apparatus.",
+ "You cannot take this because the cage is locked shut.",
+ "You are already at your chosen destination.",
+ "Passengers of your class are not permitted to enter this area.",
+ "Sorry, you must be at least 3rd class before you can summon for help.",
+ "You have not assigned a room to go to.",
+ "Sorry, this elevator does not go below floor 27.",
+ "You must select a game to load first.",
+ "You must select a game to save first.",
+ "Please supply Galactic reference material.",
+ "This is the restaurant music system. It appears to be locked.",
+ "You can't pick this up on account of it being stuck to the branch.",
+ "You cannot get this, it is frozen to the branch.",
+ "Please check in at the reception desk.",
+ "This foodstuff is already sufficiently garnished.",
+ "Sadly, this dispenser is currently empty.",
+ "Please place food source beneath dispenser for sauce delivery.",
+ "The Seasonal Adjustment switch is not operational at the present time.",
+ "This is your stateroom. It is for sleeping. If you desire "
+ "entertainment or relaxation, please visit your local leisure lounge.",
+ "The bed will not currently support your weight."
+ " We are working on this problem but are unlikely to be able to fix it.",
+ "This is not your assigned room. Please do not enjoy.",
+ "Sadly, this is out of your reach.",
+ "The Succ-U-Bus is a Single Entity Delivery Device.",
+ "Sadly, the Grand Canal transport system is closed for the winter.",
+ "This area is off limits to passengers.",
+ "Go where?",
+ "It would be nice if you could take that but you can't.",
+ "A bowl of pistachio nuts.",
+ "Not a bowl of pistachio nuts."
+};
void NORETURN_PRE error(const char *s, ...) {
printf("%s\n", s);
@@ -869,7 +933,7 @@ void writeData() {
writeStringArray("TEXT/ITEM_NAMES", ITEM_NAMES, 46);
writeStringArray("TEXT/ITEM_IDS", ITEM_IDS, 40);
writeStringArray("TEXT/ROOM_NAMES", ROOM_NAMES, 34);
-
+ writeStringArray("TEXT/STRINGS", STRINGS_EN, 58);
const int TEXT_PHRASES[3] = { 0x61D3C8, 0x618340, 0x61B1E0 };
const int TEXT_REPLACEMENTS1[3] = { 0x61D9B0, 0x61C788, 0x61B7C8 };
const int TEXT_REPLACEMENTS2[3] = { 0x61DD20, 0x61CAF8, 0x61BB38 };
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..d138b4c719
--- /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 = kDMDirNorth;
+ _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() == kDMThingTypeWeapon) {
+ 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 = CLIP<int16>(40, ((skillLevel << 3) + _vm->getRandomNumber(32)), 200);
+ int16 stepEnergy = MAX(5, 11 - skillLevel);
+ _vm->_projexpl->createProjectile(curThing, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY,
+ _vm->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 == kDMThingTypeWeapon) || (thingType == kDMThingTypeArmour))
+ && (slotIndex >= kDMSlotReadyHand) && (slotIndex <= kDMSlotQuiverLine1_1)) {
+ if (thingType == kDMThingTypeWeapon) {
+ 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() == kDMThingTypeWeapon) {
+ 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 CLIP(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() == kDMThingTypeArmour) {
+ 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() == kDMThingTypeArmour) {
+ 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 CLIP(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 = CLIP<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._Bu._slotOrdinal = 0;
+
+ int16 eventIndex = curChampion->_enableActionEventIndex;
+ if (eventIndex >= 0) {
+ int32 currentEnableActionEventTime = _vm->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);
+ }
+ _vm->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 += CLIP(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 && (_vm->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 CLIP(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] = CLIP<unsigned 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;
+ _vm->setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + 36);
+ newEvent._Bu._attack = attack;
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ }
+
+ drawChampionState((ChampionIndex)champIndex);
+}
+
+void ChampionMan::setPartyDirection(int16 dir) {
+ if (dir == _vm->_dungeonMan->_partyDir)
+ return;
+
+ int16 dirDiff = dir - _vm->_dungeonMan->_partyDir;
+ if (dirDiff < 0)
+ dirDiff += 4;
+
+ Champion *curChampion = _champions;
+ for (int16 i = kDMChampionFirst; i < _partyChampionCount; i++) {
+ curChampion->_cell = (ViewCell)_vm->normalizeModulo4(curChampion->_cell + dirDiff);
+ curChampion->_dir = (Direction)_vm->normalizeModulo4(curChampion->_dir + dirDiff);
+ 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() != kDMThingTypeWeapon)
+ 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() == kDMThingTypeWeapon) && (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 = kDMCellNorthWest;
+ 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, _vm->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;
+ _vm->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];
+ _vm->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 | kDMThingTypeJunk);
+ 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(_vm->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(_vm->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 = CLIP(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(_vm->normalizeModulo4(viewCell + _vm->_dungeonMan->_partyDir)) != kDMChampionNone)
+ viewCell++;
+
+ championPtr->_cell = (ViewCell)_vm->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() != kDMstringTypeText)
+ 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 = _vm->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 > kDMThingTypeSensor) && (curThing.getCell() == championObjectsCell)) {
+ int16 objectAllowedSlots = _vm->_dungeonMan->_objectInfos[_vm->_dungeonMan->getObjectInfoIndex(curThing)]._allowedSlots;
+ uint16 curSlotIndex = kDMSlotReadyHand;
+ switch (thingType) {
+ case kDMThingTypeArmour: {
+ 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 kDMThingTypeWeapon:
+ 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 kDMThingTypeScroll:
+ case kDMThingTypePotion:
+ 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 kDMThingTypeContainer:
+ case kDMThingTypeJunk:
+ 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..33f897832a
--- /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" no
diff --git a/engines/dm/console.cpp b/engines/dm/console.cpp
new file mode 100644
index 0000000000..b87b753dbe
--- /dev/null
+++ b/engines/dm/console.cpp
@@ -0,0 +1,290 @@
+/* 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;
+ }
+};
+
+const char *Console::debugGetDirectionName(int16 dir) {
+ static const char *directionNames[] = {"North", "East", "South", "West"};
+ if (dir < 0 || dir > 3)
+ return "Invalid direction";
+ return directionNames[dir];
+}
+
+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..0fb9478c90
--- /dev/null
+++ b/engines/dm/console.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_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);
+
+ const char *debugGetDirectionName(int16 dir);
+
+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..8ac2aa552c
--- /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)
+ },
+ kDMSaveTargetDM21, kDMSaveFormatAmigaPC98FmTowns, kDMSavePlatformAmiga,
+ { kDMSaveTargetDM21, kDMSaveTargetEndOfList },
+ { kDMSaveFormatAmigaPC98FmTowns, kDMSaveFormatEndOfList},
+ { kDMSavePlatformAcceptAny}
+ },
+ {
+ {"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),
+ },
+ kDMSaveTargetDM21, kDMSaveFormatAmigaPC98FmTowns, kDMSavePlatformAtariSt,
+ { kDMSaveTargetDM21, kDMSaveTargetEndOfList},
+ { kDMSaveFormatAmigaPC98FmTowns, kDMSaveFormatEndOfList},
+ { kDMSavePlatformAcceptAny }
+ },
+
+ {
+ AD_TABLE_END_MARKER, kDMSaveTargetNone, kDMSaveFormatNone, kDMSavePlatformNone,
+ {kDMSaveTargetNone}, {kDMSaveFormatNone}, {kDMSavePlatformNone}
+ }
+};
+
+
+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..47888090f9
--- /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 = kDMDialogChoice1;
+ }
+ } 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..2b592f6c71
--- /dev/null
+++ b/engines/dm/dialog.h
@@ -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/)
+*/
+
+#ifndef DM_DIALOG_H
+#define DM_DIALOG_H
+
+#include "dm/dm.h"
+
+namespace DM {
+
+enum DialogCommand {
+ kDMDialogCommandSetViewport = 0,
+ kDMDialogCommandSetScreen = 1,
+ kDMDialogCommandSetUnknown = 2
+};
+
+enum DialogChoice {
+ kDMDialogChoiceNone = 0,
+ kDMDialogChoice1 = 1,
+ kDMDialogChoice2 = 2,
+ kDMDialogChoice3 = 3,
+ kDMDialogChoice4 = 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..d0ffd6f8a2
--- /dev/null
+++ b/engines/dm/dm.cpp
@@ -0,0 +1,1030 @@
+/* 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 {
+Direction DMEngine::turnDirRight(int16 dir) {
+ Direction result = (Direction)((dir + 1) & 3);
+ return result;
+}
+
+Direction DMEngine::returnOppositeDir(int16 dir) {
+ Direction result = (Direction)((dir + 2) & 3);
+ return result;
+}
+
+Direction DMEngine::turnDirLeft(int16 dir) {
+ Direction result = (Direction)((dir + 3) & 3);
+ return result;
+}
+
+bool DMEngine::isOrientedWestEast(int16 dir) {
+ return dir & 1;
+}
+
+uint16 DMEngine::normalizeModulo4(int16 dir) {
+ return dir & 3;
+}
+
+int32 DMEngine::filterTime(int32 mapTime) {
+ return mapTime & 0x00FFFFFF;
+}
+
+int32 DMEngine::setMapAndTime(int32 &mapTime, uint32 map, uint32 time) {
+ return (mapTime) = ((time) | (((long)(map)) << 24));
+}
+
+uint16 DMEngine::getMap(int32 mapTime) {
+ return ((uint16)((mapTime) >> 24));
+}
+
+Thing DMEngine::thingWithNewCell(Thing thing, int16 cell) {
+ return Thing(((thing.toUint16()) & 0x3FFF) | ((cell) << 14));
+}
+
+int16 DMEngine::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 = kDMMapIndexNone;
+ _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) != kDMLoadgameFailure) {
+ _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) != kDMLoadgameSuccess);
+
+ _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 != kDMMapIndexNone) {
+ processNewPartyMap(_newPartyMapIndex);
+ _moveSens->getMoveResult(Thing::_party, kM1_MapXNotOnASquare, 0, _dungeonMan->_partyMapX, _dungeonMan->_partyMapY);
+ _newPartyMapIndex = kDMMapIndexNone;
+ _eventMan->discardAllInput();
+ }
+ _timeline->processTimeline();
+
+ if (_newPartyMapIndex == kDMMapIndexNone)
+ 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) != kDMLoadgameFailure) {
+ 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 = kDMMapIndexEntrance;
+ _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(kDMDirSouth, 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() == kDMThingTypeExplosion) {
+ 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() == kDMstringTypeText)
+ 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..6dfc2d4f97
--- /dev/null
+++ b/engines/dm/dm.h
@@ -0,0 +1,328 @@
+/* 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 {
+ kDMSaveFormatAcceptAny = -1,
+ kDMSaveFormatEndOfList = 0,
+ kDMSaveFormatNone = 0,
+ kDMSaveFormatAtari = 1,
+ kDMSaveFormatAmigaPC98FmTowns = 2,
+ kCSBSaveFormatAtari = 2,
+ kDMSaveFormatAppleIIgs = 3,
+ kDMSaveFormatAmiga36PC = 5,
+ kCSBSaveFormatAmigaPC98FmTowns = 5,
+ kDMSaveFormatTotal
+};
+
+enum OriginalSavePlatform {
+ kDMSavePlatformAcceptAny = -1,
+ kDMSavePlatformEndOfList = 0,
+ kDMSavePlatformNone = 0,
+ kDMSavePlatformAtariSt = 1, // @ C1_PLATFORM_ATARI_ST
+ kDMSavePlatformAppleIIgs = 2, // @ C2_PLATFORM_APPLE_IIGS
+ kDMSavePlatformAmiga = 3, // @ C3_PLATFORM_AMIGA
+ kDMSavePlatformPC98 = 5, // @ C5_PLATFORM_PC98
+ kDMSavePlatformX68000 = 6, // @ C6_PLATFORM_X68000
+ kDMSavePlatformFmTownsEN = 7, // @ C7_PLATFORM_FM_TOWNS_EN
+ kDMSavePlatformFmTownsJP = 8, // @ C8_PLATFORM_FM_TOWNS_JP
+ kDMSavePlatformPC = 9, // @ C9_PLATFORM_PC
+ kDMSavePlatformTotal
+};
+
+enum SaveTarget {
+ kDMSaveTargetAcceptAny = -1,
+ kDMSaveTargetEndOfList = 0,
+ kDMSaveTargetNone = 0,
+ kDMSaveTargetDM21 = 1,
+ kDMSaveTargetTotal
+};
+
+enum Direction {
+ kDMDirNorth = 0,
+ kDMDirEast = 1,
+ kDMDirSouth = 2,
+ kDMDirWest = 3
+};
+
+enum ThingType {
+ kDMThingTypeParty = -1, // @ CM1_THING_TYPE_PARTY
+ kDMThingTypeDoor = 0, // @ C00_THING_TYPE_DOOR
+ kDMThingTypeTeleporter = 1, // @ C01_THING_TYPE_TELEPORTER
+ kDMstringTypeText = 2, // @ C02_THING_TYPE_TEXTSTRING
+ kDMThingTypeSensor = 3, // @ C03_THING_TYPE_SENSOR
+ kDMThingTypeGroup = 4, // @ C04_THING_TYPE_GROUP
+ kDMThingTypeWeapon = 5, // @ C05_THING_TYPE_WEAPON
+ kDMThingTypeArmour = 6, // @ C06_THING_TYPE_ARMOUR
+ kDMThingTypeScroll = 7, // @ C07_THING_TYPE_SCROLL
+ kDMThingTypePotion = 8, // @ C08_THING_TYPE_POTION
+ kDMThingTypeContainer = 9, // @ C09_THING_TYPE_CONTAINER
+ kDMThingTypeJunk = 10, // @ C10_THING_TYPE_JUNK
+ kDMThingTypeProjectile = 14, // @ C14_THING_TYPE_PROJECTILE
+ kDMThingTypeExplosion = 15, // @ C15_THING_TYPE_EXPLOSION
+ kDMThingTypeTotal = 16 // +1 than the last (explosionThingType)
+}; // @ C[00..15]_THING_TYPE_...
+
+enum Cell {
+ kDMCellAny = -1, // @ CM1_CELL_ANY
+ kDMCellNorthWest = 0, // @ C00_CELL_NORTHWEST
+ kDMCellNorthEast = 1, // @ C01_CELL_NORTHEAST
+ kDMCellSouthEast = 2, // @ C02_CELL_SOUTHEAST
+ kDMCellSouthWest = 3 // @ C03_CELL_SOUTHWEST
+};
+
+enum GameMode {
+ k0_modeLoadSavedGame = 0, // @ C000_MODE_LOAD_SAVED_GAME
+ k1_modeLoadDungeon = 1, // @ C001_MODE_LOAD_DUNGEON
+ k99_modeWaitingOnEntrance = 99, // @ C099_MODE_WAITING_ON_ENTRANCE
+ k202_modeEntranceDrawCredits = 202 // @ C202_MODE_ENTRANCE_DRAW_CREDITS
+};
+
+enum LoadgameResult {
+ kDMLoadgameFailure = -1, // @ CM1_LOAD_GAME_FAILURE
+ kDMLoadgameSuccess = 1// @ C01_LOAD_GAME_SUCCESS
+};
+
+enum MapIndice {
+ kDMMapIndexNone = -1, // @ CM1_MAP_INDEX_NONE
+ kDMMapIndexEntrance = 255 // @ C255_MAP_INDEX_ENTRANCE
+};
+
+struct DMADGameDescription {
+ ADGameDescription _desc;
+
+ SaveTarget _saveTargetToWrite;
+ OriginalSaveFormat _origSaveFormatToWrite;
+ OriginalSavePlatform _origPlatformToWrite;
+
+ SaveTarget _saveTargetToAccept[kDMSaveTargetTotal + 1];
+ OriginalSaveFormat _saveFormatToAccept[kDMSaveFormatTotal + 1];
+ OriginalSavePlatform _origPlatformToAccept[kDMSavePlatformTotal + 1];
+};
+
+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
+
+#define setFlag(val, mask) ((val) |= (mask))
+#define getFlag(val, mask) ((val) & (mask))
+#define clearFlag(val, mask) ((val) &= (~(mask))) // @ M09_CLEAR
+
+// Note: F0026_MAIN_GetBoundedValue<T> has been replaced by CLIP<T>
+
+#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))
+
+struct SaveGameHeader {
+ byte _version;
+ SaveStateDescriptor _descr;
+};
+
+class DMEngine : public Engine {
+private:
+ 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
+ void fuseSequenceUpdate(); // @ F0445_STARTEND_FuseSequenceUpdate
+ void processEntrance(); // @ F0441_STARTEND_ProcessEntrance
+ void openEntranceDoors(); // @ F0438_STARTEND_OpenEntranceDoors
+ void drawTittle(); // @ F0437_STARTEND_DrawTitle
+
+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
+ LoadgameResult loadgame(int16 slot); // @ F0435_STARTEND_LoadGame_CPSF
+ void endGame(bool doNotDrawCreditsOnly); // @ F0444_STARTEND_Endgame
+
+ void entranceDrawCredits();
+ void fuseSequence(); // @ F0446_STARTEND_FuseSequence
+ Common::Language getGameLanguage();
+
+ Direction turnDirRight(int16 dir); // @ M17_NEXT
+ Direction turnDirLeft(int16 dir); // @ M19_PREVIOUS
+ Direction returnOppositeDir(int16 dir); // @ M18_OPPOSITE
+ bool isOrientedWestEast(int16 dir); // @ M16_IS_ORIENTED_WEST_EAST
+ uint16 normalizeModulo4(int16 dir); // @ 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
+
+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..f91ee69af3
--- /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;
+ dir = _vm->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 = kDMMapIndexNone;
+ _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 < kDMThingTypeTotal; ++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 = kDMThingTypeDoor; thingType < kDMThingTypeTotal; ++thingType) {
+ uint16 thingCount = _dungeonFileHeader._thingCounts[thingType];
+ if (_vm->_newGameFl)
+ _dungeonFileHeader._thingCounts[thingType] = MIN((thingType == kDMThingTypeExplosion) ? 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 == kDMThingTypeGroup || thingType == kDMThingTypeProjectile) && !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 == kDMThingTypeGroup) || thingType >= kDMThingTypeProjectile)
+ _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 kDMDirNorth:
+ leftRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0004_WallEastRandOrnAllowed);
+ frontRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0002_WallSouthRandOrnAllowed);
+ rightRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0001_WallWestRandOrnAllowed);
+ break;
+ case kDMDirEast:
+ leftRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0002_WallSouthRandOrnAllowed);
+ frontRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0001_WallWestRandOrnAllowed);
+ rightRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0008_WallNorthRandOrnAllowed);
+ break;
+ case kDMDirSouth:
+ leftRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0001_WallWestRandOrnAllowed);
+ frontRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0008_WallNorthRandOrnAllowed);
+ rightRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, k0x0004_WallEastRandOrnAllowed);
+ break;
+ case kDMDirWest:
+ 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() <= kDMThingTypeSensor)) {
+ ThingType curThingType = curThing.getType();
+ int16 AL0310_i_SideIndex = _vm->normalizeModulo4(curThing.getCell() - dir);
+ if (AL0310_i_SideIndex) { /* Invisible on the back wall if 0 */
+ Sensor *curSensor = (Sensor *)getThingData(curThing);
+ if (curThingType == kDMstringTypeText) {
+ 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() <= kDMThingTypeSensor)) {
+ if (curThing.getType() == kDMThingTypeSensor) {
+ 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)) == _vm->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() <= kDMThingTypeSensor))
+ curThing = getNextThing(curThing);
+ break;
+ case k4_DoorElemType:
+ if (bool((getFlag(AL0307_uc_Square, (byte) k0x0008_DoorNorthSouthOrient) >> 3)) == _vm->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() <= kDMThingTypeSensor))
+ 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 * (_vm->normalizeModulo4(++dir) + 1), 30);
+ aspectArray[k3_FrontWallOrnOrdAspect] = getRandomOrnOrdinal(frontAllowed, randomWallOrnamentCount, mapX, mapY * (_vm->normalizeModulo4(++dir) + 1), 30);
+ aspectArray[k4_LeftWallOrnOrdAspect] = getRandomOrnOrdinal(rightAllowed, randomWallOrnamentCount, mapX, mapY-- * (_vm->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[kDMstringTypeText] + thing.getIndex() * _thingDataWordCount[kDMstringTypeText]);
+ 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 | kDMThingTypeJunk)) {
+ thingType = kDMThingTypeJunk;
+ } else if (thingType == kDMThingTypeJunk)
+ 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 kDMThingTypeWeapon:
+ weight = _weaponInfos[((Weapon *)junk)->getType()]._weight;
+ break;
+ case kDMThingTypeArmour:
+ weight = _armourInfos[((Armour *)junk)->getType()]._weight;
+ break;
+ case kDMThingTypeJunk:
+ weight = junkInfo[junk->getType()];
+ if (junk->getType() == k1_JunkTypeWaterskin)
+ weight += junk->getChargeCount() << 1;
+
+ break;
+ case kDMThingTypeContainer:
+ weight = 50;
+ thing = ((Container *)junk)->getSlot();
+ while (thing != Thing::_endOfList) {
+ weight += getObjectWeight(thing);
+ thing = getNextThing(thing);
+ }
+ break;
+ case kDMThingTypePotion:
+ if (((Potion *)junk)->getType() == k20_PotionTypeEmptyFlask)
+ weight = 1;
+ else
+ weight = 3;
+ break;
+ case kDMThingTypeScroll:
+ 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 kDMThingTypeScroll:
+ return k0_ObjectInfoIndexFirstScroll;
+ case kDMThingTypeContainer:
+ return k1_ObjectInfoIndexFirstContainer + Container(rawType).getType();
+ case kDMThingTypeJunk:
+ return k127_ObjectInfoIndexFirstJunk + Junk(rawType).getType();
+ case kDMThingTypeWeapon:
+ return k23_ObjectInfoIndexFirstWeapon + Weapon(rawType).getType();
+ case kDMThingTypeArmour:
+ return k69_ObjectInfoIndexFirstArmour + Armour(rawType).getType();
+ case kDMThingTypePotion:
+ 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 == kDMThingTypeExplosion) {
+ 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 == kDMThingTypeWeapon) {
+ 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 == kDMMapIndexEntrance)
+ return kDMMapIndexNone;
+
+ 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 kDMMapIndexNone;
+}
+
+Thing DungeonMan::getSquareFirstObject(int16 mapX, int16 mapY) {
+ Thing thing = getSquareFirstThing(mapX, mapY);
+ while ((thing != Thing::_endOfList) && (thing.getType() < kDMThingTypeGroup))
+ 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 == kDMThingTypeExplosion)
+ 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 == kDMThingTypeSensor) {
+ 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 kDMThingTypeGroup:
+ if (((Group *)squareThingData)->getDoNotDiscard())
+ continue;
+ case kDMThingTypeProjectile:
+ setCurrentMap(mapIndex);
+ if (thingType == kDMThingTypeGroup) {
+ _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 kDMThingTypeArmour:
+ if (((Armour *)squareThingData)->getDoNotDiscard())
+ continue;
+
+ setCurrentMap(mapIndex);
+ _vm->_moveSens->getMoveResult(squareThing, currMapX, currMapY, kM1_MapXNotOnASquare, 0);
+ break;
+ case kDMThingTypeWeapon:
+ if (((Weapon *)squareThingData)->getDoNotDiscard())
+ continue;
+
+ setCurrentMap(mapIndex);
+ _vm->_moveSens->getMoveResult(squareThing, currMapX, currMapY, kM1_MapXNotOnASquare, 0);
+ break;
+ case kDMThingTypeJunk:
+ if (((Junk *)squareThingData)->getDoNotDiscard())
+ continue;
+
+ setCurrentMap(mapIndex);
+ _vm->_moveSens->getMoveResult(squareThing, currMapX, currMapY, kM1_MapXNotOnASquare, 0);
+ break;
+ case kDMThingTypePotion:
+ 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(_vm->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[kDMDirEast];
+ mapY = mapY + _vm->_dirIntoStepCountNorth[kDMDirEast];
+ } else {
+ mapX = mapX + _vm->_dirIntoStepCountEast[kDMDirNorth];
+ mapY = mapY + _vm->_dirIntoStepCountNorth[kDMDirNorth];
+ }
+ 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 = kDMThingTypeWeapon;
+ if ((iconIndex >= kDMIconIndiceWeaponTorchUnlit) && (iconIndex <= kDMIconIndiceWeaponTorchLit))
+ iconIndex = kDMIconIndiceWeaponTorchUnlit;
+
+ int16 junkType;
+
+ switch (iconIndex) {
+ case kDMIconIndiceWeaponRock:
+ junkType = k30_WeaponTypeRock;
+ break;
+ case kDMIconIndiceJunkBoulder:
+ junkType = k25_JunkTypeBoulder;
+ thingType = kDMThingTypeJunk;
+ 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..19db589aaa
--- /dev/null
+++ b/engines/dm/eventman.cpp
@@ -0,0 +1,1673 @@
+/* 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 == (_vm->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(_vm->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;
+
+ Square curSquare = _vm->_dungeonMan->getSquare(partyMapX, partyMapY);
+ bool isStairsSquare = (curSquare.getType() == k3_StairsElemType);
+ if (isStairsSquare && (movementArrowIdx == 2)) { /* If moving backward while in stairs */
+ commandTakeStairs(getFlag(curSquare.toByte(), k0x0004_StairsUp));
+ return;
+ }
+
+ _vm->_dungeonMan->mapCoordsAfterRelMovement(_vm->_dungeonMan->_partyDir, movementArrowToStepForwardCount[movementArrowIdx], movementArrowToSepRightCount[movementArrowIdx], partyMapX, partyMapY);
+ curSquare = _vm->_dungeonMan->getSquare(partyMapX, partyMapY);
+
+ bool isMovementBlocked = false;
+ SquareType partySquareType = curSquare.getType();
+ switch (partySquareType){
+ case k0_ElementTypeWall:
+ isMovementBlocked = true;
+ break;
+ case k3_ElementTypeStairs: {
+ _vm->_moveSens->getMoveResult(Thing::_party, _vm->_dungeonMan->_partyMapX, _vm->_dungeonMan->_partyMapY, kM1_MapXNotOnASquare, 0);
+ _vm->_dungeonMan->_partyMapX = partyMapX;
+ _vm->_dungeonMan->_partyMapY = partyMapY;
+ byte stairState = curSquare.toByte();
+ commandTakeStairs(getFlag(stairState, k0x0004_StairsUp));
+ return;
+ }
+ case k4_DoorElemType: {
+ byte doorState = curSquare.getDoorState();
+ isMovementBlocked = (doorState != k0_doorState_OPEN) && (doorState != k1_doorState_FOURTH) && (doorState != k5_doorState_DESTROYED);
+ }
+ break;
+ case k6_ElementTypeFakeWall: {
+ byte wallState = curSquare.toByte();
+ isMovementBlocked = (!getFlag(wallState, k0x0004_FakeWallOpen) && !getFlag(wallState, k0x0001_FakeWallImaginary));
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (_vm->_championMan->_partyChampionCount) {
+ if (isMovementBlocked) {
+ movementArrowIdx += (_vm->_dungeonMan->_partyDir + 2);
+ int16 firstDamagedChampionIndex = _vm->_championMan->getTargetChampionIndex(partyMapX, partyMapY, _vm->normalizeModulo4(movementArrowIdx));
+ int16 secondDamagedChampionIndex = _vm->_championMan->getTargetChampionIndex(partyMapX, partyMapY, _vm->turnDirRight(movementArrowIdx));
+ int16 damage = _vm->_championMan->addPendingDamageAndWounds_getDamage(firstDamagedChampionIndex, 1, kDMWoundTorso | kDMWoundLegs, kDMAttackTypeSelf);
+ if (firstDamagedChampionIndex != secondDamagedChampionIndex)
+ damage |= _vm->_championMan->addPendingDamageAndWounds_getDamage(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, _vm->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() == kDMThingTypeSensor) {
+ ((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), _vm->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 = _vm->normalizeModulo4(_vm->_dungeonMan->_partyDir + viewCell);
+ Thing removedThing = _vm->_championMan->getObjectRemovedFromLeaderHand();
+ _vm->_moveSens->getMoveResult(_vm->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;
+ _vm->setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + 1);
+ newEvent._type = k13_TMEventTypeViAltarRebirth;
+ newEvent._priority = removedJunk->getChargeCount();
+ newEvent._Bu._location._mapX = mapX;
+ newEvent._Bu._location._mapY = mapY;
+ newEvent._Cu.A._cell = currCell;
+ newEvent._Cu.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(_vm->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(_vm->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(_vm->normalizeModulo4(champIconIndex + _vm->_dungeonMan->_partyDir));
+ if (championIndex >= 0) {
+ _vm->_championMan->_champions[championIndex]._cell = (ViewCell)_vm->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)_vm->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..13dd79d7a0
--- /dev/null
+++ b/engines/dm/gfx.cpp
@@ -0,0 +1,3865 @@
+/* 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;
+}
+
+uint16 DisplayMan::bitmapByteCount(uint16 pixelWidth, uint16 height) {
+ return pixelWidth / 2 * height;
+}
+
+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[kDMThingTypeDoor]) + 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[kDMThingTypeDoor])[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[kDMThingTypeDoor])[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[kDMThingTypeDoor])[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[kDMThingTypeDoor])[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 != kDMMapIndexEntrance) ? 1 : 0);
+ if (_vm->_dungeonMan->_partyMapIndex != kDMMapIndexEntrance)
+ 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_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 creatureGraphicInfoRed;
+ 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 = _vm->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 = _vm->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()) == kDMThingTypeGroup) {
+ groupThing = thingParam;
+ continue;
+ }
+
+ if (AL_4_thingType == kDMThingTypeProjectile) {
+ squareHasProjectile = true;
+ continue;
+ }
+
+ if (AL_4_thingType == kDMThingTypeExplosion) {
+ 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 = _vm->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 == _vm->turnDirLeft(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;
+ 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(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(creatureGraphicInfoRed, k0x0010_CreatureInfoGraphicMaskBack) && (creatureDirectionDelta == 0);
+ useCreatureAttackBitmap = !useCreatureBackBitmap;
+ if (useCreatureAttackBitmap && getFlag(creatureAspectInt, k0x0080_MaskActiveGroupIsAttacking) && getFlag(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(creatureGraphicInfoRed, k0x0008_CreatureInfoGraphicMaskSide)) {
+ AL_4_nativeBitmapIndex++; /* If the creature has a side image, it preceeds the attack image */
+ derivedBitmapIndex += 2;
+ }
+
+ if (getFlag(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(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(creatureGraphicInfoRed, k0x0004_CreatureInfoGraphicMaskFlipNonAttack) && getFlag(creatureAspectInt, k0x0040_MaskActiveGroupFlipBitmap);
+ if (useFlippedHorizontallyCreatureFrontImage) {
+ derivedBitmapIndex += 2;
+ if (getFlag(creatureGraphicInfoRed, k0x0008_CreatureInfoGraphicMaskSide))
+ derivedBitmapIndex += 2;
+
+ if (getFlag(creatureGraphicInfoRed, k0x0010_CreatureInfoGraphicMaskBack))
+ derivedBitmapIndex += 2;
+
+ if (getFlag(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(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(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(creatureGraphicInfoRed, k0x0100_CreatureInfoGraphicMaskSpecialD2FrontIsFlipped)) ||
+ (useFlippedHorizontallyCreatureFrontImage && getFlag(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 = CLIP(0, AL_4_xPos + byteWidth, 223);
+
+ if (!boxByteGreen._x2)
+ goto T0115126_CreatureNotVisible;
+ int16 AL_0_creaturePosX;
+ boxByteGreen._x1 = CLIP(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() == kDMThingTypeProjectile) && (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 (_vm->isOrientedWestEast(Direction(projectileDirection = _vm->_timeline->_events[projectile->_eventIndex]._Cu._projectile.getDir())) != _vm->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 = (_vm->turnDirRight(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() == kDMThingTypeExplosion) {
+ 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 == _vm->turnDirLeft(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 = CLIP(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..f9aa3823aa
--- /dev/null
+++ b/engines/dm/gfx.h
@@ -0,0 +1,833 @@
+/* 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();
+ uint16 bitmapByteCount(uint16 pixelWidth, uint16 height); // @ M75_BITMAP_BYTE_COUNT
+};
+
+}
+
+#endif
diff --git a/engines/dm/group.cpp b/engines/dm/group.cpp
new file mode 100644
index 0000000000..6679599e8e
--- /dev/null
+++ b/engines/dm/group.cpp
@@ -0,0 +1,2039 @@
+/* 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;
+}
+
+uint16 GroupMan::toggleFlag(uint16& val, uint16 mask) {
+ return val ^= mask;
+}
+
+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 = _vm->turnDirLeft(cell);
+
+ do {
+ byte creatureCell = getCreatureValue(groupCells, creatureIndex);
+ if (creatureCell == cell || creatureCell == _vm->turnDirRight(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 = _vm->thingWithNewCell(currentThing, _vm->getRandomNumber(4));
+ if ((currentThing).getType() == kDMThingTypeWeapon) {
+ 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 = kDMThingTypeJunk;
+ currFixedPossession -= k127_ObjectInfoIndexFirstJunk;
+ } else if (currFixedPossession >= k69_ObjectInfoIndexFirstArmour) {
+ currThingType = kDMThingTypeArmour;
+ currFixedPossession -= k69_ObjectInfoIndexFirstArmour;
+ } else {
+ weaponDropped = true;
+ currThingType = kDMThingTypeWeapon;
+ 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 = _vm->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 kDMDirNorth;
+
+ return kDMDirSouth;
+ }
+ 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 kDMDirWest;
+
+ return kDMDirEast;
+ }
+
+ int16 curDirection = kDMDirNorth;
+ for (;;) {
+ if (isDestVisibleFromSource(curDirection, srcMapX, srcMapY, destMapX, destMapY)) {
+ _vm->_projexpl->_secondaryDirToOrFromParty = _vm->turnDirRight(curDirection);
+ if (!isDestVisibleFromSource(_vm->_projexpl->_secondaryDirToOrFromParty, srcMapX, srcMapY, destMapX, destMapY)) {
+ _vm->_projexpl->_secondaryDirToOrFromParty = _vm->turnDirLeft(curDirection);
+ if ((curDirection != kDMDirNorth) || !isDestVisibleFromSource(_vm->_projexpl->_secondaryDirToOrFromParty, srcMapX, srcMapY, destMapX, destMapY)) {
+ _vm->_projexpl->_secondaryDirToOrFromParty = _vm->turnDirRight((_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 kDMDirSouth:
+ SWAP(srcMapX, destMapY);
+ SWAP(destMapX, srcMapY);
+ break;
+ case kDMDirEast:
+ SWAP(srcMapX, destMapX);
+ SWAP(destMapY, srcMapY);
+ break;
+ case kDMDirNorth:
+ 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;
+ _vm->setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_currMapIndex, _vm->_gameTime + ticks);
+ newEvent._type = k2_TMEventTypeDoorDestruction;
+ newEvent._priority = 0;
+ newEvent._Bu._location._mapX = mapX;
+ newEvent._Bu._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() != kDMThingTypeGroup))
+ 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 ((_vm->getMap(curEvent->_mapTime) == _vm->_dungeonMan->_currMapIndex) &&
+ (curEvent->_Bu._location._mapX == mapX) &&
+ (curEvent->_Bu._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 ((_vm->getMap(curEvent->_mapTime) == _vm->_dungeonMan->_currMapIndex) &&
+ (curEventType > k29_TMEventTypeGroupReactionDangerOnSquare - 1) && (curEventType < k41_TMEventTypeUpdateBehaviour_3 + 1) &&
+ (curEvent->_Bu._location._mapX == mapX) && (curEvent->_Bu._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;
+ _vm->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._Bu._location._mapX = eventMapX;
+ nextEvent._Bu._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._Bu._location._mapX = _vm->_moveSens->_moveResultMapX;
+ nextEvent._Bu._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._Cu._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 (_vm->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._Bu._location._mapX = _vm->_moveSens->_moveResultMapX;
+ nextEvent._Bu._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 = _vm->turnDirRight(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 = _vm->turnDirRight(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 = _vm->returnOppositeDir((Direction)AL0446_i_Direction), false) ||
+ (!_vm->getRandomNumber(4) && isMovementPossible(&creatureInfo, eventMapX, eventMapY, AL0446_i_Direction = _vm->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 = _vm->returnOppositeDir((Direction)AL0446_i_Direction))) ||
+ ((_fluxCageCount >= 2) && isArchenemyDoubleMovementPossible(&creatureInfo, eventMapX, eventMapY, AL0446_i_Direction = _vm->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 (_vm->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 = _vm->returnOppositeDir((Direction)getDirsWhereDestIsVisibleFromSource(eventMapX, eventMapY, AL0450_i_TargetMapX, AL0451_i_TargetMapY));
+ _vm->_projexpl->_secondaryDirToOrFromParty = _vm->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 = _vm->normalizeModulo4(_vm->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 = _vm->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 != _vm->turnDirRight(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++;
+
+ AL0446_i_Cell = _vm->normalizeModulo4(AL0446_i_Cell);
+ if (!getCreatureOrdinalInCell(curGroup, AL0446_i_Cell) ||
+ (_vm->getRandomNumber(2) && !getCreatureOrdinalInCell(curGroup, AL0446_i_Cell = _vm->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 = _vm->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() == kDMThingTypeExplosion) {
+ 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] = kDMDirNorth;
+ } 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 = _vm->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 (_vm->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 (_vm->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[kDMThingTypeGroup])[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 (_vm->normalizeModulo4(getCreatureValue(groupDirections, creatureIndex) - dir) == 2) { /* If current and new direction are opposites then change direction only one step at a time */
+ dir = _vm->turnDirRight((_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)_vm->filterTime(event->_mapTime)) {
+ event->_type -= 5;
+ event->_Cu._ticks = _vm->filterTime(event->_mapTime) - time;
+ M32_setTime(event->_mapTime, time);
+ } else
+ event->_Cu._ticks = time - _vm->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 = kDMDirNorth; direction <= kDMDirWest; 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, CLIP<byte>(20, kineticEnergy, 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 = _vm->turnDirRight(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 = _vm->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;
+ _vm->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._Cu._ticks = 0;
+ nextEvent._Bu._location._mapX = mapX;
+ nextEvent._Bu._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[kDMThingTypeGroup] +
+ _vm->_dungeonMan->_thingDataWordCount[kDMThingTypeGroup] * 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[kDMThingTypeGroup])[activeGroup->_groupThingIndex];
+ _currActiveGroupCount--;
+ group->_cells = activeGroup->_cells;
+ group->setDir(_vm->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() == kDMThingTypeGroup) {
+ 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(kDMThingTypeGroup);
+ 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(kDMThingTypeExplosion);
+ if (unusedThing == Thing::_none)
+ return;
+
+ _vm->_dungeonMan->linkThingToList(unusedThing, Thing(0), mapX, mapY);
+ (((Explosion *)_vm->_dungeonMan->_thingData[kDMThingTypeExplosion])[unusedThing.getIndex()]).setType(k50_ExplosionType_Fluxcage);
+ TimelineEvent newEvent;
+ _vm->setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_currMapIndex, _vm->_gameTime + 100);
+ newEvent._type = k24_TMEventTypeRemoveFluxcage;
+ newEvent._priority = 0;
+ newEvent._Cu._slot = unusedThing.toUint16();
+ newEvent._Bu._location._mapX = mapX;
+ newEvent._Bu._location._mapY = mapY;
+ newEvent._Bu._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() == kDMThingTypeExplosion) && (((Explosion *)_vm->_dungeonMan->_thingData[kDMThingTypeExplosion])[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 = _vm->turnDirRight(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..3b6cf2f8aa
--- /dev/null
+++ b/engines/dm/group.h
@@ -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/)
+*/
+
+#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
+ uint16 toggleFlag(uint16 &val, uint16 mask); // @ M10_TOGGLE
+
+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..ba9aa7ef44
--- /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 kDMThingTypeContainer:
+ _panelContent = k4_PanelContentChest;
+ break;
+ case kDMThingTypeScroll:
+ _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 == kDMThingTypeScroll)
+ drawPanelScroll((Scroll *)rawThingPtr);
+ else if (thingType == kDMThingTypeContainer)
+ 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 == kDMThingTypePotion)
+ && (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 kDMThingTypeWeapon: {
+ 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 kDMThingTypeArmour: {
+ potentialAttribMask = k0x0008_DescriptionMaskCursed | k0x0004_DescriptionMaskBroken;
+ Armour *armour = (Armour *)rawThingPtr;
+ actualAttribMask = (armour->getCursed() << 3) | (armour->getBroken() << 2);
+ break;
+ }
+ case kDMThingTypePotion: {
+ potentialAttribMask = k0x0001_DescriptionMaskConsumable;
+ Potion *potion = (Potion *)rawThingPtr;
+ actualAttribMask = _vm->_dungeonMan->_objectInfos[k2_ObjectInfoIndexFirstPotion + potion->getType()].getAllowedSlots();
+ break;
+ }
+ case kDMThingTypeJunk: {
+ 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 == kDMThingTypePotion)
+ removeObjectFromLeaderHand = false;
+ else {
+ junkData->setNextThing(Thing::_none);
+ removeObjectFromLeaderHand = true;
+ }
+ _vm->_eventMan->showMouse();
+ if (removeObjectFromLeaderHand)
+ _vm->_championMan->getObjectRemovedFromLeaderHand();
+
+ if (handThingType == kDMThingTypePotion) {
+ 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;
+ _vm->setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + (adjustedPotionPower * adjustedPotionPower));
+ newEvent._priority = championIndex;
+ newEvent._Bu._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..a0b1190ae2
--- /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 {
+
+LoadgameResult DMEngine::loadgame(int16 slot) {
+ if (slot == -1 && _newGameFl == k0_modeLoadSavedGame)
+ return kDMLoadgameFailure;
+
+ 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 kDMLoadgameSuccess;
+}
+
+
+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, kDMDialogCommandSetViewport, 0, kDMDialogChoiceNone);
+
+ 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, kDMDialogCommandSetViewport, 0, kDMDialogChoiceNone);
+ }
+
+ 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..5edbeb3db7
--- /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()), CLIP(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;
+ _vm->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;
+ _vm->setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + spellTicks);
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ }
+ break;
+ case kDMSpellTypeOtherPartyShield: {
+ newEvent._type = k74_TMEventTypePartyShield;
+ newEvent._Bu._defense = spellPower;
+ if (_vm->_championMan->_party._shieldDefense > 50)
+ newEvent._Bu._defense >>= 2;
+
+ _vm->_championMan->_party._shieldDefense += newEvent._Bu._defense;
+ _vm->_timeline->refreshAllChampionStatusBoxes();
+ uint16 spellTicks = spellPower * spellPower;
+ _vm->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;
+ _vm->setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_partyMapIndex, _vm->_gameTime + spellTicks);
+ _vm->_timeline->addEventGetEventIndex(&newEvent);
+ }
+ break;
+ case kDMSpellTypeOtherZokathra: {
+ Thing unusedObject = _vm->_dungeonMan->getUnusedThing(kDMThingTypeJunk);
+ 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._Bu._lightPower = lightPower;
+ _vm->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._Bu._defense = ticks >> 5;
+ if (spellShield) {
+ newEvent._type = k77_TMEventTypeSpellShield;
+ if (_vm->_championMan->_party._spellShieldDefense > 50)
+ newEvent._Bu._defense >>= 2;
+
+ _vm->_championMan->_party._spellShieldDefense += newEvent._Bu._defense;
+ } else {
+ newEvent._type = k78_TMEventTypeFireShield;
+ if (_vm->_championMan->_party._fireShieldDefense > 50)
+ newEvent._Bu._defense >>= 2;
+
+ _vm->_championMan->_party._fireShieldDefense += newEvent._Bu._defense;
+ }
+ newEvent._priority = 0;
+ _vm->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 = _vm->turnDirRight(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 = _vm->turnDirLeft(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() != kDMThingTypeWeapon) {
+ _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;
+ _vm->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 == _vm->turnDirRight(_vm->_dungeonMan->_partyDir)) || (curChampion->_cell == (ViewCell)_vm->returnOppositeDir(_vm->_dungeonMan->_partyDir)));
+ if (actionPerformed)
+ _vm->_timeline->_events[curChampion->_enableActionEventIndex]._Bu._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 kDMThingTypeWeapon:
+ if (((Weapon *)slotActionData)->getChargeCount()) {
+ ((Weapon *)slotActionData)->setChargeCount(((Weapon *)slotActionData)->getChargeCount() - 1);
+ }
+ break;
+ case kDMThingTypeArmour:
+ if (((Armour *)slotActionData)->getChargeCount()) {
+ ((Armour *)slotActionData)->setChargeCount(((Armour *)slotActionData)->getChargeCount() - 1);
+ }
+ break;
+ case kDMThingTypeJunk:
+ 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 = _vm->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(_vm->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 kDMThingTypeWeapon:
+ return ((Weapon *)junkData)->getChargeCount();
+ case kDMThingTypeArmour:
+ return ((Armour *)junkData)->getChargeCount();
+ case kDMThingTypeJunk:
+ 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..64e3e5e657
--- /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 = kDMCellNorthWest; i < kDMCellSouthWest + 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 == kDMThingTypeSensor)
+ sensorCountToProcessPerCell[thingBeingProcessed.getCell()]++;
+ else if (thingType >= kDMThingTypeGroup)
+ 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 == kDMThingTypeSensor) {
+ 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(_vm->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(_vm->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 >= kDMThingTypeGroup)
+ break;
+ }
+ processRotationEffect();
+ return atLeastOneSensorWasTriggered;
+}
+
+bool MovesensMan::getMoveResult(Thing thing, int16 mapX, int16 mapY, int16 destMapX, int16 destMapY) {
+ ThingType thingType = kDMThingTypeParty;
+ 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 == kDMThingTypeGroup) && (_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 == kDMThingTypeGroup)
+ requiredTeleporterScope = k0x0001_TelepScopeCreatures;
+ else
+ requiredTeleporterScope = (k0x0001_TelepScopeCreatures | k0x0002_TelepScopeObjOrParty);
+
+ if (thingType == kDMThingTypeProjectile) {
+ Teleporter *L0712_ps_Teleporter = (Teleporter *)_vm->_dungeonMan->getThingData(thing);
+ _moveResultDir = (_vm->_timeline->_events[((Projectile *)L0712_ps_Teleporter)->_eventIndex])._Cu._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 != kDMThingTypeGroup))
+ 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(_vm->normalizeModulo4(_vm->_dungeonMan->_partyDir + teleporter->getRotation()));
+ } else {
+ if (thingType == kDMThingTypeGroup) {
+ if (teleporter->isAudible())
+ _vm->_sound->requestPlay(k17_soundBUZZ, destMapX, destMapY, k1_soundModePlayIfPrioritized);
+
+ moveGroupResult = getTeleporterRotatedGroupResult(teleporter, thing, mapIndexSource);
+ } else {
+ if (thingType == kDMThingTypeProjectile)
+ thing = getTeleporterRotatedProjectileThing(teleporter, thing);
+ else if (!(teleporter->getAbsoluteRotation()) && (mapX != -2))
+ thing = _vm->thingWithNewCell(thing, _vm->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 == kDMThingTypeGroup) {
+ _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 != kDMThingTypeProjectile)) {
+ 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 = _vm->returnOppositeDir((Direction)direction);
+ uint16 thingCell = thing.getCell();
+ thingCell = _vm->normalizeModulo4((((thingCell - direction + 1) & 0x0002) >> 1) + direction);
+ thing = _vm->thingWithNewCell(thing, thingCell);
+ } else
+ break;
+ }
+ }
+ if ((thingType == kDMThingTypeGroup) && (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 != kDMThingTypeProjectile))
+ 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 == kDMThingTypeGroup) {
+ _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[kDMThingTypeGroup])[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 == kDMThingTypeProjectile) /* 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 == kDMThingTypeGroup)
+ retVal = getFlag(_vm->_dungeonMan->getCreatureAttributes(thing), k0x0020_MaskCreatureInfo_levitation);
+ else if ((thingType == kDMThingTypeProjectile) || (thingType == kDMThingTypeExplosion))
+ // 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 = kDMCellNorthWest; cellIdx < kDMCellSouthWest + 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 = kDMCellNorthWest; cellIdx < kDMCellSouthWest + 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 = _vm->turnDirRight(primaryDirection);
+ for (int16 i = 0; i < 4; ++i)
+ intermediaryChampionOrCreatureOrdinalInCell[i] = 0;
+
+ intermediaryChampionOrCreatureOrdinalInCell[_vm->turnDirLeft(primaryDirection)] = championOrCreatureOrdinalInCell[primaryDirection];
+ if (intermediaryChampionOrCreatureOrdinalInCell[_vm->turnDirLeft(primaryDirection)])
+ checkDestinationSquareProjectileImpacts = true;
+
+ intermediaryChampionOrCreatureOrdinalInCell[_vm->turnDirRight(secondaryDirection)] = championOrCreatureOrdinalInCell[secondaryDirection];
+ if (intermediaryChampionOrCreatureOrdinalInCell[_vm->turnDirRight(secondaryDirection)])
+ checkDestinationSquareProjectileImpacts = true;
+
+ if (!championOrCreatureOrdinalInCell[primaryDirection])
+ championOrCreatureOrdinalInCell[primaryDirection] = championOrCreatureOrdinalInCell[_vm->turnDirLeft(primaryDirection)];
+
+ if (!championOrCreatureOrdinalInCell[secondaryDirection])
+ championOrCreatureOrdinalInCell[secondaryDirection] = championOrCreatureOrdinalInCell[_vm->turnDirRight(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() == kDMThingTypeProjectile) &&
+ (_vm->_timeline->_events[(((Projectile *)_vm->_dungeonMan->_thingData[kDMThingTypeProjectile])[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;
+ _vm->setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_currMapIndex, time);
+ newEvent._type = type;
+ newEvent._priority = 0;
+ newEvent._Bu._location._mapX = mapX;
+ newEvent._Bu._location._mapY = mapY;
+ newEvent._Cu.A._cell = cell;
+ newEvent._Cu.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 = _vm->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 = _vm->normalizeModulo4(4 + updatedGroupDirections - groupDirections);
+ for (int16 creatureIdx = 0; creatureIdx <= group->getCount(); creatureIdx++) {
+ updatedGroupDirections = _vm->_groupMan->getGroupValueUpdatedWithCreatureValue(updatedGroupDirections, creatureIdx, absoluteRotation ? rotation : _vm->normalizeModulo4(groupDirections + rotation));
+ if (creatureSize == k0_MaskCreatureSizeQuarter) {
+ relativeRotation = absoluteRotation ? 1 : 0;
+ if (relativeRotation)
+ relativeRotation = rotation;
+ }
+ if (relativeRotation)
+ updatedGroupCells = _vm->_groupMan->getGroupValueUpdatedWithCreatureValue(updatedGroupCells, creatureIdx, _vm->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 = _vm->normalizeModulo4(updatedDirection + rotation);
+ projectileThing = _vm->thingWithNewCell(projectileThing, _vm->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 = kDMThingTypeParty;
+ objectType = kDMIconIndiceNone;
+ }
+
+ if ((!addThing) && (thingType != kDMThingTypeParty))
+ _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 = kDMCellAny; // 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 == kDMCellAny) {
+ while (curThing != Thing::_endOfList) {
+ uint16 curThingType = curThing.getType();
+ if (curThingType == kDMThingTypeGroup)
+ squareContainsGroup = true;
+ else if ((curThingType == kDMstringTypeText) && (thingType == kDMThingTypeParty) && addThing && !partySquare) {
+ _vm->_dungeonMan->decodeText(_vm->_stringBuildBuffer, curThing, k1_TextTypeMessage);
+ _vm->_textMan->printMessage(k15_ColorWhite, _vm->_stringBuildBuffer);
+ } else if ((curThingType > kDMThingTypeGroup) && (curThingType < kDMThingTypeProjectile)) {
+ 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() > kDMThingTypeGroup)) {
+ squareContainsObject = true;
+ squareContainsThingOfSameType |= (_vm->_objectMan->getObjectType(curThing) == objectType);
+ squareContainsThingOfDifferentType |= (_vm->_objectMan->getObjectType(curThing) != objectType);
+ }
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+ }
+ if (addThing && (thingType != kDMThingTypeParty))
+ _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 == kDMThingTypeSensor) {
+ Sensor *curSensor = (Sensor *)_vm->_dungeonMan->getThingData(curThing);
+ if (curSensor->getType() == k0_SensorDisabled)
+ continue;
+
+ int16 curSensorData = curSensor->getData();
+ bool triggerSensor = addThing;
+ if (sensorTriggeredCell == kDMCellAny) {
+ 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 > kDMThingTypeGroup) || partySquare || squareContainsGroup)
+ continue;
+ break;
+ case k3_SensorFloorParty:
+ if ((thingType != kDMThingTypeParty) || (_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 != kDMThingTypeParty) || (curSquare.getType() != k3_StairsElemType))
+ continue;
+ break;
+ case k6_SensorFloorGroupGenerator:
+ continue;
+ break;
+ case k7_SensorFloorCreature:
+ if ((thingType > kDMThingTypeGroup) || (thingType == kDMThingTypeParty) || squareContainsGroup)
+ continue;
+ break;
+ case k8_SensorFloorPartyPossession:
+ if (thingType != kDMThingTypeParty)
+ continue;
+
+ triggerSensor = isObjectInPartyPossession(curSensorData);
+ break;
+ case k9_SensorFloorVersionChecker:
+ if ((thingType != kDMThingTypeParty) || !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)kDMCellAny); // this will wrap around
+ continue;
+ }
+
+ if (curThingType >= kDMThingTypeGroup)
+ 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 = kDMCellNorthWest;
+
+ 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 != kDMCellAny);
+ 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() != kDMThingTypeSensor)
+ || ((_sensorRotationEffCell != kDMCellAny) && (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() != kDMThingTypeSensor)
+ || ((_sensorRotationEffCell != kDMCellAny) && (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() == kDMThingTypeSensor))) {
+ if ((_sensorRotationEffCell == kDMCellAny) || (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;
+ _vm->setMapAndTime(newEvent._mapTime, mapIndex, _vm->_gameTime + 5);
+ newEvent._type = audible ? k61_TMEventTypeMoveGroupAudible : k60_TMEventTypeMoveGroupSilent;
+ newEvent._priority = 0;
+ newEvent._Bu._location._mapX = mapX;
+ newEvent._Bu._location._mapY = mapY;
+ newEvent._Cu._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 == kDMCellAny) || (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..e3ee666046
--- /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(kDMThingTypeProjectile);
+ 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 = _vm->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;
+ _vm->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._Bu._slot = projectileThing.toUint16();
+ newEvent._Cu._projectile.setMapX(mapX);
+ newEvent._Cu._projectile.setMapY(mapY);
+ newEvent._Cu._projectile.setStepEnergy(stepEnergy);
+ newEvent._Cu._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 == kDMThingTypePotion) {
+ 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 == kDMThingTypeExplosion) && (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 == kDMThingTypeExplosion) {
+ 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 != kDMThingTypeJunk)
+ || (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 == kDMThingTypeWeapon)
+ && 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() == kDMThingTypeWeapon)
+ 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 != kDMThingTypeExplosion) {
+ if (thingType == kDMThingTypeWeapon) {
+ 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(kDMThingTypeExplosion);
+ if (unusedThing == Thing::_none)
+ return;
+
+ Explosion *explosion = &((Explosion *)_vm->_dungeonMan->_thingData[kDMThingTypeExplosion])[(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 = _vm->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;
+ _vm->setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_currMapIndex, _vm->_gameTime + ((explThing == Thing::_explRebirthStep1) ? 5 : 1));
+ newEvent._type = k25_TMEventTypeExplosion;
+ newEvent._priority = 0;
+ newEvent._Cu._slot = unusedThing.toUint16();
+ newEvent._Bu._location._mapX = projectileMapX;
+ newEvent._Bu._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() == kDMThingTypeProjectile) && ((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() != kDMThingTypeExplosion) {
+ 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->_Bu._slot);
+ Thing projectileThing = projectileThingNewCell;
+ Projectile *projectile = (Projectile *)_vm->_dungeonMan->getThingData(projectileThing);
+ int16 destinationMapX = curEvent->_Cu._projectile.getMapX();
+ int16 destinationMapY = curEvent->_Cu._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->_Cu._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->_Cu._projectile.getDir();
+ projectileThingNewCell = Thing(curEvent->_Bu._slot);
+ uint16 projectileNewCell = projectileThingNewCell.getCell();
+ bool projectileMovesToOtherSquare = (projectileDirection == projectileNewCell) || (_vm->turnDirRight(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 = _vm->thingWithNewCell(projectileThingNewCell, projectileNewCell &= 0x0003);
+ if (projectileMovesToOtherSquare) {
+ _vm->_moveSens->getMoveResult(projectileThingNewCell, sourceMapX, sourceMapY, destinationMapX, destinationMapY);
+ curEvent->_Cu._projectile.setMapX(_vm->_moveSens->_moveResultMapX);
+ curEvent->_Cu._projectile.setMapY(_vm->_moveSens->_moveResultMapY);
+ curEvent->_Cu._projectile.setDir((Direction)_vm->_moveSens->_moveResultDir);
+ projectileThingNewCell = _vm->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->_Bu._slot = projectileThingNewCell.toUint16();
+ projectile->_eventIndex = _vm->_timeline->addEventGetEventIndex(curEvent);
+}
+
+void ProjExpl::processEvent25(TimelineEvent *event) {
+ uint16 mapX = event->_Bu._location._mapX;
+ uint16 mapY = event->_Bu._location._mapY;
+ Explosion *explosion = &((Explosion *)_vm->_dungeonMan->_thingData[kDMThingTypeExplosion])[Thing((event->_Cu._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->_Cu._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..d077ee58ec
--- /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 kDMDirNorth:
+ rightVolumeColumnIndex = mapX - _vm->_dungeonMan->_partyMapX;
+ lineIndex = mapY - _vm->_dungeonMan->_partyMapY;
+ break;
+ case kDMDirEast:
+ rightVolumeColumnIndex = mapY - _vm->_dungeonMan->_partyMapY;
+ lineIndex = -(mapX - _vm->_dungeonMan->_partyMapX);
+ break;
+ case kDMDirSouth:
+ rightVolumeColumnIndex = -(mapX - _vm->_dungeonMan->_partyMapX);
+ lineIndex = -(mapY - _vm->_dungeonMan->_partyMapY);
+ break;
+ case kDMDirWest:
+ 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;
+ _vm->setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_currMapIndex, _vm->_gameTime + mode - 1);
+ newEvent._type = k20_TMEventTypePlaySound;
+ newEvent._priority = sound->_priority;
+ newEvent._Cu._soundIndex = soundIndex;
+ newEvent._Bu._location._mapX = mapX;
+ newEvent._Bu._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..1b375bc3d0
--- /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 = (_vm->filterTime(eventA->_mapTime) == _vm->filterTime(eventB->_mapTime));
+
+ return (_vm->filterTime(eventA->_mapTime) < _vm->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->_Cu.A._cell == event->_Cu.A._cell))) {
+ curEvent->_Cu.A._effect = event->_Cu.A._effect;
+ return eventIndex;
+ }
+ continue;
+ } else if ((curEvent->_type == k1_TMEventTypeDoorAnimation) && (event->_mapTime == curEvent->_mapTime) && (event->getMapXY() == curEvent->getMapXY())) {
+ if (event->_Cu.A._effect == k2_SensorEffToggle)
+ event->_Cu.A._effect = 1 - curEvent->_Cu.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->_Cu.A._effect == k2_SensorEffToggle)
+ curEvent->_Cu.A._effect = 1 - event->_Cu.A._effect;
+
+ return eventIndex;
+ }
+ if (curEvent->_type == k1_TMEventTypeDoorAnimation) {
+ curEvent->_Cu.A._effect = event->_Cu.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()) && (_vm->getMap(event->_mapTime) == _vm->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(_vm->getMap(newEvent._mapTime));
+ uint16 curEventType = newEvent._type;
+ if ((curEventType > (k29_TMEventTypeGroupReactionDangerOnSquare - 1)) && (curEventType < (k41_TMEventTypeUpdateBehaviour_3 + 1)))
+ _vm->_groupMan->processEvents29to41(newEvent._Bu._location._mapX, newEvent._Bu._location._mapY, curEventType, newEvent._Cu._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._Cu._soundIndex, newEvent._Bu._location._mapX, newEvent._Bu._location._mapY, k1_soundModePlayIfPrioritized);
+ break;
+ case k24_TMEventTypeRemoveFluxcage:
+ if (!_vm->_gameWon) {
+ _vm->_dungeonMan->unlinkThingFromList(Thing(newEvent._Cu._slot), Thing(0), newEvent._Bu._location._mapX, newEvent._Bu._location._mapY);
+ curEvent = (TimelineEvent *)_vm->_dungeonMan->getThingData(Thing(newEvent._Cu._slot));
+ ((Explosion *)curEvent)->setNextThing(Thing::_none);
+ }
+ break;
+ case k11_TMEventTypeEnableChampionAction:
+ processEventEnableChampionAction(newEvent._priority);
+ if (newEvent._Bu._slotOrdinal)
+ processEventMoveWeaponFromQuiverToSlot(newEvent._priority, _vm->ordinalToIndex(newEvent._Bu._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._Bu._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._Bu._defense;
+ refreshAllChampionStatusBoxes();
+ break;
+ case k77_TMEventTypeSpellShield:
+ _vm->_championMan->_party._spellShieldDefense -= newEvent._Bu._defense;
+ refreshAllChampionStatusBoxes();
+ break;
+ case k78_TMEventTypeFireShield:
+ _vm->_championMan->_party._fireShieldDefense -= newEvent._Bu._defense;
+ refreshAllChampionStatusBoxes();
+ break;
+ case k75_TMEventTypePoisonChampion: {
+ uint16 championIndex = newEvent._priority;
+ _vm->_championMan->_champions[championIndex = newEvent._priority]._poisonEventCount--;
+ _vm->_championMan->championPoison(championIndex, newEvent._Bu._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 && (_vm->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->_Bu._location._mapX;
+ uint16 mapY = event->_Bu._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->_Cu.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->_Bu._location._mapX;
+ uint16 mapY = event->_Bu._location._mapY;
+ byte *curSquare = &_vm->_dungeonMan->_currMapData[mapX][mapY];
+ int16 effect = event->_Cu.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->_Bu._location._mapX][event->_Bu._location._mapY];
+ square->setDoorState(k5_doorState_DESTROYED);
+}
+
+void Timeline::processEventSquareDoor(TimelineEvent *event) {
+ int16 doorState = Square(_vm->_dungeonMan->_currMapData[event->_Bu._location._mapX][event->_Bu._location._mapY]).getDoorState();
+ if (doorState == k5_doorState_DESTROYED)
+ return;
+
+ if (event->_Cu.A._effect == k2_SensorEffToggle)
+ event->_Cu.A._effect = (doorState == k0_doorState_OPEN) ? k1_SensorEffClear : k0_SensorEffSet;
+ else if (event->_Cu.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->_Bu._location._mapX;
+ uint16 mapY = event->_Bu._location._mapY;
+
+ byte *square = &_vm->_dungeonMan->_currMapData[mapX][mapY];
+ if (event->_Cu.A._effect == k2_SensorEffToggle)
+ event->_Cu.A._effect = getFlag(*square, k0x0008_PitOpen) ? k1_SensorEffClear : k0_SensorEffSet;
+
+ if (event->_Cu.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() > kDMThingTypeGroup)
+ 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 > kDMThingTypeGroup)
+ _vm->_moveSens->getMoveResult(curThing, mapX, mapY, mapX, mapY);
+
+ if (curThingType == kDMThingTypeProjectile) {
+ Projectile *projectile = (Projectile *)_vm->_dungeonMan->getThingData(curThing);
+ TimelineEvent *newEvent;
+ newEvent = &_events[projectile->_eventIndex];
+ newEvent->_Cu._projectile.setMapX(_vm->_moveSens->_moveResultMapX);
+ newEvent->_Cu._projectile.setMapY(_vm->_moveSens->_moveResultMapY);
+ newEvent->_Cu._projectile.setDir((Direction)_vm->_moveSens->_moveResultDir);
+ newEvent->_Bu._slot = _vm->thingWithNewCell(curThing, _vm->_moveSens->_moveResultCell).toUint16();
+ M31_setMap(newEvent->_mapTime, _vm->_moveSens->_moveResultMapIndex);
+ } else if (curThingType == kDMThingTypeExplosion) {
+ TimelineEvent *newEvent = _events;
+ for (uint16 i = 0; i < _eventMaxCount; newEvent++, i++) {
+ if ((newEvent->_type == k25_TMEventTypeExplosion) && (newEvent->_Cu._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->_Bu._location._mapX = _vm->_moveSens->_moveResultMapX;
+ newEvent->_Bu._location._mapY = _vm->_moveSens->_moveResultMapY;
+ newEvent->_Cu._slot = _vm->thingWithNewCell(curThing, _vm->_moveSens->_moveResultCell).toUint16();
+ M31_setMap(newEvent->_mapTime, _vm->_moveSens->_moveResultMapIndex);
+ }
+ }
+ }
+ curThing = nextThing;
+ }
+}
+
+void Timeline::processEventSquareTeleporter(TimelineEvent *event) {
+ uint16 mapX = event->_Bu._location._mapX;
+ uint16 mapY = event->_Bu._location._mapY;
+
+ byte *curSquare = &_vm->_dungeonMan->_currMapData[mapX][mapY];
+ if (event->_Cu.A._effect == k2_SensorEffToggle)
+ event->_Cu.A._effect = getFlag(*curSquare, k0x0008_TeleporterOpen) ? k1_SensorEffClear : k0_SensorEffSet;
+
+ if (event->_Cu.A._effect == k0_SensorEffSet) {
+ setFlag(*curSquare, k0x0008_TeleporterOpen);
+ moveTeleporterOrPitSquareThings(mapX, mapY);
+ } else
+ clearFlag(*curSquare, k0x0008_TeleporterOpen);
+}
+
+void Timeline::processEventSquareWall(TimelineEvent *event) {
+ int16 mapX = event->_Bu._location._mapX;
+ int16 mapY = event->_Bu._location._mapY;
+ Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
+ uint16 curCell = event->_Cu.A._cell;
+ while (curThing != Thing::_endOfList) {
+ int16 curThingType = curThing.getType();
+ if ((curThingType == kDMstringTypeText) && (curThing.getCell() == event->_Cu.A._cell)) {
+ TextString *textString = (TextString *)_vm->_dungeonMan->getThingData(curThing);
+ if (event->_Cu.A._effect == k2_SensorEffToggle)
+ textString->setVisible(!textString->isVisible());
+ else
+ textString->setVisible(event->_Cu.A._effect == k0_SensorEffSet);
+ } else if (curThingType == kDMThingTypeSensor) {
+ Sensor *curThingSensor = (Sensor *)_vm->_dungeonMan->getThingData(curThing);
+ uint16 curSensorType = curThingSensor->getType();
+ uint16 curSensorData = curThingSensor->getData();
+ if (curSensorType == k6_SensorWallCountdown) {
+ if (curSensorData > 0) {
+ if (event->_Cu.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->_Cu.A._cell);
+ if (event->_Cu.A._effect == k2_SensorEffToggle) {
+ if (getFlag(curSensorData, bitMask))
+ clearFlag(curSensorData, bitMask);
+ else
+ setFlag(curSensorData, bitMask);
+ } else if (event->_Cu.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->_Cu.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->_Bu._location._mapX;
+ int16 mapY = event->_Bu._location._mapY;
+ uint16 cell = event->_Cu.A._cell;
+ uint16 projectileCell = _vm->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() > kDMThingTypeSensor) && ((projectiveThingCell == cell) || (projectiveThingCell == _vm->turnDirRight(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() > kDMThingTypeSensor) && ((projectiveThingCell == cell) || (projectiveThingCell == _vm->turnDirRight(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 = _vm->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, _vm->turnDirRight(projectileCell), (Direction)cell, kineticEnergy, 100, stepEnergy);
+
+ _vm->_projexpl->_createLauncherProjectile = false;
+}
+
+void Timeline::processEventSquareCorridor(TimelineEvent *event) {
+ uint16 mapX = event->_Bu._location._mapX;
+ uint16 mapY = event->_Bu._location._mapY;
+ Thing curThing = _vm->_dungeonMan->getSquareFirstThing(mapX, mapY);
+ while (curThing != Thing::_endOfList) {
+ int16 curThingType = curThing.getType();
+ if (curThingType == kDMstringTypeText) {
+ TextString *textString = (TextString *)_vm->_dungeonMan->getThingData(curThing);
+ bool textCurrentlyVisible = textString->isVisible();
+ if (event->_Cu.A._effect == k2_SensorEffToggle)
+ textString->setVisible(!textCurrentlyVisible);
+ else
+ textString->setVisible((event->_Cu.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 == kDMThingTypeSensor) {
+ 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;
+ _vm->setMapAndTime(newEvent._mapTime, _vm->_dungeonMan->_currMapIndex, _vm->_gameTime + actionTicks);
+ newEvent._priority = 0;
+ newEvent._Bu._location._mapX = mapX;
+ newEvent._Bu._location._mapY = mapY;
+ newEvent._Bu._location._mapY = mapY;
+ addEventGetEventIndex(&newEvent);
+ }
+ }
+ }
+ }
+ curThing = _vm->_dungeonMan->getNextThing(curThing);
+ }
+}
+
+void Timeline::processEventsMoveGroup(TimelineEvent *event) {
+ bool randomDirectionMoveRetried = false;
+ uint16 mapX = event->_Bu._location._mapX;
+ uint16 mapY = event->_Bu._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->_Cu._slot), kM1_MapXNotOnASquare, 0, mapX, mapY);
+ } else {
+ if (!randomDirectionMoveRetried) {
+ randomDirectionMoveRetried = true;
+ Group *group = (Group *)_vm->_dungeonMan->getThingData(Thing(event->_Cu._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->_Bu._location._mapX, event->_Bu._location._mapY);
+ while (curThing != Thing::_none) {
+ if ((curThing.getType()) == kDMThingTypeSensor) {
+ 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() == kDMThingTypeWeapon) {
+ _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->_Bu._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._Bu._lightPower = weakerLightPower;
+ _vm->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->_Bu._location._mapX;
+ int16 mapY = event->_Bu._location._mapY;
+ uint16 cell = event->_Cu.A._cell;
+ uint16 championIndex = event->_priority;
+ uint16 rebirthStep = event->_Cu.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->_Cu.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() == kDMThingTypeJunk)) {
+ 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->_Bu._location._mapX); // writing bytes of the union I think should preserve the union's identity
+ file->writeByte(event->_Bu._location._mapY);
+ file->writeUint16BE(event->_Cu.A._cell); // writing bytes of the union I think should preserve the union's identity
+ file->writeUint16BE(event->_Cu.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->_Bu._location._mapX = file->readByte();
+ event->_Bu._location._mapY = file->readByte();
+ event->_Cu.A._cell = file->readUint16BE();
+ event->_Cu.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..22e563d853
--- /dev/null
+++ b/engines/dm/timeline.h
@@ -0,0 +1,204 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 _Bu;
+
+ int16 getMapXY() { return (_Bu._location._mapX << 8) + _Bu._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 _Cu;
+}; // @ 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
diff --git a/engines/fullpipe/scenes/scene27.cpp b/engines/fullpipe/scenes/scene27.cpp
index 3589cc1bd1..17bf3916de 100644
--- a/engines/fullpipe/scenes/scene27.cpp
+++ b/engines/fullpipe/scenes/scene27.cpp
@@ -454,7 +454,6 @@ void sceneHandler27_batLogic() {
g_vars->scene27_bat = g_vars->scene27_balls[0];
g_vars->scene27_balls.remove_at(0);
- g_vars->scene27_balls.push_back(g_vars->scene27_bat);
int mv;
diff --git a/engines/fullpipe/statics.cpp b/engines/fullpipe/statics.cpp
index fe1f1e2d01..0539edd047 100644
--- a/engines/fullpipe/statics.cpp
+++ b/engines/fullpipe/statics.cpp
@@ -1480,8 +1480,13 @@ bool Statics::load(MfcArchive &file) {
void Statics::init() {
Picture::init();
- if (_staticsId & 0x4000)
- _bitmap->reverseImage();
+ if (_staticsId & 0x4000) {
+ Bitmap *reversed = _bitmap->reverseImage();
+ freePixelData();
+ // TODO: properly dispose old _bitmap
+ _bitmap = reversed;
+ // _data = ... // useless?
+ }
}
Common::Point *Statics::getSomeXY(Common::Point &p) {
@@ -1949,12 +1954,12 @@ DynamicPhase *Movement::getDynamicPhaseByIndex(int idx) {
void Movement::loadPixelData() {
Movement *mov = this;
- for (Movement *i = _currMovement; i; i = i->_currMovement)
- mov = i;
+ while (mov->_currMovement)
+ mov = mov->_currMovement;
- for (uint i = 0; i < _dynamicPhases.size(); i++) {
- if ((Statics *)_dynamicPhases[i] != mov->_staticsObj2 || !(mov->_staticsObj2->_staticsId & 0x4000))
- _dynamicPhases[i]->getPixelData();
+ for (uint i = 0; i < mov->_dynamicPhases.size(); i++) {
+ if ((Statics *)mov->_dynamicPhases[i] != mov->_staticsObj2 || !(mov->_staticsObj2->_staticsId & 0x4000))
+ mov->_dynamicPhases[i]->getPixelData();
}
if (!(mov->_staticsObj1->_staticsId & 0x4000))
@@ -1979,8 +1984,8 @@ void Movement::removeFirstPhase() {
_dynamicPhases.remove_at(0);
for (uint i = 0; i < _dynamicPhases.size(); i++) {
- _framePosOffsets[i - 1]->x = _framePosOffsets[i]->x;
- _framePosOffsets[i - 1]->y = _framePosOffsets[i]->y;
+ _framePosOffsets[i]->x = _framePosOffsets[i + 1]->x;
+ _framePosOffsets[i]->y = _framePosOffsets[i + 1]->y;
}
}
_currDynamicPhaseIndex--;
diff --git a/engines/titanic/carry/long_stick.cpp b/engines/titanic/carry/long_stick.cpp
index 557b75ab87..16cd69e373 100644
--- a/engines/titanic/carry/long_stick.cpp
+++ b/engines/titanic/carry/long_stick.cpp
@@ -48,7 +48,7 @@ bool CLongStick::UseWithOtherMsg(CUseWithOtherMsg *msg) {
CPuzzleSolvedMsg puzzleMsg;
puzzleMsg.execute(msg->_other);
} else if (msg->_other->isEquals("LongStickDispensor")) {
- petDisplayMessage(1, "You already have one.");
+ petDisplayMessage(1, ALREADY_HAVE_STICK);
} else if (msg->_other->isEquals("Bomb")) {
CActMsg actMsg("Hit");
actMsg.execute("Bomb");
diff --git a/engines/titanic/carry/napkin.cpp b/engines/titanic/carry/napkin.cpp
index d0ee9acc1a..3996ca7c74 100644
--- a/engines/titanic/carry/napkin.cpp
+++ b/engines/titanic/carry/napkin.cpp
@@ -49,7 +49,7 @@ bool CNapkin::UseWithOtherMsg(CUseWithOtherMsg *msg) {
CActMsg actMsg("Clean");
actMsg.execute("Chicken");
} else {
- petDisplayMessage("The Chicken is already quite clean enough, thank you.");
+ petDisplayMessage(CHICKEN_IS_CLEAN);
}
}
diff --git a/engines/titanic/carry/vision_centre.cpp b/engines/titanic/carry/vision_centre.cpp
index fd30089fc5..f81e35fa4f 100644
--- a/engines/titanic/carry/vision_centre.cpp
+++ b/engines/titanic/carry/vision_centre.cpp
@@ -49,7 +49,7 @@ bool CVisionCentre::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
if (_fieldE0) {
return CBrain::MouseButtonDownMsg(msg);
} else {
- petDisplayMessage(1, "It would be nice if you could take that but you can't.");
+ petDisplayMessage(1, NICE_IF_TAKE_BUT_CANT);
return true;
}
}
@@ -58,7 +58,7 @@ bool CVisionCentre::MouseDragStartMsg(CMouseDragStartMsg *msg) {
if (_fieldE0) {
return CBrain::MouseDragStartMsg(msg);
} else {
- petDisplayMessage(1, "It would be nice if you could take that but you can't.");
+ petDisplayMessage(1, NICE_IF_TAKE_BUT_CANT);
return true;
}
}
diff --git a/engines/titanic/core/game_object.cpp b/engines/titanic/core/game_object.cpp
index 59ae96138d..a1427b131e 100644
--- a/engines/titanic/core/game_object.cpp
+++ b/engines/titanic/core/game_object.cpp
@@ -1536,14 +1536,24 @@ bool CGameObject::petDoorOrBellbotPresent() const {
return pet ? pet->isDoorOrBellbotPresent() : false;
}
+void CGameObject::petDisplayMessage(int unused, StringId stringId) {
+ petDisplayMessage(stringId);
+}
+
+void CGameObject::petDisplayMessage(StringId stringId, int param) {
+ CPetControl *pet = getPetControl();
+ if (pet)
+ pet->displayMessage(stringId, param);
+}
+
void CGameObject::petDisplayMessage(int unused, const CString &msg) {
petDisplayMessage(msg);
}
-void CGameObject::petDisplayMessage(const CString &msg) {
+void CGameObject::petDisplayMessage(const CString &msg, int param) {
CPetControl *pet = getPetControl();
if (pet)
- pet->displayMessage(msg);
+ pet->displayMessage(msg, param);
}
void CGameObject::petInvChange() {
diff --git a/engines/titanic/core/game_object.h b/engines/titanic/core/game_object.h
index 0da3fad605..b9a55ac008 100644
--- a/engines/titanic/core/game_object.h
+++ b/engines/titanic/core/game_object.h
@@ -25,13 +25,14 @@
#include "audio/mixer.h"
#include "common/stream.h"
+#include "titanic/core/named_item.h"
+#include "titanic/sound/proximity.h"
#include "titanic/support/mouse_cursor.h"
#include "titanic/support/credit_text.h"
#include "titanic/support/movie_range_info.h"
-#include "titanic/sound/proximity.h"
#include "titanic/support/rect.h"
+#include "titanic/support/strings.h"
#include "titanic/support/movie_clip.h"
-#include "titanic/core/named_item.h"
#include "titanic/pet_control/pet_section.h"
#include "titanic/pet_control/pet_text.h"
#include "titanic/game_state.h"
@@ -826,12 +827,22 @@ public:
/**
* Display a message in the PET
*/
- void petDisplayMessage(int unused, const CString &msg);
+ void petDisplayMessage(int unused, StringId stringId);
+
+ /**
+ * Display a message in the PET
+ */
+ void petDisplayMessage(int unused, const CString &str);
+
+ /**
+ * Display a message in the PET
+ */
+ void petDisplayMessage(StringId stringId, int param = 0);
/**
* Display a message in the PET
*/
- void petDisplayMessage(const CString &msg);
+ void petDisplayMessage(const CString &str, int param = 0);
/**
* Gets the entry number used when last arriving at the well
diff --git a/engines/titanic/core/saveable_object.cpp b/engines/titanic/core/saveable_object.cpp
index db3249c107..d74f9a26ef 100644
--- a/engines/titanic/core/saveable_object.cpp
+++ b/engines/titanic/core/saveable_object.cpp
@@ -175,6 +175,7 @@
#include "titanic/game/pet_disabler.h"
#include "titanic/game/phonograph.h"
#include "titanic/game/phonograph_lid.h"
+#include "titanic/game/place_holder_item.h"
#include "titanic/game/play_music_button.h"
#include "titanic/game/play_on_act.h"
#include "titanic/game/port_hole.h"
@@ -207,6 +208,7 @@
#include "titanic/game/tow_parrot_nav.h"
#include "titanic/game/up_lighter.h"
#include "titanic/game/useless_lever.h"
+#include "titanic/game/variable_list.h"
#include "titanic/game/volume_control.h"
#include "titanic/game/wheel_button.h"
#include "titanic/game/wheel_hotspot.h"
@@ -586,6 +588,7 @@ DEFFN(CNutReplacer);
DEFFN(CPetDisabler);
DEFFN(CPhonograph);
DEFFN(CPhonographLid);
+DEFFN(CPlaceHolderItem);
DEFFN(CPlayMusicButton);
DEFFN(CPlayOnAct);
DEFFN(CPortHole);
@@ -618,6 +621,7 @@ DEFFN(CTitaniaStillControl);
DEFFN(CTOWParrotNav);
DEFFN(CUpLighter);
DEFFN(CUselessLever);
+DEFFN(CVariableListItem);
DEFFN(CVolumeControl);
DEFFN(CWheelButton);
DEFFN(CWheelHotSpot);
@@ -1174,6 +1178,7 @@ void CSaveableObject::initClassList() {
ADDFN(CPetDisabler, CGameObject);
ADDFN(CPhonograph, CMusicPlayer);
ADDFN(CPhonographLid, CGameObject);
+ ADDFN(CPlaceHolderItem, CNamedItem);
ADDFN(CPlayMusicButton, CBackground);
ADDFN(CPlayOnAct, CBackground);
ADDFN(CPortHole, CGameObject);
@@ -1206,6 +1211,7 @@ void CSaveableObject::initClassList() {
ADDFN(CTOWParrotNav, CGameObject);
ADDFN(CUpLighter, CDropTarget);
ADDFN(CUselessLever, CToggleButton);
+ ADDFN(CVariableListItem, ListItem);
ADDFN(CVolumeControl, CGameObject);
ADDFN(CWheelButton, CBackground);
ADDFN(CWheelHotSpot, CBackground);
diff --git a/engines/titanic/detection_tables.h b/engines/titanic/detection_tables.h
index d4acebc3bf..ba67942dfe 100644
--- a/engines/titanic/detection_tables.h
+++ b/engines/titanic/detection_tables.h
@@ -35,7 +35,6 @@ static const TitanicGameDescription gameDescriptions[] = {
},
},
-#if 0
// German version currently disabled because it won't start up,
// even with the English generated titanic.dat file
{
@@ -49,7 +48,6 @@ static const TitanicGameDescription gameDescriptions[] = {
GUIO1(GUIO_NONE)
},
},
-#endif
{ AD_TABLE_END_MARKER }
};
diff --git a/engines/titanic/game/chicken_dispensor.cpp b/engines/titanic/game/chicken_dispensor.cpp
index 7fb8fefcda..17d1e6a515 100644
--- a/engines/titanic/game/chicken_dispensor.cpp
+++ b/engines/titanic/game/chicken_dispensor.cpp
@@ -65,14 +65,14 @@ bool CChickenDispensor::StatusChangeMsg(CStatusChangeMsg *msg) {
for (obj = pet->getFirstObject(); obj; obj = pet->getNextObject(obj)) {
if (obj->isEquals("Chicken")) {
- petDisplayMessage(1, "Chickens are allocated on a one-per-customer basis.");
+ petDisplayMessage(1, ONE_CHICKEN_PER_CUSTOMER);
return true;
}
}
for (obj = getMailManFirstObject(); obj; obj = getNextMail(obj)) {
if (obj->isEquals("Chicken")) {
- petDisplayMessage(1, "Chickens are allocated on a one-per-customer basis.");
+ petDisplayMessage(1, ONE_ALLOCATED_CHICKEN_PER_CUSTOMER);
return true;
}
}
@@ -82,7 +82,7 @@ bool CChickenDispensor::StatusChangeMsg(CStatusChangeMsg *msg) {
switch (v1) {
case 0:
- petDisplayMessage(1, "Only one piece of chicken per passenger. Thank you.");
+ petDisplayMessage(1, ONE_CHICKEN_PER_CUSTOMER);
break;
case 1:
setVisible(true);
diff --git a/engines/titanic/game/fan_control.cpp b/engines/titanic/game/fan_control.cpp
index 56a1e49dec..ad3efc92fb 100644
--- a/engines/titanic/game/fan_control.cpp
+++ b/engines/titanic/game/fan_control.cpp
@@ -137,7 +137,7 @@ bool CFanControl::StatusChangeMsg(CStatusChangeMsg *msg) {
statusMsg._newStatus = _state;
statusMsg.execute("RightFan");
} else {
- petDisplayMessage(1, "Unfortunately this fan controller has blown a fuse.");
+ petDisplayMessage(1, FAN_HAS_BLOWN_A_FUSE);
}
}
diff --git a/engines/titanic/game/hammer_dispensor_button.cpp b/engines/titanic/game/hammer_dispensor_button.cpp
index fbda501a24..89d37a0476 100644
--- a/engines/titanic/game/hammer_dispensor_button.cpp
+++ b/engines/titanic/game/hammer_dispensor_button.cpp
@@ -69,7 +69,7 @@ bool CHammerDispensorButton::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) {
bool CHammerDispensorButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
playSound("z#93.wav");
- petDisplayMessage(1, "In case of emergency hammer requirement, poke with long stick.");
+ petDisplayMessage(1, POKE_WITH_LONG_STICK);
return true;
}
diff --git a/engines/titanic/game/lemon_dispensor.cpp b/engines/titanic/game/lemon_dispensor.cpp
index 31a04cbeca..f84a494c26 100644
--- a/engines/titanic/game/lemon_dispensor.cpp
+++ b/engines/titanic/game/lemon_dispensor.cpp
@@ -80,7 +80,7 @@ bool CLemonDispensor::FrameMsg(CFrameMsg *msg) {
CGameObject *obj = getDraggingObject();
if (obj && getView() == findView()) {
if (obj->isEquals("Perch")) {
- petDisplayMessage(1, "This stick is too short to reach the branches.");
+ petDisplayMessage(1, TOO_SHORT_TO_REACH_BRANCHES);
return true;
}
diff --git a/engines/titanic/game/light.cpp b/engines/titanic/game/light.cpp
index 65e357047e..bc8782a2b1 100644
--- a/engines/titanic/game/light.cpp
+++ b/engines/titanic/game/light.cpp
@@ -101,10 +101,10 @@ bool CLight::StatusChangeMsg(CStatusChangeMsg *msg) {
bool flag = pet ? pet->isRoom59706() : false;
if (_fieldFC == 1 && flag) {
- petDisplayMessage(1, "That light appears to be loose.");
+ petDisplayMessage(1, LIGHT_IS_LOOSE);
playSound("z#144.wav", 70);
} else {
- petDisplayMessage(1, "Lumi-Glow(tm) Lights. They glow in the dark!");
+ petDisplayMessage(1, LUMI_GLOW_LIGHTS);
playSound("z#62.wav", 70);
}
@@ -116,10 +116,10 @@ bool CLight::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
bool flag = pet ? pet->isRoom59706() : false;
if (_fieldFC == 1 && flag) {
- petDisplayMessage(1, "That light appears to be loose.");
+ petDisplayMessage(1, LIGHT_IS_LOOSE);
playSound("z#144.wav", 70);
} else {
- petDisplayMessage(1, "Lumi-Glow(tm) Lights. They glow in the dark!");
+ petDisplayMessage(1, LUMI_GLOW_LIGHTS);
playSound("z#62.wav", 70);
}
diff --git a/engines/titanic/game/long_stick_dispenser.cpp b/engines/titanic/game/long_stick_dispenser.cpp
index 08a29f2e4b..04014e8adf 100644
--- a/engines/titanic/game/long_stick_dispenser.cpp
+++ b/engines/titanic/game/long_stick_dispenser.cpp
@@ -62,7 +62,7 @@ bool CLongStickDispenser::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) {
loadFrame(19);
} else if (_fieldC0) {
playSound("z#63.wav");
- petDisplayMessage(1, "'This glass is totally and utterly unbreakable.");
+ petDisplayMessage(1, GLASS_IS_UNBREAKABLE);
}
return true;
@@ -92,11 +92,10 @@ bool CLongStickDispenser::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
switch (_fieldBC) {
case 0:
- petDisplayMessage(1, "For emergency long stick, smash glass.");
+ petDisplayMessage(1, FOR_STICK_BREAK_GLASS);
break;
case 1:
- petDisplayMessage(1, "This dispenser has suddenly been fitted with unbreakable glass "
- "to prevent unseemly hoarding of sticks.");
+ petDisplayMessage(1, DISPENSOR_HAS_UNBREAKABLE_GLASS);
break;
default:
break;
diff --git a/engines/titanic/game/nav_helmet.cpp b/engines/titanic/game/nav_helmet.cpp
index 08ff073c26..96411ad6b7 100644
--- a/engines/titanic/game/nav_helmet.cpp
+++ b/engines/titanic/game/nav_helmet.cpp
@@ -54,7 +54,7 @@ bool CNavHelmet::MovieEndMsg(CMovieEndMsg *msg) {
CPetControl *pet = getPetControl();
if (pet) {
pet->setArea(PET_STARFIELD);
- petDisplayMessage(1, "Now would be an excellent opportunity to adjust your viewing apparatus.");
+ petDisplayMessage(1, ADJUST_VIEWING_APPARATUS);
pet->incAreaLocks();
}
diff --git a/engines/titanic/game/parrot/parrot_perch_holder.cpp b/engines/titanic/game/parrot/parrot_perch_holder.cpp
index d594446219..b1a7dabfbf 100644
--- a/engines/titanic/game/parrot/parrot_perch_holder.cpp
+++ b/engines/titanic/game/parrot/parrot_perch_holder.cpp
@@ -47,7 +47,7 @@ void CParrotPerchHolder::load(SimpleFile *file) {
bool CParrotPerchHolder::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
if (CParrot::_v1) {
if (CCage::_open) {
- petDisplayMessage("You cannot take this because the cage is locked shut.");
+ petDisplayMessage(CANNOT_TAKE_CAGE_LOCKED);
} else if (!CParrot::_v4) {
CTrueTalkTriggerActionMsg triggerMsg(280252, 0, 0);
triggerMsg.execute(getRoot(), CParrot::_type,
diff --git a/engines/titanic/game/pet/pet_lift.cpp b/engines/titanic/game/pet/pet_lift.cpp
index afa9dd04cd..a7b48853e6 100644
--- a/engines/titanic/game/pet/pet_lift.cpp
+++ b/engines/titanic/game/pet/pet_lift.cpp
@@ -52,7 +52,7 @@ bool CPETLift::TransportMsg(CTransportMsg *msg) {
} else if (msg->_roomName == "PlayersRoom" && pet) {
int assignedFloor = pet->getAssignedFloorNum();
if (assignedFloor < 1 || assignedFloor > 39) {
- pet->petDisplayMessage("You have not assigned a room to go to.");
+ pet->petDisplayMessage(NO_ROOM_ASSIGNED);
floorNum = -1;
}
}
@@ -61,7 +61,7 @@ bool CPETLift::TransportMsg(CTransportMsg *msg) {
int elevatorNum = pet ? pet->getRoomsElevatorNum() : 0;
if ((elevatorNum == 2 || elevatorNum == 4) && floorNum > 27) {
- petDisplayMessage("Sorry, this elevator does not go below floor 27.");
+ petDisplayMessage(ELEVATOR_NOT_BELOW_27);
} else {
CTrueTalkTriggerActionMsg triggerMsg(2, floorNum, 0);
triggerMsg.execute("Liftbot");
diff --git a/engines/titanic/game/phonograph_lid.cpp b/engines/titanic/game/phonograph_lid.cpp
index 3741749fbf..d1ab478f3d 100644
--- a/engines/titanic/game/phonograph_lid.cpp
+++ b/engines/titanic/game/phonograph_lid.cpp
@@ -58,7 +58,7 @@ bool CPhonographLid::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
_open = !_open;
} else {
- petDisplayMessage(0, "This is the restaurant music system. It appears to be locked.");
+ petDisplayMessage(0, LOCKED_MUSIC_SYSTEM);
}
return true;
diff --git a/engines/titanic/game/pickup/pick_up_speech_centre.cpp b/engines/titanic/game/pickup/pick_up_speech_centre.cpp
index 5e99c0a3b7..941d5cdb28 100644
--- a/engines/titanic/game/pickup/pick_up_speech_centre.cpp
+++ b/engines/titanic/game/pickup/pick_up_speech_centre.cpp
@@ -63,7 +63,7 @@ bool CPickUpSpeechCentre::MouseDragStartMsg(CMouseDragStartMsg *msg) {
CActMsg actMsg("PlayerGetsSpeechCentre");
actMsg.execute("SeasonalAdjust");
} else {
- petDisplayMessage("You can't pick this up on account of it being stuck to the branch.");
+ petDisplayMessage(STUCK_TO_BRANCH);
}
}
diff --git a/engines/titanic/game/place_holder_item.cpp b/engines/titanic/game/place_holder_item.cpp
new file mode 100644
index 0000000000..ecd9c9a10b
--- /dev/null
+++ b/engines/titanic/game/place_holder_item.cpp
@@ -0,0 +1,96 @@
+/* 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.
+ *
+ */
+
+#include "titanic/game/place_holder_item.h"
+
+namespace Titanic {
+
+EMPTY_MESSAGE_MAP(CPlaceHolderItem, CNamedItem);
+
+CPlaceHolderItem::CPlaceHolderItem() :
+ _field4C(0), _field60(0), _field64(0), _field68(0), _field7C(0) {
+}
+
+void CPlaceHolderItem::save(SimpleFile *file, int indent) {
+ file->writeNumberLine(7, indent);
+ file->writeNumberLine(_field7C, indent);
+ file->writeQuotedLine("Movies", indent);
+ _clips.save(file, indent + 1);
+ file->writeNumberLine(_field68, indent);
+ file->writeNumberLine(_field64, indent);
+ file->writeNumberLine(_field60, indent);
+ _list.save(file, indent);
+ file->writeQuotedLine(_string2, indent);
+ file->writeNumberLine(_field4C, indent);
+ file->writePoint(_pos1, indent);
+ file->writePoint(_pos2, indent);
+ file->writeQuotedLine(_string1, indent);
+
+ CNamedItem::save(file, indent);
+}
+
+void CPlaceHolderItem::load(SimpleFile *file) {
+ switch (file->readNumber()) {
+ case 7:
+ _field7C = file->readNumber();
+ // Deliberate fall-through
+
+ case 6:
+ file->readString();
+ _clips.load(file);
+ // Deliberate fall-through
+
+ case 5:
+ _field68 = file->readNumber();
+ // Deliberate fall-through
+
+ case 4:
+ _field64 = file->readNumber();
+ // Deliberate fall-through
+
+ case 3:
+ _field60 = file->readNumber();
+ // Deliberate fall-through
+
+ case 2:
+ _list.load(file);
+ // Deliberate fall-through
+
+ case 1:
+ _string2 = file->readString();
+ _field4C = file->readNumber();
+ _pos1 = file->readPoint();
+ _pos2 = file->readPoint();
+ // Deliberate fall-through
+
+ case 0:
+ _string1 = file->readString();
+ break;
+
+ default:
+ break;
+ }
+
+ CNamedItem::load(file);
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/game/place_holder_item.h b/engines/titanic/game/place_holder_item.h
new file mode 100644
index 0000000000..c4850377db
--- /dev/null
+++ b/engines/titanic/game/place_holder_item.h
@@ -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.
+ *
+ */
+
+#ifndef TITANIC_PLACE_HOLDER_ITEM_H
+#define TITANIC_PLACE_HOLDER_ITEM_H
+
+#include "titanic/core/named_item.h"
+#include "titanic/game/variable_list.h"
+#include "titanic/support/movie_clip.h"
+
+namespace Titanic {
+
+class CPlaceHolderItem : public CNamedItem {
+ DECLARE_MESSAGE_MAP;
+public:
+ CString _string1;
+ Point _pos1;
+ Point _pos2;
+ CString _string2;
+ int _field4C;
+ CVariableList _list;
+ int _field60;
+ int _field64;
+ int _field68;
+ CMovieClipList _clips;
+ int _field7C;
+public:
+ CLASSDEF;
+ CPlaceHolderItem();
+
+ /**
+ * Save the data for the class to file
+ */
+ virtual void save(SimpleFile *file, int indent);
+
+ /**
+ * Load the data for the class from file
+ */
+ virtual void load(SimpleFile *file);
+};
+
+} // End of namespace Titanic
+
+#endif /* TITANIC_PLACE_HOLDER_ITEM_H */
diff --git a/engines/titanic/game/sauce_dispensor.cpp b/engines/titanic/game/sauce_dispensor.cpp
index fda7082ba6..410d4a3153 100644
--- a/engines/titanic/game/sauce_dispensor.cpp
+++ b/engines/titanic/game/sauce_dispensor.cpp
@@ -75,7 +75,7 @@ bool CSauceDispensor::Use(CUse *msg) {
playSound("b#15.wav", 50);
if (chicken->_string6 != "None") {
- petDisplayMessage(1, "This foodstuff is already sufficiently garnished.");
+ petDisplayMessage(1, FOODSTUFF_ALREADY_GARNISHED);
msg->execute("Chicken");
} else {
setVisible(true);
@@ -96,7 +96,7 @@ bool CSauceDispensor::Use(CUse *msg) {
endMsg.execute(this);
playSound("z#120.wav");
- petDisplayMessage(1, "Sadly, this dispenser is currently empty.");
+ petDisplayMessage(1, DISPENSOR_IS_EMPTY);
} else if (msg->_item->isEquals("BeerGlass")) {
CGlass *glass = dynamic_cast<CGlass *>(msg->_item);
_field108 = true;
@@ -157,12 +157,12 @@ bool CSauceDispensor::LeaveViewMsg(CLeaveViewMsg *msg) {
}
bool CSauceDispensor::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
- petDisplayMessage(1, "Please place food source beneath dispenser for sauce delivery.");
+ petDisplayMessage(1, PUT_FOOD_UNDER_DISPENSOR);
return true;
}
bool CSauceDispensor::StatusChangeMsg(CStatusChangeMsg *msg) {
- petDisplayMessage(1, "Please place food source beneath dispenser for sauce delivery.");
+ petDisplayMessage(1, PUT_FOOD_UNDER_DISPENSOR);
return true;
}
diff --git a/engines/titanic/game/seasonal_adjustment.cpp b/engines/titanic/game/seasonal_adjustment.cpp
index 1f1cb88afb..d9eb0d6fdf 100644
--- a/engines/titanic/game/seasonal_adjustment.cpp
+++ b/engines/titanic/game/seasonal_adjustment.cpp
@@ -81,7 +81,7 @@ bool CSeasonalAdjustment::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
bool CSeasonalAdjustment::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
playSound("z#42.wav");
if (!_fieldE4) {
- petDisplayMessage(1, "The Seasonal Adjustment switch is not operational at the present time.");
+ petDisplayMessage(1, SEASONAL_SWITCH_NOT_WORKING);
} else if (!_fieldE0) {
playMovie(0, 6, MOVIE_NOTIFY_OBJECT);
playMovie(6, 18, 0);
diff --git a/engines/titanic/game/sgt/sgt_nav.cpp b/engines/titanic/game/sgt/sgt_nav.cpp
index c004f947d2..6e3c88e509 100644
--- a/engines/titanic/game/sgt/sgt_nav.cpp
+++ b/engines/titanic/game/sgt/sgt_nav.cpp
@@ -59,11 +59,9 @@ bool SGTNav::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
changeView("SGTState.Node 2.E");
} else if (_statics->_v1 == "Open") {
- petDisplayMessage(1, "This is your stateroom. It is for sleeping. If you desire "
- "entertainment or relaxation, please visit your local leisure lounge.");
+ petDisplayMessage(1, YOUR_STATEROOM);
} else if (_statics->_v6 == "Closed") {
- petDisplayMessage(1, "The bed will not currently support your weight."
- " We are working on this problem but are unlikely to be able to fix it.");
+ petDisplayMessage(1, BED_NOT_SUPPORT_YOUR_WEIGHT);
}
return true;
diff --git a/engines/titanic/game/sgt/sgt_state_room.cpp b/engines/titanic/game/sgt/sgt_state_room.cpp
index c089e401b8..02f1723d72 100644
--- a/engines/titanic/game/sgt/sgt_state_room.cpp
+++ b/engines/titanic/game/sgt/sgt_state_room.cpp
@@ -105,7 +105,7 @@ bool CSGTStateRoom::ActMsg(CActMsg *msg) {
uint assignedRoom = pet->getAssignedRoomFlags();
if (roomFlags != assignedRoom) {
- petDisplayMessage("This is not your assigned room. Please do not enjoy.");
+ petDisplayMessage(NOT_YOUR_ASSIGNED_ROOM);
} else if (_fieldE0) {
CTurnOn onMsg;
onMsg.execute(this);
diff --git a/engines/titanic/game/speech_dispensor.cpp b/engines/titanic/game/speech_dispensor.cpp
index 20ff3c69e0..3554c48602 100644
--- a/engines/titanic/game/speech_dispensor.cpp
+++ b/engines/titanic/game/speech_dispensor.cpp
@@ -68,7 +68,7 @@ bool CSpeechDispensor::FrameMsg(CFrameMsg *msg) {
CGameObject *dragObject = getDraggingObject();
if (!_dragItem && dragObject && getView() == findView()) {
if (dragObject->isEquals("Perch")) {
- petDisplayMessage(1, "This stick is too short to reach the branches.");
+ petDisplayMessage(1, TOO_SHORT_TO_REACH_BRANCHES);
return true;
}
@@ -86,7 +86,7 @@ bool CSpeechDispensor::FrameMsg(CFrameMsg *msg) {
case 0:
playSound("z#93.wav");
if (_seasonNum == SEASON_WINTER) {
- petDisplayMessage(1, "You cannot get this, it is frozen to the branch.");
+ petDisplayMessage(1, FROZEN_TO_BRANCH);
_fieldE0 = false;
_state = 1;
} else {
@@ -121,9 +121,9 @@ bool CSpeechDispensor::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
if (!_fieldEC) {
playSound("z#93.wav");
if (_fieldF8) {
- petDisplayMessage(1, "Sadly, this is out of your reach.");
+ petDisplayMessage(1, OUT_OF_REACH);
} else {
- petDisplayMessage(1, "You can't pick this up on account of it being stuck to the branch.");
+ petDisplayMessage(1, STUCK_TO_BRANCH);
}
}
diff --git a/engines/titanic/game/sweet_bowl.cpp b/engines/titanic/game/sweet_bowl.cpp
index d0a2525bc4..29d8044a85 100644
--- a/engines/titanic/game/sweet_bowl.cpp
+++ b/engines/titanic/game/sweet_bowl.cpp
@@ -60,7 +60,7 @@ bool CSweetBowl::ActMsg(CActMsg *msg) {
}
petDisplayMessage(isEquals("BowlNutsRustler") ?
- "A bowl of pistachio nuts." : "Not a bowl of pistachio nuts.");
+ BOWL_OF_NUTS : NOT_A_BOWL_OF_NUTS);
return true;
}
diff --git a/engines/titanic/game/third_class_canal.cpp b/engines/titanic/game/third_class_canal.cpp
index 97b559e35d..5bc2ede63c 100644
--- a/engines/titanic/game/third_class_canal.cpp
+++ b/engines/titanic/game/third_class_canal.cpp
@@ -39,7 +39,7 @@ void CThirdClassCanal::load(SimpleFile *file) {
}
bool CThirdClassCanal::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
- petDisplayMessage("This area is off limits to passengers.");
+ petDisplayMessage(AREA_OFF_LIMIT_TO_PASSENGERS);
return true;
}
diff --git a/engines/titanic/game/transport/lift_indicator.cpp b/engines/titanic/game/transport/lift_indicator.cpp
index 7471affc36..10d62a0775 100644
--- a/engines/titanic/game/transport/lift_indicator.cpp
+++ b/engines/titanic/game/transport/lift_indicator.cpp
@@ -74,9 +74,7 @@ bool CLiftindicator::EnterViewMsg(CEnterViewMsg *msg) {
petSetRemoteTarget();
petSetArea(PET_REMOTE);
- CString str = CString::format("You are standing outside Elevator %d",
- petGetRoomsWellEntry());
- petDisplayMessage(-1, str);
+ petDisplayMessage(OUTSIDE_ELEVATOR_NUM, petGetRoomsWellEntry());
debugC(kDebugScripts, "Claiming PET - %d, Multiplier = %f",
_liftNum, multiplier);
@@ -149,14 +147,13 @@ bool CLiftindicator::PETActivateMsg(CPETActivateMsg *msg) {
if (msg->_name == "Lift") {
if (petDoorOrBellbotPresent()) {
- petDisplayMessage(1, "I'm sorry, you cannot enter this elevator at present "
- "as a bot is in the way.");
+ petDisplayMessage(1, BOT_BLOCKING_ELEVATOR);
} else {
_endFrame = pet->getRoomsFloorNum();
if (petGetRoomsWellEntry() == 4 && !CLift::_v6
&& pet->getRoomsFloorNum() != CLift::_elevator4Floor) {
- petDisplayMessage(1, "This elevator is currently in an advanced state of non-functionality.");
+ petDisplayMessage(1, ELEVATOR_NON_FUNCTIONAL);
} else {
_start = _indicatorPos.y + (int)(_startFrame * multiplier);
_end = _indicatorPos.y + (int)(_endFrame * multiplier);
diff --git a/engines/titanic/game/transport/pellerator.cpp b/engines/titanic/game/transport/pellerator.cpp
index e4af40c334..23c61ad0ba 100644
--- a/engines/titanic/game/transport/pellerator.cpp
+++ b/engines/titanic/game/transport/pellerator.cpp
@@ -67,9 +67,9 @@ bool CPellerator::StatusChangeMsg(CStatusChangeMsg *msg) {
int newDest = msg->_newStatus;
if (msg->_newStatus == _destination) {
- petDisplayMessage(1, "You are already at your chosen destination.");
+ petDisplayMessage(1, ALREADY_AT_DESTINATION);
} else if (classNum == 3 || (msg->_newStatus > 4 && classNum != 1)) {
- petDisplayMessage(1, "Passengers of your class are not permitted to enter this area.");
+ petDisplayMessage(1, CLASS_NOT_ALLOWED_AT_DEST);
} else if (newDest > _destination) {
CString name = getName();
changeView(name == "PelleratorObject2" ?
diff --git a/engines/titanic/game/variable_list.cpp b/engines/titanic/game/variable_list.cpp
new file mode 100644
index 0000000000..1ddf1bc957
--- /dev/null
+++ b/engines/titanic/game/variable_list.cpp
@@ -0,0 +1,73 @@
+/* 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.
+ *
+ */
+
+#include "titanic/game/variable_list.h"
+
+namespace Titanic {
+
+void CVariableListItem::save(SimpleFile *file, int indent) {
+ file->writeNumberLine(3, indent);
+ file->writeNumberLine(_field44, indent);
+ file->writeQuotedLine(_string1, indent);
+ file->writeNumberLine(_field18, indent);
+ file->writeNumberLine(_field40, indent);
+ file->writeQuotedLine(_string2, indent);
+ file->writeQuotedLine(_string3, indent);
+ file->writeQuotedLine(_string4, indent);
+
+ ListItem::save(file, indent);
+}
+
+void CVariableListItem::load(SimpleFile *file) {
+ int field40 = 0, field44 = 0;
+
+ switch (file->readNumber()) {
+ case 3:
+ field44 = file->readNumber();
+ // Deliberate fall-through
+
+ case 2:
+ _string1 = file->readString();
+ _field18 = file->readNumber();
+ // Deliberate fall-through
+
+ case 1:
+ field40 = file->readNumber();
+ // Deliberate fall-through
+
+ case 0:
+ _string2 = file->readString();
+ _string3 = file->readString();
+ _string4 = file->readString();
+ break;
+
+ default:
+ break;
+ }
+
+ _field40 = field40;
+ _field44 = field44;
+
+ ListItem::load(file);
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/game/variable_list.h b/engines/titanic/game/variable_list.h
new file mode 100644
index 0000000000..9309e19ac5
--- /dev/null
+++ b/engines/titanic/game/variable_list.h
@@ -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.
+ *
+ */
+
+#ifndef TITANIC_VARIABLE_LIST_H
+#define TITANIC_VARIABLE_LIST_H
+
+#include "titanic/core/list.h"
+
+namespace Titanic {
+
+class CVariableListItem : public ListItem {
+public:
+ CString _string1;
+ int _field18;
+ CString _string2;
+ CString _string3;
+ CString _string4;
+ int _field40;
+ int _field44;
+public:
+ CLASSDEF;
+ CVariableListItem() : ListItem(), _field18(0), _field40(0), _field44(0) {}
+
+ /**
+ * Save the data for the class to file
+ */
+ virtual void save(SimpleFile *file, int indent);
+
+ /**
+ * Load the data for the class from file
+ */
+ virtual void load(SimpleFile *file);
+};
+
+
+/**
+ * Movie clip list
+ */
+class CVariableList: public List<CVariableListItem> {
+};
+
+} // End of namespace Titanic
+
+#endif /* TITANIC_VARIABLE_LIST_H */
diff --git a/engines/titanic/game/wheel_hotspot.cpp b/engines/titanic/game/wheel_hotspot.cpp
index 544e6f5470..aeca7130b5 100644
--- a/engines/titanic/game/wheel_hotspot.cpp
+++ b/engines/titanic/game/wheel_hotspot.cpp
@@ -69,7 +69,7 @@ bool CWheelHotSpot::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
break;
}
} else if (_fieldE4 == 3) {
- petDisplayMessage("Go where?");
+ petDisplayMessage(GO_WHERE);
}
return true;
diff --git a/engines/titanic/module.mk b/engines/titanic/module.mk
index 01ad617d6c..798d81dbe5 100644
--- a/engines/titanic/module.mk
+++ b/engines/titanic/module.mk
@@ -167,6 +167,7 @@ MODULE_OBJS := \
game/pet_disabler.o \
game/phonograph.o \
game/phonograph_lid.o \
+ game/place_holder_item.o \
game/play_music_button.o \
game/play_on_act.o \
game/port_hole.o \
@@ -199,6 +200,7 @@ MODULE_OBJS := \
game/titania_still_control.o \
game/up_lighter.o \
game/useless_lever.o \
+ game/variable_list.o \
game/volume_control.o \
game/wheel_button.o \
game/wheel_hotspot.o \
@@ -463,6 +465,7 @@ MODULE_OBJS := \
support/font.o \
support/image.o \
support/image_decoders.o \
+ support/strings.o \
support/mouse_cursor.o \
support/movie.o \
support/movie_clip.o \
diff --git a/engines/titanic/moves/call_pellerator.cpp b/engines/titanic/moves/call_pellerator.cpp
index 0dd8195277..75edd50a25 100644
--- a/engines/titanic/moves/call_pellerator.cpp
+++ b/engines/titanic/moves/call_pellerator.cpp
@@ -47,7 +47,7 @@ bool CCallPellerator::EnterViewMsg(CEnterViewMsg *msg) {
CString name = getFullViewName();
if (name == "TopOfWell.Node 6.S") {
- petDisplayMessage(2, "You are standing outside the Pellerator.");
+ petDisplayMessage(2, STANDING_OUTSIDE_PELLERATOR);
}
petSetRemoteTarget();
@@ -64,7 +64,7 @@ bool CCallPellerator::PETActivateMsg(CPETActivateMsg *msg) {
if (msg->_name == "Pellerator") {
if (petDoorOrBellbotPresent()) {
- petDisplayMessage("I'm sorry, you cannot enter this pellerator at present as a bot is in the way.");
+ petDisplayMessage(BOT_BLOCKING_PELLERATOR);
} else if (name == "Bar.Node 1.S") {
changeView("Pellerator.Node 1.S");
} else {
diff --git a/engines/titanic/moves/enter_exit_first_class_state.cpp b/engines/titanic/moves/enter_exit_first_class_state.cpp
index 34e9984aa7..efdead533a 100644
--- a/engines/titanic/moves/enter_exit_first_class_state.cpp
+++ b/engines/titanic/moves/enter_exit_first_class_state.cpp
@@ -52,12 +52,11 @@ bool CEnterExitFirstClassState::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
break;
case 2:
- petDisplayMessage(1, "This room is reserved for the exclusive use of first class passengeres."
- " That does not currently include you");
+ petDisplayMessage(1, ROOM_RESERVED_FOR_FIRST_CLASS);
break;
default:
- petDisplayMessage("No losers.");
+ petDisplayMessage(NO_LOSERS);
break;
}
diff --git a/engines/titanic/moves/enter_sec_class_state.cpp b/engines/titanic/moves/enter_sec_class_state.cpp
index 2a35621003..853a00d590 100644
--- a/engines/titanic/moves/enter_sec_class_state.cpp
+++ b/engines/titanic/moves/enter_sec_class_state.cpp
@@ -50,7 +50,7 @@ void CEnterSecClassState::load(SimpleFile *file) {
bool CEnterSecClassState::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
if (getPassengerClass() > 2) {
playSound("b#105.wav");
- petDisplayMessage(1, "Passengers of your class are not permitted to enter this area.");
+ petDisplayMessage(1, CLASS_NOT_PERMITTED_IN_AREA);
} else if (!compareRoomNameTo("SecClassLittleLift") || _mode == 2) {
CActMsg actMsg(getFullViewName().deleteRight(3) + ".S");
actMsg.execute("SecClassRoomLeaver");
diff --git a/engines/titanic/moves/exit_pellerator.cpp b/engines/titanic/moves/exit_pellerator.cpp
index 12ca2c1e3c..3140d90b4c 100644
--- a/engines/titanic/moves/exit_pellerator.cpp
+++ b/engines/titanic/moves/exit_pellerator.cpp
@@ -94,7 +94,7 @@ bool CExitPellerator::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
changeView(_statics->_isWinter ? "FrozenArboretum.Node 4.S" : "Arboretum.Node 4.W");
break;
default:
- petDisplayMessage(2, "Please exit from the other side.");
+ petDisplayMessage(2, EXIT_FROM_OTHER_SIDE);
CPellerator::_soundHandle = queueSound("z#438.wav", CPellerator::_soundHandle);
}
@@ -116,7 +116,7 @@ bool CExitPellerator::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
if (_statics->_v2 == 2) {
changeView("Bar.Node 1.N");
} else {
- petDisplayMessage(2, "Please exit from the other side.");
+ petDisplayMessage(2, EXIT_FROM_OTHER_SIDE);
CPellerator::_soundHandle = queueSound("z#438.wav", CPellerator::_soundHandle);
}
}
diff --git a/engines/titanic/moves/exit_tiania.cpp b/engines/titanic/moves/exit_tiania.cpp
index fb0f149ba9..63c1cbae4e 100644
--- a/engines/titanic/moves/exit_tiania.cpp
+++ b/engines/titanic/moves/exit_tiania.cpp
@@ -54,8 +54,7 @@ void CExitTiania::load(SimpleFile *file) {
bool CExitTiania::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
if (getPassengerClass() == 4) {
- petDisplayMessage(1, "For mysterious and unknowable reasons, "
- "this transport is temporarily out of order.");
+ petDisplayMessage(1, TRANSPORT_OUT_OF_ORDER);
} else {
lockMouse();
for (int idx = 0; idx < 3; ++idx)
diff --git a/engines/titanic/moves/restricted_move.cpp b/engines/titanic/moves/restricted_move.cpp
index 37cb1c46dc..b1040a3554 100644
--- a/engines/titanic/moves/restricted_move.cpp
+++ b/engines/titanic/moves/restricted_move.cpp
@@ -52,10 +52,10 @@ bool CRestrictedMove::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
// Okay to change to the given destination
changeView(_destination);
} else if (classNum != 4) {
- petDisplayMessage(1, "Passengers of your class are not permitted to enter this area.");
+ petDisplayMessage(1, CLASS_NOT_ALLOWED_AT_DEST);
} else if (compareRoomNameTo("EmbLobby")) {
playSound("a#17.wav");
- petDisplayMessage(1, "Please check in at the reception desk.");
+ petDisplayMessage(1, CHECK_IN_AT_RECEPTION);
} else if (compareViewNameTo("Titania.Node 1.S")) {
playSound("z#226.wav");
changeView(_destination);
diff --git a/engines/titanic/moves/trip_down_canal.cpp b/engines/titanic/moves/trip_down_canal.cpp
index e9818fa96d..010505a510 100644
--- a/engines/titanic/moves/trip_down_canal.cpp
+++ b/engines/titanic/moves/trip_down_canal.cpp
@@ -43,7 +43,7 @@ void CTripDownCanal::load(SimpleFile *file) {
bool CTripDownCanal::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
if (stateGetSeason() == SEASON_WINTER) {
- petDisplayMessage("Sadly, the Grand Canal transport system is closed for the winter.");
+ petDisplayMessage(CANAL_CLOSED_FOR_WINTER);
} else if (getGameManager()) {
changeView(_destination);
}
diff --git a/engines/titanic/npcs/bilge_succubus.cpp b/engines/titanic/npcs/bilge_succubus.cpp
index 16064bf212..d1b89e58b4 100644
--- a/engines/titanic/npcs/bilge_succubus.cpp
+++ b/engines/titanic/npcs/bilge_succubus.cpp
@@ -81,7 +81,7 @@ bool CBilgeSuccUBus::PETReceiveMsg(CPETReceiveMsg *msg) {
playSound("z#28.wav", 70);
} else if (!_enabled) {
- petDisplayMessage(2, "The Succ-U-Bus is in Standby, or \"Off\" mode at present.");
+ petDisplayMessage(2, SUCCUBUS_IS_IN_STANDBY);
return false;
} else if (!pet) {
return false;
@@ -96,7 +96,7 @@ bool CBilgeSuccUBus::PETReceiveMsg(CPETReceiveMsg *msg) {
if (_startFrame4 >= 0)
playMovie(_startFrame4, _endFrame4, MOVIE_GAMESTATE);
} else {
- petDisplayMessage(2, "There is currently nothing to deliver.");
+ petDisplayMessage(2, NOTHING_TO_DELIVER);
}
}
@@ -112,7 +112,7 @@ bool CBilgeSuccUBus::PETDeliverMsg(CPETDeliverMsg *msg) {
CGameObject *mailObject = findMail(petRoomFlags);
if (!mailObject) {
- petDisplayMessage(2, "There is currently nothing in the tray to send.");
+ petDisplayMessage(2, NOTHING_IN_SUCCUBUS_TRAY);
return true;
}
@@ -329,7 +329,7 @@ bool CBilgeSuccUBus::SubAcceptCCarryMsg(CSubAcceptCCarryMsg *msg) {
uint petRoomFlags = pet->getRoomFlags();
if (mailExists(petRoomFlags)) {
- petDisplayMessage(2, "The Succ-U-Bus is a Single Entity Delivery Device.");
+ petDisplayMessage(2, SUCCUBUS_SINGLE_DELIVERY);
item->petAddToInventory();
return true;
}
@@ -387,7 +387,7 @@ bool CBilgeSuccUBus::EnterViewMsg(CEnterViewMsg *msg) {
}
bool CBilgeSuccUBus::LeaveViewMsg(CLeaveViewMsg *msg) {
- petDisplayMessage(2, "");
+ petDisplayMessage(2, BLANK);
petClear();
if (_soundHandle != -1) {
diff --git a/engines/titanic/npcs/deskbot.cpp b/engines/titanic/npcs/deskbot.cpp
index 5b6364f1cb..8f1fc28e20 100644
--- a/engines/titanic/npcs/deskbot.cpp
+++ b/engines/titanic/npcs/deskbot.cpp
@@ -176,12 +176,12 @@ bool CDeskbot::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) {
switch (_classNum) {
case 1:
- petDisplayMessage("You have been upgraded to 1st Class status. Enjoy hugely.");
+ petDisplayMessage(UPGRADED_TO_FIRST_CLASS);
setPassengerClass(_classNum);
petReassignRoom(_classNum);
break;
case 2:
- petDisplayMessage("You have been upgraded to 2nd Class status. Enjoy.");
+ petDisplayMessage(UPGRADED_TO_SECOND_CLASS);
setPassengerClass(_classNum);
petReassignRoom(_classNum);
break;
diff --git a/engines/titanic/npcs/succubus.cpp b/engines/titanic/npcs/succubus.cpp
index 5588d862c3..453f016757 100644
--- a/engines/titanic/npcs/succubus.cpp
+++ b/engines/titanic/npcs/succubus.cpp
@@ -274,7 +274,7 @@ bool CSuccUBus::SubAcceptCCarryMsg(CSubAcceptCCarryMsg *msg) {
if (!_enabled || !pet || !item || !tempRect.contains(item->getControid())) {
item->petAddToInventory();
} else if (mailExists(roomFlags)) {
- petDisplayMessage("The Succ-U-Bus is a Single Entity Delivery Device.");
+ petDisplayMessage(SUCCUBUS_DESCRIPTION);
item->petAddToInventory();
} else {
petContainerRemove(item);
@@ -337,7 +337,7 @@ bool CSuccUBus::EnterViewMsg(CEnterViewMsg *msg) {
}
bool CSuccUBus::LeaveViewMsg(CLeaveViewMsg *msg) {
- petDisplayMessage(2, "");
+ petDisplayMessage(2, BLANK);
if (_startFrame8 >= 0)
loadFrame(_startFrame8);
else if (!_field15C && _startFrame9 >= 0)
@@ -370,7 +370,7 @@ bool CSuccUBus::PETDeliverMsg(CPETDeliverMsg *msg) {
return true;
if (!_enabled) {
- petDisplayMessage(2, "The Succ-U-Bus is in Standby, or \"Off\" mode at present.");
+ petDisplayMessage(2, SUCCUBUS_IS_IN_STANDBY);
return true;
}
@@ -394,7 +394,7 @@ bool CSuccUBus::PETDeliverMsg(CPETDeliverMsg *msg) {
break;
}
- petDisplayMessage(2, "There is currently nothing in the tray to send.");
+ petDisplayMessage(2, NOTHING_IN_SUCCUBUS_TRAY);
} else {
_field19C = 0;
@@ -458,7 +458,7 @@ bool CSuccUBus::PETReceiveMsg(CPETReceiveMsg *msg) {
if (_field1D8 || !pet)
return true;
if (!_enabled) {
- petDisplayMessage(2, "The Succ-U-Bus is in Standby, or \"Off\" mode at present.");
+ petDisplayMessage(2, SUCCUBUS_IS_IN_STANDBY);
return true;
}
@@ -489,7 +489,7 @@ bool CSuccUBus::PETReceiveMsg(CPETReceiveMsg *msg) {
playMovie(_startFrame6, _endFrame6, 0);
playMovie(_startFrame7, _endFrame7, 0);
- petDisplayMessage(2, "There is currently nothing to deliver.");
+ petDisplayMessage(2, NOTHING_TO_DELIVER);
} else {
startTalking(this, 230004, findView());
diff --git a/engines/titanic/pet_control/pet_control.cpp b/engines/titanic/pet_control/pet_control.cpp
index 7ab76ddc1d..2ab30af5de 100644
--- a/engines/titanic/pet_control/pet_control.cpp
+++ b/engines/titanic/pet_control/pet_control.cpp
@@ -376,7 +376,13 @@ bool CPetControl::checkDragEnd(CGameObject *item) const {
return _sections[_currentArea]->checkDragEnd(item);
}
-void CPetControl::displayMessage(const CString &msg) const {
+void CPetControl::displayMessage(StringId stringId, int param) const {
+ CString msg = CString::format(_strings[stringId].c_str(), param);
+ _sections[_currentArea]->displayMessage(msg);
+}
+
+void CPetControl::displayMessage(const CString &str, int param) const {
+ CString msg = CString::format(str.c_str(), param);
_sections[_currentArea]->displayMessage(msg);
}
diff --git a/engines/titanic/pet_control/pet_control.h b/engines/titanic/pet_control/pet_control.h
index 439a94e2d3..c4b65457e2 100644
--- a/engines/titanic/pet_control/pet_control.h
+++ b/engines/titanic/pet_control/pet_control.h
@@ -36,6 +36,7 @@
#include "titanic/pet_control/pet_real_life.h"
#include "titanic/pet_control/pet_remote.h"
#include "titanic/pet_control/pet_rooms.h"
+#include "titanic/support/strings.h"
#include "titanic/room_flags.h"
namespace Titanic {
@@ -67,6 +68,7 @@ private:
CRoomItem *_hiddenRoom;
Rect _drawBounds;
PetEventInfo _timers[2];
+ Strings _strings;
private:
/**
* Returns true if the control is in a valid state
@@ -232,7 +234,12 @@ public:
/**
* Display a message
*/
- void displayMessage(const CString &msg) const;
+ void displayMessage(StringId stringId, int param = 0) const;
+
+ /**
+ * Display a message
+ */
+ void displayMessage(const CString &str, int param = 0) const;
/**
* Get the first game object stored in the PET
diff --git a/engines/titanic/pet_control/pet_conversations.cpp b/engines/titanic/pet_control/pet_conversations.cpp
index dc096afcfe..173c586fc1 100644
--- a/engines/titanic/pet_control/pet_conversations.cpp
+++ b/engines/titanic/pet_control/pet_conversations.cpp
@@ -419,7 +419,7 @@ int CPetConversations::canSummonBot(const CString &name) {
void CPetConversations::summonBot(const CString &name) {
if (_petControl) {
if (_petControl->getPassengerClass() >= 4) {
- _petControl->displayMessage("Sorry, you must be at least 3rd class before you can summon for help.");
+ _petControl->displayMessage(AT_LEAST_3RD_CLASS_FOR_HELP);
} else {
_petControl->summonBot(name, 0);
}
diff --git a/engines/titanic/pet_control/pet_load.cpp b/engines/titanic/pet_control/pet_load.cpp
index 04eec54f25..8e2afc727f 100644
--- a/engines/titanic/pet_control/pet_load.cpp
+++ b/engines/titanic/pet_control/pet_load.cpp
@@ -63,7 +63,7 @@ void CPetLoad::execute() {
// WORKAROUND: Schedule the savegame to be loaded after frame rendering ends
window->loadGame(_savegameSlotNum);
} else if (pet) {
- pet->displayMessage("You must select a game to load first.");
+ pet->displayMessage(SELECT_GAME_TO_LOAD);
}
}
diff --git a/engines/titanic/pet_control/pet_save.cpp b/engines/titanic/pet_control/pet_save.cpp
index 9305759117..6d0733f534 100644
--- a/engines/titanic/pet_control/pet_save.cpp
+++ b/engines/titanic/pet_control/pet_save.cpp
@@ -76,10 +76,10 @@ void CPetSave::execute() {
if (project) {
project->saveGame(_savegameSlotNum, _slotNames[_savegameSlotNum].getText());
- pet->displayMessage("");
+ pet->displayMessage(BLANK);
}
} else if (pet) {
- pet->displayMessage("You must select a game to save first.");
+ pet->displayMessage(SELECT_GAME_TO_SAVE);
}
}
diff --git a/engines/titanic/pet_control/pet_starfield.cpp b/engines/titanic/pet_control/pet_starfield.cpp
index 34d696e09c..3287f98817 100644
--- a/engines/titanic/pet_control/pet_starfield.cpp
+++ b/engines/titanic/pet_control/pet_starfield.cpp
@@ -91,7 +91,7 @@ bool CPetStarfield::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
CPETPhotoOnOffMsg photoMsg;
photoMsg.execute(_petControl->_remoteTarget);
} else {
- _petControl->displayMessage("Please supply Galactic reference material.");
+ _petControl->displayMessage(SUPPLY_GALACTIC_REFERENCE);
}
} else if (!_btnSetDest.MouseButtonDownMsg(msg->_mousePos)) {
return elementsMouseDown(msg);
diff --git a/engines/titanic/support/strings.cpp b/engines/titanic/support/strings.cpp
new file mode 100644
index 0000000000..7664b6849b
--- /dev/null
+++ b/engines/titanic/support/strings.cpp
@@ -0,0 +1,35 @@
+/* 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.
+ *
+ */
+
+#include "titanic/support/strings.h"
+#include "titanic/titanic.h"
+
+namespace Titanic {
+
+Strings::Strings() {
+ Common::SeekableReadStream *r = g_vm->_filesManager->getResource("TEXT/STRINGS");
+ while (r->pos() < r->size())
+ push_back(readStringFromStream(r));
+ delete r;
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/support/strings.h b/engines/titanic/support/strings.h
new file mode 100644
index 0000000000..1f7187bdee
--- /dev/null
+++ b/engines/titanic/support/strings.h
@@ -0,0 +1,98 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#ifndef TITANIC_STRINGS_H
+#define TITANIC_STRINGS_H
+
+#include "common/str-array.h"
+
+namespace Titanic {
+
+enum StringId {
+ BLANK,
+ STANDING_OUTSIDE_PELLERATOR,
+ BOT_BLOCKING_PELLERATOR,
+ SUCCUBUS_IS_IN_STANDBY,
+ NOTHING_TO_DELIVER,
+ NOTHING_IN_SUCCUBUS_TRAY,
+ SUCCUBUS_SINGLE_DELIVERY,
+ ONE_ALLOCATED_CHICKEN_PER_CUSTOMER,
+ ONE_CHICKEN_PER_CUSTOMER,
+ UPGRADED_TO_FIRST_CLASS,
+ UPGRADED_TO_SECOND_CLASS,
+ ROOM_RESERVED_FOR_FIRST_CLASS,
+ NO_LOSERS,
+ CLASS_NOT_PERMITTED_IN_AREA,
+ EXIT_FROM_OTHER_SIDE,
+ TRANSPORT_OUT_OF_ORDER,
+ FAN_HAS_BLOWN_A_FUSE,
+ POKE_WITH_LONG_STICK,
+ TOO_SHORT_TO_REACH_BRANCHES,
+ OUTSIDE_ELEVATOR_NUM,
+ BOT_BLOCKING_ELEVATOR,
+ ELEVATOR_NON_FUNCTIONAL,
+ LIGHT_IS_LOOSE,
+ LUMI_GLOW_LIGHTS,
+ ALREADY_HAVE_STICK,
+ GLASS_IS_UNBREAKABLE,
+ FOR_STICK_BREAK_GLASS,
+ DISPENSOR_HAS_UNBREAKABLE_GLASS,
+ CHICKEN_IS_CLEAN,
+ ADJUST_VIEWING_APPARATUS,
+ CANNOT_TAKE_CAGE_LOCKED,
+ ALREADY_AT_DESTINATION,
+ CLASS_NOT_ALLOWED_AT_DEST,
+ AT_LEAST_3RD_CLASS_FOR_HELP,
+ NO_ROOM_ASSIGNED,
+ ELEVATOR_NOT_BELOW_27,
+ SELECT_GAME_TO_LOAD,
+ SELECT_GAME_TO_SAVE,
+ SUPPLY_GALACTIC_REFERENCE,
+ LOCKED_MUSIC_SYSTEM,
+ STUCK_TO_BRANCH,
+ FROZEN_TO_BRANCH,
+ CHECK_IN_AT_RECEPTION,
+ FOODSTUFF_ALREADY_GARNISHED,
+ DISPENSOR_IS_EMPTY,
+ PUT_FOOD_UNDER_DISPENSOR,
+ SEASONAL_SWITCH_NOT_WORKING,
+ YOUR_STATEROOM,
+ BED_NOT_SUPPORT_YOUR_WEIGHT,
+ NOT_YOUR_ASSIGNED_ROOM,
+ OUT_OF_REACH,
+ SUCCUBUS_DESCRIPTION,
+ CANAL_CLOSED_FOR_WINTER,
+ AREA_OFF_LIMIT_TO_PASSENGERS,
+ GO_WHERE,
+ NICE_IF_TAKE_BUT_CANT,
+ BOWL_OF_NUTS,
+ NOT_A_BOWL_OF_NUTS
+};
+
+class Strings : public Common::StringArray {
+public:
+ Strings();
+};
+
+} // End of namespace Titanic
+
+#endif /* TITANIC_STRINGS_H */
diff --git a/engines/xeen/character.cpp b/engines/xeen/character.cpp
new file mode 100644
index 0000000000..b0502d254a
--- /dev/null
+++ b/engines/xeen/character.cpp
@@ -0,0 +1,1840 @@
+/* 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.
+ *
+ */
+
+#include "xeen/character.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+XeenItem::XeenItem() {
+ clear();
+}
+
+void XeenItem::clear() {
+ _material = _id = _bonusFlags = 0;
+ _frame = 0;
+}
+
+void XeenItem::synchronize(Common::Serializer &s) {
+ s.syncAsByte(_material);
+ s.syncAsByte(_id);
+ s.syncAsByte(_bonusFlags);
+ s.syncAsByte(_frame);
+}
+
+ElementalCategory XeenItem::getElementalCategory() const {
+ int idx;
+ for (idx = 0; ELEMENTAL_CATEGORIES[idx] < _material; ++idx)
+ ;
+
+ return (ElementalCategory)idx;
+}
+
+AttributeCategory XeenItem::getAttributeCategory() const {
+ int m = _material - 59;
+ int idx;
+ for (idx = 0; ATTRIBUTE_CATEGORIES[idx] < m; ++idx)
+ ;
+
+ return (AttributeCategory)idx;
+}
+
+/*------------------------------------------------------------------------*/
+
+InventoryItems::InventoryItems(Character *character, ItemCategory category):
+ _character(character), _category(category) {
+ resize(INV_ITEMS_TOTAL);
+
+ static const char *const *NAMES[4] = {
+ WEAPON_NAMES, ARMOR_NAMES, ACCESSORY_NAMES, MISC_NAMES
+ };
+ _names = NAMES[category];
+}
+
+void InventoryItems::clear() {
+ for (uint idx = 0; idx < size(); ++idx)
+ operator[](idx).clear();
+}
+
+bool InventoryItems::passRestrictions(int itemId, bool showError) const {
+ CharacterClass charClass = _character->_class;
+
+ switch (charClass) {
+ case CLASS_KNIGHT:
+ case CLASS_PALADIN:
+ return true;
+
+ case CLASS_ARCHER:
+ case CLASS_CLERIC:
+ case CLASS_SORCERER:
+ case CLASS_ROBBER:
+ case CLASS_NINJA:
+ case CLASS_BARBARIAN:
+ case CLASS_DRUID:
+ case CLASS_RANGER: {
+ if (!(ITEM_RESTRICTIONS[itemId + RESTRICTION_OFFSETS[_category]] &
+ (1 << (charClass - CLASS_ARCHER))))
+ return true;
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ Common::String name = _names[itemId];
+ if (showError) {
+ Common::String msg = Common::String::format(NOT_PROFICIENT,
+ CLASS_NAMES[charClass], name.c_str());
+ ErrorScroll::show(Party::_vm, msg, WT_FREEZE_WAIT);
+ }
+
+ return false;
+}
+
+Common::String InventoryItems::getName(int itemIndex) {
+ int id = operator[](itemIndex)._id;
+ return _names[id];
+}
+
+Common::String InventoryItems::getIdentifiedDetails(int itemIndex) {
+ XeenItem &item = operator[](itemIndex);
+
+ Common::String classes;
+ for (int charClass = CLASS_KNIGHT; charClass <= CLASS_RANGER; ++charClass) {
+ if (passRestrictions(charClass, true)) {
+ const char *const name = CLASS_NAMES[charClass];
+ classes += name[0];
+ classes += name[1];
+ classes += " ";
+ }
+ }
+ if (classes.size() == 30)
+ classes = ALL;
+
+ return getAttributes(item, classes);
+}
+
+bool InventoryItems::discardItem(int itemIndex) {
+ XeenItem &item = operator[](itemIndex);
+ XeenEngine *vm = Party::_vm;
+
+ if (item._bonusFlags & ITEMFLAG_CURSED) {
+ ErrorScroll::show(vm, CANNOT_DISCARD_CURSED_ITEM);
+ } else {
+ Common::String itemDesc = getFullDescription(itemIndex, 4);
+ Common::String msg = Common::String::format(PERMANENTLY_DISCARD, itemDesc.c_str());
+
+ if (Confirm::show(vm, msg)) {
+ operator[](itemIndex).clear();
+ sort();
+
+ return true;
+ }
+ }
+
+ return true;
+}
+
+void InventoryItems::sort() {
+ for (uint idx = 0; idx < size(); ++idx) {
+ if (operator[](idx)._id == 0) {
+ // Found empty slot
+ operator[](idx).clear();
+
+ // Scan through the rest of the list to find any item
+ for (uint idx2 = idx + 1; idx2 < size(); ++idx2) {
+ if (operator[](idx2)._id) {
+ // Found an item, so move it into the blank slot
+ operator[](idx) = operator[](idx2);
+ operator[](idx2).clear();
+ break;
+ }
+ }
+ }
+ }
+}
+
+void InventoryItems::removeItem(int itemIndex) {
+ XeenItem &item = operator[](itemIndex);
+ XeenEngine *vm = Party::_vm;
+
+ if (item._bonusFlags & ITEMFLAG_CURSED)
+ ErrorScroll::show(vm, CANNOT_REMOVE_CURSED_ITEM);
+ else
+ item._frame = 0;
+}
+
+XeenEngine *InventoryItems::getVm() {
+ return Party::_vm;
+}
+
+void InventoryItems::equipError(int itemIndex1, ItemCategory category1, int itemIndex2,
+ ItemCategory category2) {
+ XeenEngine *vm = Party::_vm;
+
+ if (itemIndex1 >= 0) {
+ Common::String itemName1 = _character->_items[category1].getName(itemIndex1);
+ Common::String itemName2 = _character->_items[category2].getName(itemIndex2);
+
+ ErrorDialog::show(vm, Common::String::format(REMOVE_X_TO_EQUIP_Y,
+ itemName1.c_str(), itemName2.c_str()));
+ } else {
+ ErrorDialog::show(vm, Common::String::format(EQUIPPED_ALL_YOU_CAN,
+ (itemIndex1 == -1) ? RING : MEDAL));
+ }
+}
+
+void InventoryItems::enchantItem(int itemIndex, int amount) {
+ XeenEngine *vm = Party::_vm;
+ vm->_sound->playFX(21);
+ ErrorScroll::show(vm, Common::String::format(NOT_ENCHANTABLE, SPELL_FAILED));
+}
+
+bool InventoryItems::isFull() const {
+ return operator[](size() - 1)._id != 0;
+}
+
+/*------------------------------------------------------------------------*/
+
+void WeaponItems::equipItem(int itemIndex) {
+ XeenItem &item = operator[](itemIndex);
+
+ if (item._id <= 17) {
+ if (passRestrictions(item._id, false)) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 13 || i._frame == 1) {
+ equipError(itemIndex, CATEGORY_WEAPON, idx, CATEGORY_WEAPON);
+ return;
+ }
+ }
+
+ item._frame = 1;
+ }
+ } else if (item._id >= 30 && item._id <= 33) {
+ if (passRestrictions(item._id, false)) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 4) {
+ equipError(itemIndex, CATEGORY_WEAPON, idx, CATEGORY_WEAPON);
+ return;
+ }
+ }
+
+ item._frame = 4;
+ }
+ } else {
+ if (passRestrictions(item._id, false)) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 13 || i._frame == 1) {
+ equipError(itemIndex, CATEGORY_WEAPON, idx, CATEGORY_WEAPON);
+ return;
+ }
+ }
+
+ for (uint idx = 0; idx < _character->_armor.size(); ++idx) {
+ XeenItem &i = _character->_armor[idx];
+ if (i._frame == 2) {
+ equipError(itemIndex, CATEGORY_WEAPON, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ item._frame = 13;
+ }
+ }
+}
+
+Common::String WeaponItems::getFullDescription(int itemIndex, int displayNum) {
+ XeenItem &i = operator[](itemIndex);
+ Resources &res = *getVm()->_resources;
+
+ return Common::String::format("\f%02u%s%s%s\f%02u%s%s%s", displayNum,
+ !i._bonusFlags ? res._maeNames[i._material].c_str() : "",
+ (i._bonusFlags & ITEMFLAG_BROKEN) ? ITEM_BROKEN : "",
+ (i._bonusFlags & ITEMFLAG_CURSED) ? ITEM_CURSED : "",
+ displayNum,
+ WEAPON_NAMES[i._id],
+ !i._bonusFlags ? "" : BONUS_NAMES[i._bonusFlags & ITEMFLAG_BONUS_MASK],
+ (i._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED)) ||
+ !i._bonusFlags ? "\b " : ""
+ );
+}
+
+void WeaponItems::enchantItem(int itemIndex, int amount) {
+ SoundManager &sound = *getVm()->_sound;
+ XeenItem &item = operator[](itemIndex);
+ Character tempCharacter;
+
+ if (item._material == 0 && item._bonusFlags == 0 && item._id != 34) {
+ tempCharacter.makeItem(amount, 0, 1);
+ XeenItem &tempItem = tempCharacter._weapons[0];
+
+ item._material = tempItem._material;
+ item._bonusFlags = tempItem._bonusFlags;
+ sound.playFX(19);
+ } else {
+ InventoryItems::enchantItem(itemIndex, amount);
+ }
+}
+
+/*
+ * Returns a text string listing all the stats/attributes of a given item
+ */
+Common::String WeaponItems::getAttributes(XeenItem &item, const Common::String &classes) {
+ Common::String attrBonus, elemDamage, physDamage, toHit, specialPower;
+ attrBonus = elemDamage = physDamage = toHit = specialPower = FIELD_NONE;
+
+ // First calculate physical damage
+ int minVal = WEAPON_DAMAGE_BASE[item._id];
+ int maxVal = minVal * WEAPON_DAMAGE_MULTIPLIER[item._id];
+
+ if (item._material >= 37 && item._material <= 58) {
+ minVal += METAL_DAMAGE[item._material - 37];
+ maxVal += METAL_DAMAGE[item._material - 37];
+ toHit = Common::String::format("%+d", METAL_DAMAGE_PERCENT[item._material - 37]);
+ }
+
+ physDamage = Common::String::format(DAMAGE_X_TO_Y, minVal, maxVal);
+
+ // Next handle elemental/attribute damage
+ if (item._material < 37) {
+ int damage = ELEMENTAL_DAMAGE[item._material];
+ if (damage > 0) {
+ ElementalCategory elemCategory = item.getElementalCategory();
+ elemDamage = Common::String::format(ELEMENTAL_XY_DAMAGE,
+ damage, ELEMENTAL_NAMES[elemCategory]);
+ }
+ } else if (item._material >= 59) {
+ int bonus = ATTRIBUTE_BONUSES[item._material - 59];
+ AttributeCategory attrCategory = item.getAttributeCategory();
+ attrBonus = Common::String::format(ATTR_XY_BONUS, bonus,
+ ATTRIBUTE_NAMES[attrCategory]);
+ }
+
+ // Handle weapon effective against
+ int effective = item._bonusFlags & ITEMFLAG_BONUS_MASK;
+ if (effective) {
+ specialPower = Common::String::format(EFFECTIVE_AGAINST,
+ EFFECTIVENESS_NAMES[effective]);
+ }
+
+ return Common::String::format(ITEM_DETAILS, classes.c_str(),
+ toHit.c_str(), physDamage.c_str(), elemDamage.c_str(),
+ FIELD_NONE, FIELD_NONE, attrBonus.c_str(), specialPower.c_str()
+ );
+}
+
+/*------------------------------------------------------------------------*/
+
+void ArmorItems::equipItem(int itemIndex) {
+ XeenItem &item = operator[](itemIndex);
+
+ if (item._id <= 7) {
+ if (passRestrictions(item._id, false)) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 9) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ item._frame = 3;
+ }
+ } else if (item._id == 8) {
+ if (passRestrictions(item._id, false)) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 2) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ for (uint idx = 0; idx < _character->_weapons.size(); ++idx) {
+ XeenItem &i = _character->_weapons[idx];
+ if (i._frame == 13) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_WEAPON);
+ return;
+ }
+ }
+
+ item._frame = 2;
+ }
+ } else if (item._id == 9) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 5) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ item._frame = 5;
+ } else if (item._id == 10) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 9) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ item._frame = 9;
+ } else if (item._id <= 12) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 10) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ item._frame = 10;
+ } else {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 6) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ item._frame = 6;
+ }
+}
+
+Common::String ArmorItems::getFullDescription(int itemIndex, int displayNum) {
+ XeenItem &i = operator[](itemIndex);
+ Resources &res = *getVm()->_resources;
+
+ return Common::String::format("\f%02u%s%s%s\f%02u%s%s", displayNum,
+ !i._bonusFlags ? "" : res._maeNames[i._material].c_str(),
+ (i._bonusFlags & ITEMFLAG_BROKEN) ? ITEM_BROKEN : "",
+ (i._bonusFlags & ITEMFLAG_CURSED) ? ITEM_CURSED : "",
+ displayNum,
+ ARMOR_NAMES[i._id],
+ (i._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED)) ||
+ !i._bonusFlags ? "\b " : ""
+ );
+}
+
+void ArmorItems::enchantItem(int itemIndex, int amount) {
+ SoundManager &sound = *getVm()->_sound;
+ XeenItem &item = operator[](itemIndex);
+ Character tempCharacter;
+
+ if (item._material == 0 && item._bonusFlags == 0) {
+ tempCharacter.makeItem(amount, 0, 2);
+ XeenItem &tempItem = tempCharacter._armor[0];
+
+ item._material = tempItem._material;
+ item._bonusFlags = tempItem._bonusFlags;
+ sound.playFX(19);
+ } else {
+ InventoryItems::enchantItem(itemIndex, amount);
+ }
+}
+
+/*
+* Returns a text string listing all the stats/attributes of a given item
+*/
+Common::String ArmorItems::getAttributes(XeenItem &item, const Common::String &classes) {
+ Common::String elemResist, attrBonus, acBonus;
+ elemResist = attrBonus = acBonus = FIELD_NONE;
+
+ if (item._material < 36) {
+ int resistence = ELEMENTAL_RESISTENCES[item._material];
+ if (resistence > 0) {
+ int eCategory = ELEM_FIRE;
+ while (eCategory < ELEM_MAGIC && ELEMENTAL_CATEGORIES[eCategory] < item._material)
+ ++eCategory;
+
+ elemResist = Common::String::format(ATTR_XY_BONUS, resistence,
+ ELEMENTAL_NAMES[eCategory]);
+ }
+ } else if (item._material >= 59) {
+ int bonus = ATTRIBUTE_BONUSES[item._material - 59];
+ AttributeCategory aCategory = item.getAttributeCategory();
+ attrBonus = Common::String::format(ATTR_XY_BONUS, bonus,
+ ATTRIBUTE_NAMES[aCategory]);
+ }
+
+ int strength = ARMOR_STRENGTHS[item._id];
+ if (item._material >= 37 && item._material <= 58) {
+ strength += METAL_LAC[item._material - 37];
+ }
+ acBonus = Common::String::format("%+d", strength);
+
+ return Common::String::format(ITEM_DETAILS, classes.c_str(),
+ FIELD_NONE, FIELD_NONE, FIELD_NONE,
+ elemResist.c_str(), acBonus.c_str(), attrBonus.c_str(), FIELD_NONE);
+}
+
+/*------------------------------------------------------------------------*/
+
+void AccessoryItems::equipItem(int itemIndex) {
+ XeenItem &item = operator[](itemIndex);
+
+ if (item._id == 1) {
+ int count = 0;
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 8)
+ ++count;
+ }
+
+ if (count <= 1)
+ item._frame = 8;
+ else
+ equipError(-1, CATEGORY_ACCESSORY, itemIndex, CATEGORY_ACCESSORY);
+ } else if (item._id == 2) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 12) {
+ equipError(itemIndex, CATEGORY_ACCESSORY, idx, CATEGORY_ACCESSORY);
+ return;
+ }
+ }
+ } else if (item._id <= 7) {
+ int count = 0;
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 7)
+ ++count;
+ }
+
+ if (count <= 1)
+ item._frame = 7;
+ else
+ equipError(-2, CATEGORY_ACCESSORY, itemIndex, CATEGORY_ACCESSORY);
+ } else {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 11) {
+ equipError(itemIndex, CATEGORY_ACCESSORY, idx, CATEGORY_ACCESSORY);
+ return;
+ }
+ }
+
+ item._frame = 11;
+ }
+}
+
+Common::String AccessoryItems::getFullDescription(int itemIndex, int displayNum) {
+ XeenItem &i = operator[](itemIndex);
+ Resources &res = *getVm()->_resources;
+
+ return Common::String::format("\f%02u%s%s%s\f%02u%s%s", displayNum,
+ !i._bonusFlags ? "" : res._maeNames[i._material].c_str(),
+ (i._bonusFlags & ITEMFLAG_BROKEN) ? ITEM_BROKEN : "",
+ (i._bonusFlags & ITEMFLAG_CURSED) ? ITEM_CURSED : "",
+ displayNum,
+ ARMOR_NAMES[i._id],
+ (i._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED)) ||
+ !i._bonusFlags ? "\b " : ""
+ );
+}
+
+/*
+* Returns a text string listing all the stats/attributes of a given item
+*/
+Common::String AccessoryItems::getAttributes(XeenItem &item, const Common::String &classes) {
+ Common::String elemResist, attrBonus;
+ elemResist = attrBonus = FIELD_NONE;
+
+ if (item._material < 36) {
+ int resistence = ELEMENTAL_RESISTENCES[item._material];
+ if (resistence > 0) {
+ int eCategory = ELEM_FIRE;
+ while (eCategory < ELEM_MAGIC && ELEMENTAL_CATEGORIES[eCategory] < item._material)
+ ++eCategory;
+
+ elemResist = Common::String::format(ATTR_XY_BONUS, resistence,
+ ELEMENTAL_NAMES[eCategory]);
+ }
+ } else if (item._material >= 59) {
+ int bonus = ATTRIBUTE_BONUSES[item._material - 59];
+ AttributeCategory aCategory = item.getAttributeCategory();
+ attrBonus = Common::String::format(ATTR_XY_BONUS, bonus,
+ ATTRIBUTE_NAMES[aCategory]);
+ }
+
+ return Common::String::format(ITEM_DETAILS, classes.c_str(),
+ FIELD_NONE, FIELD_NONE, FIELD_NONE,
+ elemResist.c_str(), FIELD_NONE, attrBonus.c_str(), FIELD_NONE);
+}
+
+/*------------------------------------------------------------------------*/
+
+Common::String MiscItems::getFullDescription(int itemIndex, int displayNum) {
+ XeenItem &i = operator[](itemIndex);
+ Resources &res = *getVm()->_resources;
+
+ return Common::String::format("\f%02u%s%s%s\f%02u%s%s", displayNum,
+ !i._bonusFlags ? "" : res._maeNames[i._material].c_str(),
+ (i._bonusFlags & ITEMFLAG_BROKEN) ? ITEM_BROKEN : "",
+ (i._bonusFlags & ITEMFLAG_CURSED) ? ITEM_CURSED : "",
+ displayNum,
+ ARMOR_NAMES[i._id],
+ (i._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED)) ||
+ !i._id ? "\b " : ""
+ );
+}
+
+
+/*
+* Returns a text string listing all the stats/attributes of a given item
+*/
+Common::String MiscItems::getAttributes(XeenItem &item, const Common::String &classes) {
+ Common::String specialPower = FIELD_NONE;
+ Spells &spells = *getVm()->_spells;
+
+ if (item._id) {
+ specialPower = spells._spellNames[MISC_SPELL_INDEX[item._id]];
+ }
+
+ return Common::String::format(ITEM_DETAILS, classes.c_str(),
+ FIELD_NONE, FIELD_NONE, FIELD_NONE, FIELD_NONE, FIELD_NONE,
+ FIELD_NONE, specialPower.c_str());
+}
+/*------------------------------------------------------------------------*/
+
+InventoryItemsGroup::InventoryItemsGroup(InventoryItems &weapons, InventoryItems &armor,
+ InventoryItems &accessories, InventoryItems &misc) {
+ _itemSets[0] = &weapons;
+ _itemSets[1] = &armor;
+ _itemSets[2] = &accessories;
+ _itemSets[3] = &misc;
+}
+
+InventoryItems &InventoryItemsGroup::operator[](ItemCategory category) {
+ return *_itemSets[category];
+}
+
+void InventoryItemsGroup::breakAllItems() {
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ if ((*_itemSets[0])[idx]._id != 34) {
+ (*_itemSets[0])[idx]._bonusFlags |= ITEMFLAG_BROKEN;
+ (*_itemSets[0])[idx]._frame = 0;
+ }
+
+ (*_itemSets[1])[idx]._bonusFlags |= ITEMFLAG_BROKEN;
+ (*_itemSets[2])[idx]._bonusFlags |= ITEMFLAG_BROKEN;
+ (*_itemSets[3])[idx]._bonusFlags |= ITEMFLAG_BROKEN;
+ (*_itemSets[1])[idx]._frame = 0;
+ (*_itemSets[2])[idx]._frame = 0;
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+
+void AttributePair::synchronize(Common::Serializer &s) {
+ s.syncAsByte(_permanent);
+ s.syncAsByte(_temporary);
+}
+
+/*------------------------------------------------------------------------*/
+
+AttributePair::AttributePair() {
+ _temporary = _permanent = 0;
+}
+
+/*------------------------------------------------------------------------*/
+
+Character::Character():
+ _weapons(this), _armor(this), _accessories(this), _misc(this),
+ _items(_weapons, _armor, _accessories, _misc) {
+ clear();
+ _faceSprites = nullptr;
+ _rosterId = -1;
+}
+
+void Character::clear() {
+ _sex = MALE;
+ _race = HUMAN;
+ _xeenSide = 0;
+ _class = CLASS_KNIGHT;
+ _ACTemp = 0;
+ _birthDay = 0;
+ _tempAge = 0;
+ Common::fill(&_skills[0], &_skills[18], 0);
+ Common::fill(&_awards[0], &_awards[128], false);
+ Common::fill(&_spells[0], &_spells[39], 0);
+ _lloydMap = 0;
+ _hasSpells = false;
+ _currentSpell = 0;
+ _quickOption = QUICK_ATTACK;
+ _lloydSide = 0;
+ Common::fill(&_conditions[0], &_conditions[16], 0);
+ _townUnknown = 0;
+ _savedMazeId = 0;
+ _currentHp = 0;
+ _currentSp = 0;
+ _birthYear = 0;
+ _experience = 0;
+ _currentAdventuringSpell = 0;
+ _currentCombatSpell = 0;
+
+ _might._permanent = _might._temporary = 0;
+ _intellect._permanent = _intellect._temporary = 0;
+ _personality._permanent = _personality._temporary = 0;
+ _endurance._permanent = _endurance._temporary = 0;
+ _speed._permanent = _speed._temporary = 0;
+ _accuracy._permanent = _accuracy._temporary = 0;
+ _luck._permanent = _luck._temporary = 0;
+ _fireResistence._permanent = _fireResistence._temporary = 0;
+ _coldResistence._permanent = _coldResistence._temporary = 0;
+ _electricityResistence._permanent = _electricityResistence._temporary = 0;
+ _poisonResistence._permanent = _poisonResistence._temporary = 0;
+ _energyResistence._permanent = _energyResistence._temporary = 0;
+ _magicResistence._permanent = _magicResistence._temporary = 0;
+ _weapons.clear();
+ _armor.clear();
+ _accessories.clear();
+ _misc.clear();
+}
+
+void Character::synchronize(Common::Serializer &s) {
+ char name[16];
+ Common::fill(&name[0], &name[16], '\0');
+ Common::strlcpy(name, _name.c_str(), 16);
+ s.syncBytes((byte *)name, 16);
+
+ if (s.isLoading())
+ _name = Common::String(name);
+
+ s.syncAsByte(_sex);
+ s.syncAsByte(_race);
+ s.syncAsByte(_xeenSide);
+ s.syncAsByte(_class);
+
+ _might.synchronize(s);
+ _intellect.synchronize(s);
+ _personality.synchronize(s);
+ _endurance.synchronize(s);
+ _speed.synchronize(s);
+ _accuracy.synchronize(s);
+ _luck.synchronize(s);
+ s.syncAsByte(_ACTemp);
+ _level.synchronize(s);
+ s.syncAsByte(_birthDay);
+ s.syncAsByte(_tempAge);
+
+ // Synchronize the skill list
+ for (int idx = 0; idx < 18; ++idx)
+ s.syncAsByte(_skills[idx]);
+
+ // Synchronize character awards
+ for (int idx = 0; idx < 64; ++idx) {
+ byte b = (_awards[idx] ? 1 : 0) | (_awards[idx + 64] ? 0x10 : 0);
+ s.syncAsByte(b);
+ if (s.isLoading()) {
+ _awards[idx] = (b & 0xF) != 0;
+ _awards[idx + 64] = (b & 0xF0) != 0;
+ }
+ }
+
+ // Synchronize spell list
+ for (int i = 0; i < MAX_SPELLS_PER_CLASS - 1; ++i)
+ s.syncAsByte(_spells[i]);
+ s.syncAsByte(_lloydMap);
+ s.syncAsByte(_lloydPosition.x);
+ s.syncAsByte(_lloydPosition.y);
+ s.syncAsByte(_hasSpells);
+ s.syncAsByte(_currentSpell);
+ s.syncAsByte(_quickOption);
+
+ for (int i = 0; i < 9; ++i)
+ _weapons[i].synchronize(s);
+ for (int i = 0; i < 9; ++i)
+ _armor[i].synchronize(s);
+ for (int i = 0; i < 9; ++i)
+ _accessories[i].synchronize(s);
+ for (int i = 0; i < 9; ++i)
+ _misc[i].synchronize(s);
+
+ s.syncAsByte(_lloydSide);
+ _fireResistence.synchronize(s);
+ _coldResistence.synchronize(s);
+ _electricityResistence.synchronize(s);
+ _poisonResistence.synchronize(s);
+ _energyResistence.synchronize(s);
+ _magicResistence.synchronize(s);
+
+ for (int i = 0; i < 16; ++i)
+ s.syncAsByte(_conditions[i]);
+
+ s.syncAsUint16LE(_townUnknown);
+ s.syncAsByte(_savedMazeId);
+ s.syncAsUint16LE(_currentHp);
+ s.syncAsUint16LE(_currentSp);
+ s.syncAsUint16LE(_birthYear);
+ s.syncAsUint32LE(_experience);
+ s.syncAsByte(_currentAdventuringSpell);
+ s.syncAsByte(_currentCombatSpell);
+}
+
+Condition Character::worstCondition() const {
+ for (int cond = ERADICATED; cond >= CURSED; --cond) {
+ if (_conditions[cond])
+ return (Condition)cond;
+ }
+
+ return NO_CONDITION;
+}
+
+bool Character::isDisabled() const {
+ Condition condition = worstCondition();
+
+ return condition == ASLEEP || condition == PARALYZED || condition == UNCONSCIOUS
+ || condition == STONED || condition == ERADICATED;
+}
+
+bool Character::isDisabledOrDead() const {
+ Condition condition = worstCondition();
+
+ return condition == ASLEEP || (condition >= PARALYZED && condition <= ERADICATED);
+}
+
+bool Character::isDead() const {
+ Condition condition = worstCondition();
+
+ return condition >= DEAD && condition <= ERADICATED;
+}
+
+int Character::getAge(bool ignoreTemp) const {
+ int year = MIN(Party::_vm->_party->_year - _birthYear, (uint)254);
+
+ return ignoreTemp ? year : year + _tempAge;
+}
+
+int Character::getMaxHP() const {
+ int hp = BASE_HP_BY_CLASS[_class];
+ hp += statBonus(getStat(ENDURANCE));
+ hp += RACE_HP_BONUSES[_race];
+ if (_skills[BODYBUILDER])
+ ++hp;
+ if (hp < 1)
+ hp = 1;
+
+ hp *= getCurrentLevel();
+ hp += itemScan(7);
+
+ return MAX(hp, 0);
+}
+
+int Character::getMaxSP() const {
+ int result = 0;
+ bool flag = false;
+ int amount = 0;
+ Attribute attrib;
+ Skill skill;
+
+ if (!_hasSpells)
+ return 0;
+
+ if (_class == CLASS_SORCERER || _class == CLASS_ARCHER) {
+ attrib = INTELLECT;
+ skill = PRESTIDIGITATION;
+ } else {
+ attrib = PERSONALITY;
+ skill = PRAYER_MASTER;
+ }
+ if (_class == CLASS_DRUID || _class == CLASS_RANGER)
+ skill = ASTROLOGER;
+
+ for (;;) {
+ // Get the base number of spell points
+ result = statBonus(getStat(attrib)) + 3;
+ result += RACE_SP_BONUSES[_race][attrib - 1];
+
+ if (_skills[skill])
+ result += 2;
+ if (result < 1)
+ result = 1;
+
+ // Multiply it by the character's level
+ result *= getCurrentLevel();
+
+ // Classes other than sorcerer, clerics, and druids only get half the SP
+ if (_class != CLASS_SORCERER && _class != CLASS_CLERIC && _class != CLASS_DRUID)
+ result /= 2;
+
+ if (flag || (_class != CLASS_DRUID && _class != CLASS_RANGER))
+ break;
+
+ // Druids and rangers get bonuses averaged on both personality and intellect
+ attrib = INTELLECT;
+ flag = true;
+ amount = result;
+ }
+ if (flag)
+ result = (amount + result) / 2;
+
+ result += itemScan(8);
+ if (result < 0)
+ result = 0;
+
+ return result;
+}
+
+uint Character::getStat(Attribute attrib, bool baseOnly) const {
+ AttributePair attr;
+ int mode = 0;
+
+ switch (attrib) {
+ case MIGHT:
+ attr = _might;
+ break;
+ case INTELLECT:
+ attr = _intellect;
+ mode = 1;
+ break;
+ case PERSONALITY:
+ attr = _personality;
+ mode = 1;
+ break;
+ case ENDURANCE:
+ attr = _endurance;
+ break;
+ case SPEED:
+ attr = _speed;
+ break;
+ case ACCURACY:
+ attr = _accuracy;
+ break;
+ case LUCK:
+ attr = _luck;
+ mode = 2;
+ break;
+ default:
+ return 0;
+ }
+
+ // All the attributes except luck are affected by the character's age
+ if (mode < 2) {
+ int age = getAge(false);
+ int ageIndex = 0;
+ while (AGE_RANGES[ageIndex] <= age)
+ ++ageIndex;
+
+ attr._permanent += AGE_RANGES_ADJUST[mode][ageIndex];
+ }
+
+
+ attr._permanent += itemScan((int)attrib);
+
+ if (!baseOnly) {
+ attr._permanent += conditionMod(attrib);
+ attr._permanent += attr._temporary;
+ }
+
+ return MAX(attr._permanent, (uint)0);
+}
+
+int Character::statColor(int amount, int threshold) {
+ if (amount < 1)
+ return 6;
+ else if (amount > threshold)
+ return 2;
+ else if (amount == threshold)
+ return 15;
+ else if (amount <= (threshold / 4))
+ return 9;
+ else
+ return 32;
+}
+
+int Character::statBonus(uint statValue) const {
+ int idx;
+ for (idx = 0; STAT_VALUES[idx] <= statValue; ++idx)
+ ;
+
+ return STAT_BONUSES[idx];
+}
+
+bool Character::charSavingThrow(DamageType attackType) const {
+ int v, vMax;
+
+ if (attackType == DT_PHYSICAL) {
+ v = statBonus(getStat(LUCK)) + getCurrentLevel();
+ vMax = v + 20;
+ } else {
+ switch (attackType) {
+ case DT_MAGICAL:
+ v = _magicResistence._permanent + _magicResistence._temporary + itemScan(16);
+ break;
+ case DT_FIRE:
+ v = _fireResistence._permanent + _fireResistence._temporary + itemScan(11);
+ break;
+ case DT_ELECTRICAL:
+ v = _electricityResistence._permanent + _electricityResistence._temporary + itemScan(12);
+ break;
+ case DT_COLD:
+ v = _coldResistence._permanent + _coldResistence._temporary + itemScan(13);
+ break;
+ case DT_POISON:
+ v = _poisonResistence._permanent + _poisonResistence._temporary + itemScan(14);
+ break;
+ case DT_ENERGY:
+ v = _energyResistence._permanent + _energyResistence._temporary + itemScan(15);
+ break;
+ default:
+ v = 0;
+ break;
+ }
+
+ vMax = v + 40;
+ }
+
+ return Party::_vm->getRandomNumber(1, vMax) <= v;
+}
+
+bool Character::noActions() {
+ Condition condition = worstCondition();
+
+ switch (condition) {
+ case CURSED:
+ case POISONED:
+ case DISEASED:
+ case INSANE:
+ case IN_LOVE:
+ case DRUNK: {
+ Common::String msg = Common::String::format(IN_NO_CONDITION, _name.c_str());
+ ErrorScroll::show(Party::_vm, msg,
+ Party::_vm->_mode == 17 ? WT_2 : WT_NONFREEZED_WAIT);
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
+void Character::setAward(int awardId, bool value) {
+ int v = awardId;
+ if (awardId == 73)
+ v = 126;
+ else if (awardId == 81)
+ v = 127;
+
+ _awards[v] = value;
+}
+
+bool Character::hasAward(int awardId) const {
+ int v = awardId;
+ if (awardId == 73)
+ v = 126;
+ else if (awardId == 81)
+ v = 127;
+
+ return _awards[v];
+}
+
+int Character::getArmorClass(bool baseOnly) const {
+ Party &party = *Party::_vm->_party;
+
+ int result = statBonus(getStat(SPEED)) + itemScan(9);
+ if (!baseOnly)
+ result += party._blessed + _ACTemp;
+
+ return MAX(result, 0);
+}
+
+int Character::getThievery() const {
+ int result = getCurrentLevel() * 2;
+
+ if (_class == CLASS_NINJA)
+ result += 15;
+ else if (_class == CLASS_ROBBER)
+ result += 30;
+
+ switch (_race) {
+ case ELF:
+ case GNOME:
+ result += 10;
+ break;
+ case DWARF:
+ result += 5;
+ break;
+ case HALF_ORC:
+ result -= 10;
+ break;
+ default:
+ break;
+ }
+
+ result += itemScan(10);
+
+ // If the character doesn't have a thievery skill, then do'nt allow any result
+ if (!_skills[THIEVERY])
+ result = 0;
+
+ return MAX(result, 0);
+}
+
+uint Character::getCurrentLevel() const {
+ return MAX(_level._permanent + _level._temporary, (uint)0);
+}
+
+int Character::itemScan(int itemId) const {
+ int result = 0;
+
+ for (int accessIdx = 0; accessIdx < 3; ++accessIdx) {
+ switch (accessIdx) {
+ case 0:
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ const XeenItem &item = _weapons[idx];
+
+ if (item._frame && !(item._bonusFlags & 0xC0) && itemId < 11
+ && itemId != 3 && item._material >= 59 && item._material <= 130) {
+ int mIndex = (int)item.getAttributeCategory();
+ if (mIndex > PERSONALITY)
+ ++mIndex;
+
+ if (mIndex == itemId)
+ result += ATTRIBUTE_BONUSES[item._material - 59];
+ }
+ }
+ break;
+
+ case 1:
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ const XeenItem &item = _armor[idx];
+
+ if (item._frame && !(item._bonusFlags & 0xC0)) {
+ if (itemId < 11 && itemId != 3 && item._material >= 59 && item._material <= 130) {
+ int mIndex = (int)item.getAttributeCategory();
+ if (mIndex > PERSONALITY)
+ ++mIndex;
+
+ if (mIndex == itemId)
+ result += ATTRIBUTE_BONUSES[item._material - 59];
+ }
+
+ if (itemId > 10 && item._material < 37) {
+ int mIndex = item.getElementalCategory() + 11;
+
+ if (mIndex == itemId) {
+ result += ELEMENTAL_RESISTENCES[item._material];
+ }
+ }
+
+ if (itemId == 9) {
+ result += ARMOR_STRENGTHS[item._id];
+
+ if (item._material >= 37 && item._material <= 58)
+ result += METAL_LAC[item._material - 37];
+ }
+ }
+ }
+ break;
+
+ case 2:
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ const XeenItem &item = _accessories[idx];
+
+ if (item._frame && !(item._bonusFlags & 0xC0) && itemId < 11 && itemId != 3) {
+ if (item._material >= 59 && item._material <= 130) {
+ int mIndex = (int)item.getAttributeCategory();
+ if (mIndex > PERSONALITY)
+ ++mIndex;
+
+ if (mIndex == itemId) {
+ result += ATTRIBUTE_BONUSES[item._material - 59];
+ }
+ }
+
+ if (itemId > 10 && item._material < 37) {
+ int mIndex = item.getElementalCategory() + 11;
+
+ if (mIndex == itemId)
+ result += ELEMENTAL_RESISTENCES[item._material];
+ }
+ }
+ }
+ break;
+ }
+ };
+
+ return result;
+}
+
+int Character::conditionMod(Attribute attrib) const {
+ if (_conditions[DEAD] || _conditions[STONED] || _conditions[ERADICATED])
+ return 0;
+
+ int v[7];
+ Common::fill(&v[0], &v[7], 0);
+ if (_conditions[CURSED])
+ v[6] -= _conditions[CURSED];
+
+ if (_conditions[INSANE]) {
+ v[2] -= _conditions[INSANE];
+ v[1] -= _conditions[INSANE];
+ v[5] -= _conditions[INSANE];
+ v[0] -= _conditions[INSANE];
+ v[4] -= _conditions[INSANE];
+ }
+
+ if (_conditions[POISONED]) {
+ v[0] -= _conditions[POISONED];
+ v[4] -= _conditions[POISONED];
+ v[5] -= _conditions[POISONED];
+ }
+
+ if (_conditions[DISEASED]) {
+ v[3] -= _conditions[DISEASED];
+ v[2] -= _conditions[DISEASED];
+ v[1] -= _conditions[DISEASED];
+ }
+
+ for (int idx = 0; idx < 7; ++idx) {
+ v[idx] -= _conditions[HEART_BROKEN];
+ v[idx] -= _conditions[IN_LOVE];
+ v[idx] -= _conditions[WEAK];
+ v[idx] -= _conditions[DRUNK];
+ }
+
+ return v[attrib];
+}
+
+void Character::setValue(int id, uint value) {
+ Party &party = *Party::_vm->_party;
+ Scripts &scripts = *Party::_vm->_scripts;
+
+ switch (id) {
+ case 3:
+ // Set character sex
+ _sex = (Sex)value;
+ break;
+ case 4:
+ // Set race
+ _race = (Race)value;
+ break;
+ case 5:
+ // Set class
+ _class = (CharacterClass)value;
+ break;
+ case 8:
+ // Set the current Hp
+ _currentHp = value;
+ break;
+ case 9:
+ // Set the current Sp
+ _currentSp = value;
+ break;
+ case 10:
+ case 77:
+ // Set temporary armor class
+ _ACTemp = value;
+ break;
+ case 11:
+ // Set temporary level
+ _level._temporary = value;
+ break;
+ case 12:
+ // Set the character's temporary age
+ _tempAge = value;
+ break;
+ case 16:
+ // Set character experience
+ _experience = value;
+ break;
+ case 17:
+ // Set party poison resistence
+ party._poisonResistence = value;
+ break;
+ case 18:
+ // Set condition
+ if (value == 16) {
+ // Clear all the conditions
+ Common::fill(&_conditions[CURSED], &_conditions[NO_CONDITION], false);
+ } else if (value == 6) {
+ _conditions[value] = 1;
+ } else {
+ ++_conditions[value];
+ }
+
+ if (value >= DEAD && value <= ERADICATED && _currentHp > 0)
+ _currentHp = 0;
+ break;
+ case 25:
+ // Set time of day in minutes (0-1440)
+ party._minutes = value;
+ break;
+ case 34:
+ // Set party gold
+ party._gold = value;
+ break;
+ case 35:
+ // Set party gems
+ party._gems = value;
+ break;
+ case 37:
+ _might._temporary = value;
+ break;
+ case 38:
+ _intellect._temporary = value;
+ break;
+ case 39:
+ _personality._temporary = value;
+ break;
+ case 40:
+ _endurance._temporary = value;
+ break;
+ case 41:
+ _speed._temporary = value;
+ break;
+ case 42:
+ _accuracy._temporary = value;
+ break;
+ case 43:
+ _luck._temporary = value;
+ break;
+ case 45:
+ _might._permanent = value;
+ break;
+ case 46:
+ _intellect._permanent = value;
+ break;
+ case 47:
+ _personality._permanent = value;
+ break;
+ case 48:
+ _endurance._permanent = value;
+ break;
+ case 49:
+ _speed._permanent = value;
+ break;
+ case 50:
+ _accuracy._permanent = value;
+ break;
+ case 51:
+ _luck._permanent = value;
+ break;
+ case 52:
+ _fireResistence._permanent = value;
+ break;
+ case 53:
+ _electricityResistence._permanent = value;
+ break;
+ case 54:
+ _coldResistence._permanent = value;
+ break;
+ case 55:
+ _poisonResistence._permanent = value;
+ break;
+ case 56:
+ _energyResistence._permanent = value;
+ break;
+ case 57:
+ _magicResistence._permanent = value;
+ break;
+ case 58:
+ _fireResistence._temporary = value;
+ break;
+ case 59:
+ _electricityResistence._temporary = value;
+ break;
+ case 60:
+ _coldResistence._temporary = value;
+ break;
+ case 61:
+ _poisonResistence._temporary = value;
+ break;
+ case 62:
+ _energyResistence._temporary = value;
+ break;
+ case 63:
+ _magicResistence._temporary = value;
+ break;
+ case 64:
+ _level._permanent = value;
+ break;
+ case 65:
+ // Set party food
+ party._food = value;
+ break;
+ case 69:
+ // Set levitate active
+ party._levitateActive = value != 0;
+ break;
+ case 70:
+ party._lightCount = value;
+ break;
+ case 71:
+ party._fireResistence = value;
+ break;
+ case 72:
+ party._electricityResistence = value;
+ break;
+ case 73:
+ party._coldResistence = value;
+ break;
+ case 74:
+ party._walkOnWaterActive = value != 0;
+ party._poisonResistence = value;
+ party._wizardEyeActive = value != 0;
+ party._coldResistence = value;
+ party._electricityResistence = value;
+ party._fireResistence = value;
+ party._lightCount = value;
+ party._levitateActive = value != 0;
+ break;
+ case 76:
+ // Set day of the year (0-99)
+ party._day = value;
+ break;
+ case 79:
+ party._wizardEyeActive = true;
+ break;
+ case 83:
+ scripts._nEdamageType = value;
+ break;
+ case 84:
+ party._mazeDirection = (Direction)value;
+ break;
+ case 85:
+ party._year = value;
+ break;
+ case 94:
+ party._walkOnWaterActive = value != 0;
+ break;
+ default:
+ break;
+ }
+}
+
+bool Character::guildMember() const {
+ Party &party = *Party::_vm->_party;
+
+ if (party._mazeId == 49 && !Party::_vm->_files->_isDarkCc) {
+ return hasAward(5);
+ }
+
+ switch (party._mazeId) {
+ case 29:
+ return hasAward(83);
+ case 31:
+ return hasAward(84);
+ case 33:
+ return hasAward(85);
+ case 35:
+ return hasAward(86);
+ default:
+ return hasAward(87);
+ }
+}
+
+uint Character::experienceToNextLevel() const {
+ uint next = nextExperienceLevel();
+ uint curr = getCurrentExperience();
+ return (curr >= next) ? 0 : next - curr;
+}
+
+uint Character::nextExperienceLevel() const {
+ int shift, base;
+ if (_level._permanent >= 12) {
+ base = _level._permanent - 12;
+ shift = 10;
+ } else {
+ base = 0;
+ shift = _level._permanent - 1;
+ }
+
+ return (base * 1024000) + (CLASS_EXP_LEVELS[_class] << shift);
+}
+
+uint Character::getCurrentExperience() const {
+ int lev = _level._permanent - 1;
+ int shift, base;
+
+ if (lev > 0 && lev < 12)
+ return _experience;
+
+ if (lev >= 12) {
+ base = lev - 12;
+ shift = 10;
+ } else {
+ base = 0;
+ shift = lev - 1;
+ }
+
+ return (base * 1024000) + (CLASS_EXP_LEVELS[_class] << shift) +
+ _experience;
+}
+
+
+int Character::getNumSkills() const {
+ int total = 0;
+ for (int idx = THIEVERY; idx <= DANGER_SENSE; ++idx) {
+ if (_skills[idx])
+ ++total;
+ }
+
+ return total;
+}
+
+int Character::getNumAwards() const {
+ int total = 0;
+ for (int idx = 0; idx < 88; ++idx) {
+ if (hasAward(idx))
+ ++total;
+ }
+
+ return total;
+}
+
+int Character::makeItem(int p1, int itemIndex, int p3) {
+ XeenEngine *vm = Party::_vm;
+ Scripts &scripts = *vm->_scripts;
+
+ if (!p1)
+ return 0;
+
+ int itemId = 0;
+ int v4 = vm->getRandomNumber(100);
+ int v6 = vm->getRandomNumber(p1 < 6 ? 100 : 80);
+ ItemCategory category;
+ int v16 = 0, v14 = 0, miscBonus = 0, miscId = 0, v8 = 0, v12 = 0;
+
+ // Randomly pick a category and item Id
+ if (p3 == 12) {
+ if (scripts._itemType < 35) {
+ category = CATEGORY_WEAPON;
+ itemId = scripts._itemType;
+ } else if (scripts._itemType < 49) {
+ category = CATEGORY_ARMOR;
+ itemId = scripts._itemType - 35;
+ } else if (scripts._itemType < 60) {
+ category = CATEGORY_ACCESSORY;
+ itemId = scripts._itemType - 49;
+ } else {
+ category = CATEGORY_MISC;
+ itemId = scripts._itemType - 60;
+ }
+ } else {
+ switch (p3) {
+ case 1:
+ v4 = 35;
+ break;
+ case 2:
+ v4 = 60;
+ break;
+ case 3:
+ v4 = 100;
+ break;
+ default:
+ break;
+ }
+
+ if (p1 == 1) {
+ if (v4 <= 40) {
+ category = CATEGORY_WEAPON;
+ if (v6 <= 30) {
+ itemId = vm->getRandomNumber(1, 6);
+ } else if (v6 <= 60) {
+ itemId = vm->getRandomNumber(7, 17);
+ } else if (v6 <= 85) {
+ itemId = vm->getRandomNumber(18, 29);
+ } else {
+ itemId = vm->getRandomNumber(30, 33);
+ }
+ } else if (v4 <= 85) {
+ category = CATEGORY_ARMOR;
+ itemId = vm->getRandomNumber(1, 7);
+ } else {
+ category = CATEGORY_MISC;
+ itemId = vm->getRandomNumber(1, 9);
+ }
+ } else if (v4 <= 35) {
+ category = CATEGORY_WEAPON;
+ if (v6 <= 30) {
+ itemId = vm->getRandomNumber(1, 6);
+ } else if (v6 <= 60) {
+ itemId = vm->getRandomNumber(7, 17);
+ } else if (v6 <= 85) {
+ itemId = vm->getRandomNumber(18, 29);
+ } else {
+ itemId = vm->getRandomNumber(30, 33);
+ }
+ } else if (v4 <= 60) {
+ category = CATEGORY_ARMOR;
+ itemId = (v6 > 70) ? 8 : vm->getRandomNumber(1, 7);
+ } else if (v6 <= 10) {
+ category = CATEGORY_ARMOR;
+ itemId = 9;
+ } else if (v6 <= 20) {
+ category = CATEGORY_ARMOR;
+ itemId = 13;
+ } else if (v6 <= 35) {
+ category = CATEGORY_ACCESSORY;
+ itemId = 1;
+ } else if (v6 <= 45) {
+ category = CATEGORY_ARMOR;
+ itemId = 10;
+ } else if (v6 <= 55) {
+ category = CATEGORY_ARMOR;
+ itemId = vm->getRandomNumber(11, 12);
+ } else if (v6 <= 65) {
+ category = CATEGORY_ACCESSORY;
+ itemId = 2;
+ } else if (v6 <= 75) {
+ category = CATEGORY_ACCESSORY;
+ itemId = vm->getRandomNumber(3, 7);
+ } else if (v6 <= 80) {
+ category = CATEGORY_ACCESSORY;
+ itemId = vm->getRandomNumber(8, 10);
+ } else {
+ category = CATEGORY_MISC;
+ itemId = vm->getRandomNumber(1, 9);
+ }
+ }
+
+ XeenItem &newItem = _items[category][itemIndex];
+ newItem.clear();
+ newItem._id = itemId;
+
+ v4 = vm->getRandomNumber(1, 100);
+ switch (category) {
+ case CATEGORY_WEAPON:
+ case CATEGORY_ARMOR:
+ if (p1 != 1) {
+ if (v4 <= 70) {
+ v8 = 3;
+ } else if (v4 <= 98) {
+ v8 = 1;
+ } else {
+ v8 = 2;
+ }
+ }
+ break;
+
+ case CATEGORY_ACCESSORY:
+ if (v4 <= 20) {
+ v8 = 3;
+ } else if (v4 <= 60) {
+ v8 = 1;
+ } else {
+ v8 = 2;
+ }
+ break;
+
+ case CATEGORY_MISC:
+ v8 = 4;
+ break;
+
+ default:
+ break;
+ }
+
+ if (p1 != 1 || category == CATEGORY_MISC) {
+ int rval, mult;
+ switch (v8) {
+ case 1:
+ rval = vm->getRandomNumber(1, 100);
+ if (rval <= 25) {
+ mult = 0;
+ }
+ else if (rval <= 45) {
+ mult = 1;
+ }
+ else if (rval <= 60) {
+ mult = 2;
+ }
+ else if (rval <= 75) {
+ mult = 3;
+ }
+ else if (rval <= 95) {
+ mult = 4;
+ }
+ else {
+ mult = 5;
+ }
+
+ v12 = MAKE_ITEM_ARR1[vm->getRandomNumber(MAKE_ITEM_ARR2[mult][p1][0],
+ MAKE_ITEM_ARR2[mult][p1][1])];
+ break;
+
+ case 2:
+ rval = vm->getRandomNumber(1, 100);
+ if (rval <= 15) {
+ mult = 0;
+ } else if (rval <= 25) {
+ mult = 1;
+ } else if (rval <= 35) {
+ mult = 2;
+ } else if (rval <= 50) {
+ mult = 3;
+ } else if (rval <= 65) {
+ mult = 4;
+ } else if (rval <= 80) {
+ mult = 5;
+ } else if (rval <= 85) {
+ mult = 6;
+ } else if (rval <= 90) {
+ mult = 7;
+ } else if (rval <= 95) {
+ mult = 8;
+ } else {
+ mult = 9;
+ }
+
+ v12 = MAKE_ITEM_ARR1[vm->getRandomNumber(MAKE_ITEM_ARR3[mult][p1][0],
+ MAKE_ITEM_ARR3[mult][p1][1])];
+ break;
+
+ case 3:
+ mult = p1 == 7 || vm->getRandomNumber(1, 100) > 70 ? 1 : 0;
+ v16 = vm->getRandomNumber(MAKE_ITEM_ARR4[mult][p1][0],
+ MAKE_ITEM_ARR4[mult][p1][1]);
+ break;
+
+ case 4:
+ miscBonus = vm->getRandomNumber(MAKE_ITEM_ARR5[p1][0], MAKE_ITEM_ARR5[p1][1]);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ switch (category) {
+ case CATEGORY_WEAPON:
+ if (p1 != 1) {
+ newItem._material = (v14 ? v14 + 58 : 0) + (v16 ? v16 + 36 : 0) + v12;
+ if (vm->getRandomNumber(20) == 10)
+ newItem._bonusFlags = vm->getRandomNumber(1, 6);
+ }
+ break;
+
+ case CATEGORY_ARMOR:
+ case CATEGORY_ACCESSORY:
+ if (p1 != 1) {
+ newItem._material = (v14 ? v14 + 58 : 0) + (v16 ? v16 + 36 : 0) + v12;
+ }
+ break;
+
+ case CATEGORY_MISC:
+ newItem._id = miscId;
+ newItem._bonusFlags = miscBonus;
+ break;
+
+ default:
+ break;
+ }
+
+ return category;
+}
+
+void Character::addHitPoints(int amount) {
+ Interface &intf = *Party::_vm->_interface;
+ Common::fill(&intf._charFX[0], &intf._charFX[MAX_ACTIVE_PARTY], 0);
+
+ if (!isDead()) {
+ int maxHp = getMaxHP();
+ if (_currentHp <= maxHp) {
+ _currentHp = MIN(_currentHp + amount, maxHp);
+ intf.spellFX(this);
+ }
+
+ if (_currentHp > 0)
+ _conditions[UNCONSCIOUS] = 0;
+
+ intf.drawParty(true);
+ }
+
+ Common::fill(&intf._charFX[0], &intf._charFX[MAX_ACTIVE_PARTY], 0);
+}
+
+void Character::subtractHitPoints(int amount) {
+ SoundManager &sound = *Party::_vm->_sound;
+ _currentHp -= amount;
+ bool flag = _currentHp <= 10;
+
+ if (_currentHp < 1) {
+ int v = getMaxHP() + _currentHp;
+ if (v >= 1) {
+ _conditions[UNCONSCIOUS] = 1;
+ sound.playFX(38);;
+ } else {
+ _conditions[DEAD] = 1;
+ flag = true;
+ if (_currentHp > 0)
+ _currentHp = 0;
+ }
+
+ if (flag) {
+ // Check for breaking equipped armor
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ XeenItem &item = _armor[idx];
+ if (item._id && item._frame)
+ item._bonusFlags |= ITEMFLAG_BROKEN;
+ }
+ }
+ }
+}
+
+bool Character::hasSpecialItem() const {
+ for (uint idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ if (_weapons[idx]._id == 34)
+ // Character has Xeen Slayer sword
+ return true;
+ }
+
+ return false;
+}
+
+bool Character::hasMissileWeapon() const {
+ for (uint idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ if (_weapons[idx]._frame == 4) {
+ return !isDisabledOrDead();
+ }
+ }
+
+ return false;
+}
+
+int Character::getClassCategory() const {
+ switch (_class) {
+ case CLASS_ARCHER:
+ case CLASS_SORCERER:
+ return 1;
+
+ case CLASS_DRUID:
+ case CLASS_RANGER:
+ return 2;
+
+ default:
+ return 0;
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/character.h b/engines/xeen/character.h
new file mode 100644
index 0000000000..903d8faabc
--- /dev/null
+++ b/engines/xeen/character.h
@@ -0,0 +1,423 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_CHARACTER_H
+#define XEEN_CHARACTER_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/serializer.h"
+#include "xeen/combat.h"
+#include "xeen/sprites.h"
+
+namespace Xeen {
+
+#define INV_ITEMS_TOTAL 9
+
+enum BonusFlags {
+ ITEMFLAG_BONUS_MASK = 0xBF, ITEMFLAG_CURSED = 0x40, ITEMFLAG_BROKEN = 0x80
+};
+
+enum ItemCategory {
+ CATEGORY_WEAPON = 0, CATEGORY_ARMOR = 1, CATEGORY_ACCESSORY = 2, CATEGORY_MISC = 3,
+ NUM_ITEM_CATEGORIES = 4
+};
+
+enum Sex { MALE = 0, FEMALE = 1, YES_PLEASE = 2 };
+
+enum Race { HUMAN = 0, ELF = 1, DWARF = 2, GNOME = 3, HALF_ORC = 4 };
+
+enum CharacterClass {
+ CLASS_KNIGHT = 0, CLASS_PALADIN = 1, CLASS_ARCHER = 2, CLASS_CLERIC = 3,
+ CLASS_SORCERER = 4, CLASS_ROBBER = 5, CLASS_NINJA = 6, CLASS_BARBARIAN = 7,
+ CLASS_DRUID = 8, CLASS_RANGER = 9, TOTAL_CLASSES = 10, CLASS_12 = 12, CLASS_15 = 15, CLASS_16 = 16
+};
+
+enum Attribute {
+ MIGHT = 0, INTELLECT = 1, PERSONALITY = 2, ENDURANCE = 3, SPEED = 4,
+ ACCURACY = 5, LUCK = 6, TOTAL_ATTRIBUTES = 7
+};
+
+enum Skill {
+ THIEVERY = 0, ARMS_MASTER = 1, ASTROLOGER = 2, BODYBUILDER = 3,
+ CARTOGRAPHER = 4, CRUSADER = 5, DIRECTION_SENSE = 6, LINGUIST = 7,
+ MERCHANT = 8, MOUNTAINEER = 9, NAVIGATOR = 10, PATHFINDER = 11,
+ PRAYER_MASTER = 12, PRESTIDIGITATION = 13, SWIMMING = 14, TRACKING = 15,
+ SPOT_DOORS = 16, DANGER_SENSE = 17
+};
+
+enum Condition {
+ CURSED = 0, HEART_BROKEN = 1, WEAK = 2, POISONED = 3,
+ DISEASED = 4, INSANE = 5, IN_LOVE = 6, DRUNK = 7, ASLEEP = 8,
+ DEPRESSED = 9, CONFUSED = 10, PARALYZED = 11, UNCONSCIOUS = 12,
+ DEAD = 13, STONED = 14, ERADICATED = 15,
+ NO_CONDITION = 16
+};
+
+enum AttributeCategory {
+ ATTR_MIGHT = 0, ATTR_INTELLECT = 1, ATTR_PERSONALITY = 2, ATTR_SPEED = 3,
+ ATTR_ACCURACY = 4, ATTR_LUCK = 5, ATTR_HIT_POINTS = 6, ATTR_SPELL_POINTS = 7,
+ ATTR_ARMOR_CLASS = 8, ATTR_THIEVERY = 9
+};
+
+enum QuickAction {
+ QUICK_ATTACK = 0, QUICK_SPELL = 1, QUICK_BLOCK = 2, QUICK_RUN = 3
+};
+
+class XeenEngine;
+class Character;
+
+class XeenItem {
+public:
+ int _material;
+ uint _id;
+ int _bonusFlags;
+ int _frame;
+public:
+ XeenItem();
+
+ void clear();
+
+ bool empty() const { return _id != 0; }
+
+ void synchronize(Common::Serializer &s);
+
+ ElementalCategory getElementalCategory() const;
+
+ AttributeCategory getAttributeCategory() const;
+};
+
+class InventoryItems : public Common::Array<XeenItem> {
+protected:
+ Character *_character;
+ ItemCategory _category;
+ const char *const *_names;
+
+ XeenEngine *getVm();
+ void equipError(int itemIndex1, ItemCategory category1, int itemIndex2,
+ ItemCategory category2);
+
+ virtual Common::String getAttributes(XeenItem &item, const Common::String &classes) = 0;
+public:
+ InventoryItems(Character *character, ItemCategory category);
+ virtual ~InventoryItems() {}
+
+ void clear();
+
+ /**
+ * Return whether a given item passes class-based usage restrictions
+ */
+ bool passRestrictions(int itemId, bool showError) const;
+
+ /**
+ * Return the bare name of a given inventory item
+ */
+ Common::String getName(int itemIndex);
+
+ virtual Common::String getFullDescription(int itemIndex, int displayNum = 15) = 0;
+
+ Common::String getIdentifiedDetails(int itemIndex);
+
+ /**
+ * Discard an item from the inventory
+ */
+ bool discardItem(int itemIndex);
+
+ virtual void equipItem(int itemIndex) {}
+
+ /**
+ * Un-equips the given item
+ */
+ void removeItem(int itemIndex);
+
+ /**
+ * Sorts the items list, removing any empty item slots to the end of the array
+ */
+ void sort();
+
+ virtual void enchantItem(int itemIndex, int amount);
+
+ /**
+ * Return if the given inventory items list is full
+ */
+ bool isFull() const;
+};
+
+class WeaponItems: public InventoryItems {
+protected:
+ virtual Common::String getAttributes(XeenItem &item, const Common::String &classes);
+public:
+ WeaponItems(Character *character) : InventoryItems(character, CATEGORY_WEAPON) {}
+ virtual ~WeaponItems() {}
+
+ /**
+ * Equip a given weapon
+ */
+ virtual void equipItem(int itemIndex);
+
+ /**
+ * Assembles a full lines description for a specified item for use in
+ * the Items dialog
+ */
+ virtual Common::String getFullDescription(int itemIndex, int displayNum);
+
+ virtual void enchantItem(int itemIndex, int amount);
+};
+
+class ArmorItems : public InventoryItems {
+protected:
+ virtual Common::String getAttributes(XeenItem &item, const Common::String &classes);
+public:
+ ArmorItems(Character *character) : InventoryItems(character, CATEGORY_ARMOR) {}
+ virtual ~ArmorItems() {}
+
+ /**
+ * Equip a given piece of armor
+ */
+ virtual void equipItem(int itemIndex);
+
+ /**
+ * Assembles a full lines description for a specified item for use in
+ * the Items dialog
+ */
+ virtual Common::String getFullDescription(int itemIndex, int displayNum);
+
+ virtual void enchantItem(int itemIndex, int amount);
+};
+
+class AccessoryItems : public InventoryItems {
+protected:
+ virtual Common::String getAttributes(XeenItem &item, const Common::String &classes);
+public:
+ AccessoryItems(Character *character) : InventoryItems(character, CATEGORY_ACCESSORY) {}
+
+ /**
+ * Equip a given accessory
+ */
+ virtual void equipItem(int itemIndex);
+
+ /**
+ * Assembles a full lines description for a specified item for use in
+ * the Items dialog
+ */
+ virtual Common::String getFullDescription(int itemIndex, int displayNum);
+};
+
+class MiscItems : public InventoryItems {
+protected:
+ virtual Common::String getAttributes(XeenItem &item, const Common::String &classes);
+public:
+ MiscItems(Character *character) : InventoryItems(character, CATEGORY_MISC) {}
+ virtual ~MiscItems() {}
+
+ /**
+ * Assembles a full lines description for a specified item for use in
+ * the Items dialog
+ */
+ virtual Common::String getFullDescription(int itemIndex, int displayNum);
+};
+
+class InventoryItemsGroup {
+private:
+ InventoryItems *_itemSets[4];
+public:
+ InventoryItemsGroup(InventoryItems &weapons, InventoryItems &armor,
+ InventoryItems &accessories, InventoryItems &misc);
+
+ InventoryItems &operator[](ItemCategory category);
+
+ /**
+ * Breaks all the items in a given character's inventory
+ */
+ void breakAllItems();
+};
+
+
+class AttributePair {
+public:
+ uint _permanent;
+ uint _temporary;
+public:
+ AttributePair();
+ void synchronize(Common::Serializer &s);
+};
+
+class Character {
+private:
+ /**
+ * Modifies a passed attribute value based on player's condition
+ */
+ int conditionMod(Attribute attrib) const;
+public:
+ Common::String _name;
+ Sex _sex;
+ Race _race;
+ int _xeenSide;
+ CharacterClass _class;
+ AttributePair _might;
+ AttributePair _intellect;
+ AttributePair _personality;
+ AttributePair _endurance;
+ AttributePair _speed;
+ AttributePair _accuracy;
+ AttributePair _luck;
+ int _ACTemp;
+ AttributePair _level;
+ uint _birthDay;
+ int _tempAge;
+ int _skills[18];
+ bool _awards[128];
+ int _spells[39];
+ int _lloydMap;
+ Common::Point _lloydPosition;
+ bool _hasSpells;
+ int8 _currentSpell;
+ QuickAction _quickOption;
+ InventoryItemsGroup _items;
+ WeaponItems _weapons;
+ ArmorItems _armor;
+ AccessoryItems _accessories;
+ MiscItems _misc;
+ int _lloydSide;
+ AttributePair _fireResistence;
+ AttributePair _coldResistence;
+ AttributePair _electricityResistence;
+ AttributePair _poisonResistence;
+ AttributePair _energyResistence;
+ AttributePair _magicResistence;
+ int _conditions[16];
+ int _townUnknown;
+ int _savedMazeId;
+ int _currentHp;
+ int _currentSp;
+ uint _birthYear;
+ uint32 _experience;
+ int _currentAdventuringSpell;
+ int _currentCombatSpell;
+
+ SpriteResource *_faceSprites;
+ int _rosterId;
+public:
+ Character();
+
+ void clear();
+
+ void synchronize(Common::Serializer &s);
+
+ /**
+ * Returns the worst condition the character is suffering from
+ */
+ Condition worstCondition() const;
+
+ /**
+ * Returns whether the given character has a disabling condition, but still alive
+ */
+ bool isDisabled() const;
+
+ /**
+ * Returns whether the given character has a disabling condition, or is dead
+ */
+ bool isDisabledOrDead() const;
+
+ /**
+ * Returns whether the given character has a dead condition
+ */
+ bool isDead() const;
+
+ /**
+ * Get the character's age
+ */
+ int getAge(bool ignoreTemp = false) const;
+
+ int getMaxHP() const;
+
+ int getMaxSP() const;
+
+ /**
+ * Get the effective value of a given stat for the character
+ */
+ uint getStat(Attribute attrib, bool baseOnly = false) const;
+
+ /**
+ * Return the color number to use for a given stat value in the character
+ * info or quick reference dialogs
+ */
+ static int statColor(int amount, int threshold);
+
+ int statBonus(uint statValue) const;
+
+ bool charSavingThrow(DamageType attackType) const;
+
+ bool noActions();
+
+ void setAward(int awardId, bool value);
+
+ bool hasAward(int awardId) const;
+
+ int getArmorClass(bool baseOnly = false) const;
+
+ /**
+ * Returns the thievery skill level, adjusted by class and race
+ */
+ int getThievery() const;
+
+ uint getCurrentLevel() const;
+
+ int itemScan(int itemId) const;
+
+ void setValue(int id, uint value);
+
+ bool guildMember() const;
+
+ uint experienceToNextLevel() const;
+
+ uint nextExperienceLevel() const;
+
+ uint getCurrentExperience() const;
+
+ int getNumSkills() const;
+
+ int getNumAwards() const;
+
+ int makeItem(int p1, int itemIndex, int p3);
+
+ /**
+ * Add hit points to a character
+ */
+ void addHitPoints(int amount);
+
+ /**
+ * Remove hit points fromo the character
+ */
+ void subtractHitPoints(int amount);
+
+ bool hasSpecialItem() const;
+
+ bool hasMissileWeapon() const;
+
+ int getClassCategory() const;
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_CHARACTER_H */
diff --git a/engines/xeen/combat.cpp b/engines/xeen/combat.cpp
new file mode 100644
index 0000000000..3faff3d157
--- /dev/null
+++ b/engines/xeen/combat.cpp
@@ -0,0 +1,2065 @@
+/* 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.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/rect.h"
+#include "xeen/character.h"
+#include "xeen/combat.h"
+#include "xeen/interface.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+static const int MONSTER_GRID_X[48] = {
+ 1, 1, 1, 0, -1, -1, -1, 1, 1, 1, 0, -1,
+ -1, -1, 1, 1, 1, 0, -1, -1, -1, 1, 1, 1,
+ 0, -1, -1, -1, 1, 1, 1, 0, -1, -1, -1, 1,
+ 1, 1, 0, -1, -1, -1, 1, 1, 1, 0, -1, -1
+};
+
+static const int MONSTER_GRID_Y[48] = {
+ 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, -1, 0,
+ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0
+};
+
+static const int MONSTER_GRID3[48] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ - 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1,
+ 0, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+static const int MONSTER_GRID_BITINDEX1[48] = {
+ 1, 1, 1, 2, 3, 3, 3, 1, 1, 1, 2, 3,
+ 3, 3, 1, 1, 1, 2, 3, 3, 3, 1, 1, 1,
+ 0, 3, 3, 3, 1, 1, 1, 0, 3, 3, 3, 1,
+ 1, 1, 0, 3, 3, 3, 1, 1, 1, 0, 3, 3
+};
+
+static const int MONSTER_GRID_BITINDEX2[48] = {
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1,
+ 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static const int ATTACK_TYPE_FX[23] = {
+ 49, 18, 13, 14, 15, 17, 16, 0, 6, 1, 2, 3,
+ 4, 5, 4, 9, 27, 29, 44, 51, 53, 61, 71
+};
+
+static const int MONSTER_SHOOT_POW[7] = { 12, 14, 0, 4, 8, 10, 13 };
+
+static const int COMBAT_SHOOTING[4] = { 1, 1, 2, 3 };
+
+static const int DAMAGE_TYPE_EFFECTS[19] = {
+ 3, 10, 4, 11, 1, 2, 5, 9, 5, 14, 5, 14, 10, 8, 3, 9, 2, 2, 3
+};
+
+static const int POW_WEAPON_VOCS[35] = {
+ 0, 5, 4, 5, 5, 5, 5, 2, 4, 5, 3, 5, 4, 2, 3, 2, 2, 4, 5, 5,
+ 5, 5, 5, 1, 3, 2, 5, 1, 1, 1, 0, 0, 0, 2, 2
+};
+
+static const int MONSTER_ITEM_RANGES[6] = { 10, 20, 50, 100, 100, 100 };
+
+#define monsterSavingThrow(MONINDEX) (_vm->getRandomNumber(1, 50 + (MONINDEX)) <= (MONINDEX))
+
+/*------------------------------------------------------------------------*/
+
+Combat::Combat(XeenEngine *vm): _vm(vm), _missVoc("miss.voc"), _pow1Voc("pow1.voc") {
+ Common::fill(&_attackMonsters[0], &_attackMonsters[26], 0);
+ Common::fill(&_charsArray1[0], &_charsArray1[12], 0);
+ Common::fill(&_monPow[0], &_monPow[12], 0);
+ Common::fill(&_monsterScale[0], &_monsterScale[12], 0);
+ Common::fill(&_elemPow[0], &_elemPow[12], ELEM_FIRE);
+ Common::fill(&_elemScale[0], &_elemScale[12], 0);
+ Common::fill(&_shooting[0], &_shooting[8], 0);
+ Common::fill(&_monsterMap[0][0], &_monsterMap[32][32], 0);
+ Common::fill(&_monsterMoved[0], &_monsterMoved[MAX_NUM_MONSTERS], false);
+ Common::fill(&_rangeAttacking[0], &_rangeAttacking[MAX_NUM_MONSTERS], false);
+ Common::fill(&_gmonHit[0], &_gmonHit[36], 0);
+ Common::fill(&_missedShot[0], &_missedShot[MAX_PARTY_COUNT], 0);
+ _globalCombat = 0;
+ _whosTurn = -1;
+ _itemFlag = false;
+ _monstersAttacking = false;
+ _combatMode = COMBATMODE_0;
+ _monsterIndex = 0;
+ _partyRan = false;
+ _monster2Attack = -1;
+ _whosSpeed = 0;
+ _damageType = DT_PHYSICAL;
+ _oldCharacter = nullptr;
+ _shootType = ST_0;
+ _monsterDamage = 0;
+ _weaponDamage = 0;
+ _weaponDie = _weaponDice = 0;
+ _attackWeapon = nullptr;
+ _attackWeaponId = 0;
+ _hitChanceBonus = 0;
+ _dangerPresent = false;
+ _moveMonsters = false;
+ _rangeType = RT_SINGLE;
+}
+
+void Combat::clear() {
+ Common::fill(&_attackMonsters[0], &_attackMonsters[26], -1);
+}
+
+void Combat::giveCharDamage(int damage, DamageType attackType, int charIndex) {
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Scripts &scripts = *_vm->_scripts;
+ SoundManager &sound = *_vm->_sound;
+ int charIndex1 = charIndex + 1;
+ int selectedIndex1 = 0;
+ int selectedIndex2 = 0;
+ bool breakFlag = false;
+
+ screen.closeWindows();
+
+ int idx = (int)party._activeParty.size();
+ if (!scripts._v2) {
+ for (idx = 0; idx < (int)party._activeParty.size(); ++idx) {
+ Character &c = party._activeParty[idx];
+ Condition condition = c.worstCondition();
+
+ if (!(condition >= UNCONSCIOUS && condition <= ERADICATED)) {
+ if (!selectedIndex1) {
+ selectedIndex1 = idx + 1;
+ } else {
+ selectedIndex2 = idx + 1;
+ break;
+ }
+ }
+ }
+ }
+ if (idx == (int)party._activeParty.size()) {
+ selectedIndex1 = scripts._v2 ? charIndex : 0;
+ goto loop;
+ }
+
+ for (;;) {
+ // The if below is to get around errors due to the
+ // goto I was forced to use when reimplementing this method
+ if (true) {
+ Character &c = party._activeParty[selectedIndex1];
+ c._conditions[ASLEEP] = 0; // Force character to be awake
+
+ int frame = 0, fx = 0;
+ switch (attackType) {
+ case DT_PHYSICAL:
+ fx = 29;
+ break;
+ case DT_MAGICAL:
+ frame = 6;
+ fx = 27;
+ break;
+ case DT_FIRE:
+ damage -= party._fireResistence;
+ frame = 1;
+ fx = 22;
+ break;
+ case DT_ELECTRICAL:
+ damage -= party._electricityResistence;
+ frame = 2;
+ fx = 23;
+ break;
+ case DT_COLD:
+ damage -= party._coldResistence;
+ frame = 3;
+ fx = 24;
+ break;
+ case DT_POISON:
+ damage -= party._poisonResistence;
+ frame = 4;
+ fx = 26;
+ break;
+ case DT_ENERGY:
+ frame = 5;
+ fx = 25;
+ break;
+ case DT_SLEEP:
+ fx = 38;
+ break;
+ default:
+ break;
+ }
+
+ // All attack types other than physical allow for saving
+ // throws to reduce the damage
+ if (attackType != DT_PHYSICAL) {
+ while (c.charSavingThrow(attackType) && damage > 0)
+ damage /= 2;
+ }
+
+ // Draw the attack effect on the character sprite
+ sound.playFX(fx);
+ _powSprites.draw(screen, frame,
+ Common::Point(CHAR_FACES_X[selectedIndex1], 150));
+ screen._windows[33].update();
+
+ // Reduce damage if power shield active, and set it zero
+ // if the damage amount has become negative.. you wouldn't
+ // want attacks healing the characters
+ if (party._powerShield)
+ damage -= party._powerShield;
+ if (damage < 0)
+ damage = 0;
+
+ // TODO: This seems weird.. maybe I've got attack types wrong..
+ // why should attack type 7 (DT_SLEEP) set the dead condition?
+ if (attackType == DT_SLEEP) {
+ damage = c._currentHp;
+ c._conditions[DEAD] = 1;
+ }
+
+ // Subtract the hit points from the character
+ c.subtractHitPoints(damage);
+ }
+
+ if (selectedIndex2) {
+ ++selectedIndex1;
+loop:
+ if ((scripts._v2 ? charIndex1 : (int)party._activeParty.size()) > selectedIndex1)
+ break;
+ }
+
+ // Break check and if not, move to other index
+ if (!selectedIndex2 || breakFlag)
+ break;
+
+ selectedIndex1 = selectedIndex2 - 1;
+ breakFlag = true;
+ }
+}
+
+void Combat::doCharDamage(Character &c, int charNum, int monsterDataIndex) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ MonsterStruct &monsterData = map._monsterData[monsterDataIndex];
+
+ // Attacked characters are automatically woken up
+ c._conditions[ASLEEP] = 0;
+
+ // Figure out the damage amount
+ int damage = 0;
+ for (int idx = 0; idx < monsterData._strikes; ++idx)
+ damage += _vm->getRandomNumber(1, monsterData._dmgPerStrike);
+
+
+ int fx = 29, frame = 0;
+ if (monsterData._attackType) {
+ if (c.charSavingThrow(monsterData._attackType))
+ damage /= 2;
+
+ switch (monsterData._attackType) {
+ case DT_MAGICAL:
+ frame = 6;
+ fx = 27;
+ break;
+ case DT_FIRE:
+ damage -= party._fireResistence;
+ frame = 1;
+ fx = 22;
+ break;
+ case DT_ELECTRICAL:
+ damage -= party._electricityResistence;
+ frame = 2;
+ fx = 23;
+ break;
+ case DT_COLD:
+ damage -= party._coldResistence;
+ frame = 3;
+ fx = 24;
+ break;
+ case DT_POISON:
+ damage -= party._poisonResistence;
+ frame = 4;
+ fx = 26;
+ break;
+ case DT_ENERGY:
+ frame = 5;
+ fx = 25;
+ break;
+ default:
+ break;
+ }
+
+ while (damage > 0 && c.charSavingThrow(monsterData._attackType))
+ damage /= 2;
+ }
+
+ sound.playFX(fx);
+ intf._charPowSprites.draw(screen, frame, Common::Point(CHAR_FACES_X[charNum], 150));
+ screen._windows[33].update();
+
+ damage -= party._powerShield;
+ if (damage > 0 && monsterData._specialAttack && !c.charSavingThrow(DT_PHYSICAL)) {
+ switch (monsterData._specialAttack) {
+ case SA_POISON:
+ if (!++c._conditions[POISONED])
+ c._conditions[POISONED] = -1;
+ sound.playFX(26);
+ break;
+ case SA_DISEASE:
+ if (!++c._conditions[DISEASED])
+ c._conditions[DISEASED] = -1;
+ sound.playFX(26);
+ break;
+ case SA_INSANE:
+ if (!++c._conditions[INSANE])
+ c._conditions[INSANE] = -1;
+ sound.playFX(28);
+ break;
+ case SA_SLEEP:
+ if (!++c._conditions[ASLEEP])
+ c._conditions[ASLEEP] = -1;
+ sound.playFX(36);
+ break;
+ case SA_CURSEITEM:
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ if (c._weapons[idx]._id != 34)
+ c._weapons[idx]._bonusFlags |= ITEMFLAG_CURSED;
+ c._armor[idx]._bonusFlags |= ITEMFLAG_CURSED;
+ c._accessories[idx]._bonusFlags |= ITEMFLAG_CURSED;
+ c._misc[idx]._bonusFlags |= ITEMFLAG_CURSED;;
+ }
+ sound.playFX(37);
+ break;
+ case SA_DRAINSP:
+ c._currentSp = 0;
+ sound.playFX(37);
+ break;
+ case SA_CURSE:
+ if (!++c._conditions[CURSED])
+ c._conditions[CURSED] = -1;
+ sound.playFX(37);
+ break;
+ case SA_PARALYZE:
+ if (!++c._conditions[PARALYZED])
+ c._conditions[PARALYZED] = -1;
+ sound.playFX(37);
+ break;
+ case SA_UNCONSCIOUS:
+ if (!++c._conditions[UNCONSCIOUS])
+ c._conditions[UNCONSCIOUS] = -1;
+ sound.playFX(37);
+ break;
+ case SA_CONFUSE:
+ if (!++c._conditions[CONFUSED])
+ c._conditions[CONFUSED] = -1;
+ sound.playFX(28);
+ break;
+ case SA_BREAKWEAPON:
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ XeenItem &weapon = c._weapons[idx];
+ if (weapon._id != 34 && weapon._id != 0 && weapon._frame != 0) {
+ weapon._bonusFlags |= ITEMFLAG_BROKEN;
+ weapon._frame = 0;
+ }
+ }
+ sound.playFX(37);
+ break;
+ case SA_WEAKEN:
+ if (!++c._conditions[WEAK])
+ c._conditions[WEAK] = -1;
+ sound.playFX(36);
+ break;
+ case SA_ERADICATE:
+ if (!++c._conditions[ERADICATED])
+ c._conditions[ERADICATED] = -1;
+ c._items.breakAllItems();
+ sound.playFX(37);
+
+ if (c._currentHp > 0)
+ c._currentHp = 0;
+ break;
+ case SA_AGING:
+ ++c._tempAge;
+ sound.playFX(37);
+ break;
+ case SA_DEATH:
+ if (!++c._conditions[DEAD])
+ c._conditions[DEAD] = -1;
+ sound.playFX(38);
+ if (c._currentHp > 0)
+ c._currentHp = 0;
+ break;
+ case SA_STONE:
+ if (!++c._conditions[STONED])
+ c._conditions[STONED] = -1;
+ sound.playFX(38);
+ if (c._currentHp > 0)
+ c._currentHp = 0;
+ break;
+
+ default:
+ break;
+ }
+
+ c.subtractHitPoints(damage);
+ }
+
+ events.ipause(2);
+ intf.drawParty(true);
+}
+
+void Combat::moveMonsters() {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+
+ if (!_moveMonsters)
+ return;
+
+ intf._tillMove = 0;
+ if (intf._charsShooting)
+ return;
+
+ Common::fill(&_monsterMap[0][0], &_monsterMap[32][32], 0);
+ Common::fill(&_monsterMoved[0], &_monsterMoved[MAX_NUM_MONSTERS], false);
+ Common::fill(&_rangeAttacking[0], &_rangeAttacking[MAX_NUM_MONSTERS], false);
+ Common::fill(&_gmonHit[0], &_gmonHit[36], -1);
+ _dangerPresent = false;
+
+ for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
+ MazeMonster &monster = map._mobData._monsters[idx];
+ if (monster._position.y < 32) {
+ _monsterMap[monster._position.y][monster._position.x]++;
+ }
+ }
+
+ for (int loopNum = 0; loopNum < 2; ++loopNum) {
+ int arrIndex = -1;
+ for (int yDiff = 3; yDiff >= -3; --yDiff) {
+ for (int xDiff = -3; xDiff <= 3; ++xDiff) {
+ Common::Point pt = party._mazePosition + Common::Point(xDiff, yDiff);
+ ++arrIndex;
+
+ for (int idx = 0; idx < (int)map._mobData._monsters.size(); ++idx) {
+ MazeMonster &monster = map._mobData._monsters[idx];
+ MonsterStruct &monsterData = *monster._monsterData;
+
+ if (pt == monster._position) {
+ _dangerPresent = true;
+ if ((monster._isAttacking || _vm->_mode == MODE_SLEEPING)
+ && !_monsterMoved[idx]) {
+ if (party._mazePosition.x == pt.x || party._mazePosition.y == pt.y) {
+ // Check for range attacks
+ if (monsterData._rangeAttack && !_rangeAttacking[idx]
+ && _attackMonsters[0] != idx && _attackMonsters[1] != idx
+ && _attackMonsters[2] != idx && !monster._damageType) {
+ // Setup monster for attacking
+ setupMonsterAttack(monster._spriteId, pt);
+ _rangeAttacking[idx] = true;
+ }
+ }
+
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ case DIR_SOUTH:
+ if (monsterCanMove(pt, MONSTER_GRID_BITMASK[MONSTER_GRID_BITINDEX1[arrIndex]],
+ MONSTER_GRID_X[arrIndex], MONSTER_GRID_Y[arrIndex], idx)) {
+ // Move the monster
+ moveMonster(idx, Common::Point(MONSTER_GRID_X[arrIndex], MONSTER_GRID_Y[arrIndex]));
+ } else {
+ if (monsterCanMove(pt, MONSTER_GRID_BITMASK[MONSTER_GRID_BITINDEX2[arrIndex]],
+ arrIndex >= 21 && arrIndex <= 27 ? MONSTER_GRID3[arrIndex] : 0,
+ arrIndex >= 21 && arrIndex <= 27 ? 0 : MONSTER_GRID3[arrIndex],
+ idx)) {
+ if (arrIndex >= 21 && arrIndex <= 27) {
+ moveMonster(idx, Common::Point(MONSTER_GRID3[arrIndex], 0));
+ } else {
+ moveMonster(idx, Common::Point(0, MONSTER_GRID3[arrIndex]));
+ }
+ }
+ }
+ break;
+
+ case DIR_EAST:
+ case DIR_WEST:
+ if (monsterCanMove(pt, MONSTER_GRID_BITMASK[MONSTER_GRID_BITINDEX2[arrIndex]],
+ arrIndex >= 21 && arrIndex <= 27 ? MONSTER_GRID3[arrIndex] : 0,
+ arrIndex >= 21 && arrIndex <= 27 ? 0 : MONSTER_GRID3[arrIndex],
+ idx)) {
+ if (arrIndex >= 21 && arrIndex <= 27) {
+ moveMonster(idx, Common::Point(MONSTER_GRID3[arrIndex], 0));
+ } else {
+ moveMonster(idx, Common::Point(0, MONSTER_GRID3[arrIndex]));
+ }
+ } else if (monsterCanMove(pt, MONSTER_GRID_BITMASK[MONSTER_GRID_BITINDEX1[arrIndex]],
+ MONSTER_GRID_X[arrIndex], MONSTER_GRID_Y[arrIndex], idx)) {
+ moveMonster(idx, Common::Point(MONSTER_GRID_X[arrIndex], MONSTER_GRID_Y[arrIndex]));
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ monsterOvercome();
+ if (_monstersAttacking)
+ monstersAttack();
+}
+
+void Combat::monstersAttack() {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ int powNum = -1;
+ MonsterStruct *monsterData = nullptr;
+ OutdoorDrawList &outdoorList = intf._outdoorList;
+ IndoorDrawList &indoorList = intf._indoorList;
+
+ for (int idx = 0; idx < 36; ++idx) {
+ if (_gmonHit[idx] != -1) {
+ monsterData = &map._monsterData[_gmonHit[idx]];
+ powNum = MONSTER_SHOOT_POW[monsterData->_attackType];
+ if (powNum != 12)
+ break;
+ }
+ }
+
+ _powSprites.load(Common::String::format("pow%d.icn", powNum));
+ sound.playFX(ATTACK_TYPE_FX[monsterData->_attackType]);
+
+ for (int charNum = 0; charNum < MAX_PARTY_COUNT; ++charNum) {
+ if (!_shooting[charNum])
+ continue;
+
+ if (map._isOutdoors) {
+ outdoorList._attackImgs1[charNum]._scale = 3;
+ outdoorList._attackImgs2[charNum]._scale = 7;
+ outdoorList._attackImgs3[charNum]._scale = 11;
+ outdoorList._attackImgs4[charNum]._scale = 15;
+ outdoorList._attackImgs1[charNum]._sprites = nullptr;
+ outdoorList._attackImgs2[charNum]._sprites = nullptr;
+ outdoorList._attackImgs3[charNum]._sprites = nullptr;
+ outdoorList._attackImgs4[charNum]._sprites = nullptr;
+
+ switch (_shooting[charNum]) {
+ case 1:
+ outdoorList._attackImgs1[charNum]._sprites = &_powSprites;
+ break;
+ case 2:
+ outdoorList._attackImgs2[charNum]._sprites = &_powSprites;
+ break;
+ default:
+ outdoorList._attackImgs3[charNum]._sprites = &_powSprites;
+ break;
+ }
+ } else {
+ indoorList._attackImgs1[charNum]._scale = 3;
+ indoorList._attackImgs2[charNum]._scale = 7;
+ indoorList._attackImgs3[charNum]._scale = 11;
+ indoorList._attackImgs4[charNum]._scale = 15;
+ indoorList._attackImgs1[charNum]._sprites = nullptr;
+ indoorList._attackImgs2[charNum]._sprites = nullptr;
+ indoorList._attackImgs3[charNum]._sprites = nullptr;
+ indoorList._attackImgs4[charNum]._sprites = nullptr;
+
+ switch (_shooting[charNum]) {
+ case 1:
+ indoorList._attackImgs1[charNum]._sprites = &_powSprites;
+ break;
+ case 2:
+ indoorList._attackImgs2[charNum]._sprites = &_powSprites;
+ break;
+ default:
+ indoorList._attackImgs3[charNum]._sprites = &_powSprites;
+ break;
+ }
+ }
+ }
+
+ // Wait whilst the attacking effect is done
+ do {
+ intf.draw3d(true);
+ events.pollEventsAndWait();
+ } while (!_vm->shouldQuit() && intf._isAttacking);
+
+ endAttack();
+
+ if (_vm->_mode != MODE_COMBAT) {
+ // Combat wasn't previously active, but it is now. Set up
+ // the combat party from the currently active party
+ setupCombatParty();
+ }
+
+ for (int idx = 0; idx < 36; ++idx) {
+ if (_gmonHit[idx] != -1)
+ doMonsterTurn(_gmonHit[idx]);
+ }
+
+ _monstersAttacking = false;
+
+ if (_vm->_mode != MODE_SLEEPING) {
+ for (uint charNum = 0; charNum < party._activeParty.size(); ++charNum) {
+ Condition condition = party._activeParty[charNum].worstCondition();
+
+ if (condition != ASLEEP && (condition < PARALYZED || condition == NO_CONDITION)) {
+ _vm->_mode = MODE_1;
+ break;
+ }
+ }
+ }
+}
+
+void Combat::setupMonsterAttack(int monsterDataIndex, const Common::Point &pt) {
+ Party &party = *_vm->_party;
+
+ for (int idx = 0; idx < 36; ++idx) {
+ if (_gmonHit[idx] != -1) {
+ int result = stopAttack(pt - party._mazePosition);
+ if (result) {
+ _monstersAttacking = true;
+ _gmonHit[idx] = monsterDataIndex;
+
+ if (result != 1) {
+ for (int charNum = 0; charNum < MAX_PARTY_COUNT; ++charNum) {
+ if (!_shooting[charNum]) {
+ _shooting[charNum] = COMBAT_SHOOTING[result - 1];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+bool Combat::monsterCanMove(const Common::Point &pt, int wallShift,
+ int xDiff, int yDiff, int monsterId) {
+ Map &map = *_vm->_map;
+ MazeMonster &monster = map._mobData._monsters[monsterId];
+ MonsterStruct &monsterData = *monster._monsterData;
+
+ Common::Point tempPos = pt;
+ if (map._isOutdoors) {
+ tempPos += Common::Point(xDiff, yDiff);
+ wallShift = 4;
+ }
+ int v = map.mazeLookup(tempPos, wallShift);
+
+ if (!map._isOutdoors) {
+ return v <= map.mazeData()._difficulties._wallNoPass;
+ } else {
+ SurfaceType surfaceType;
+ switch (v) {
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 8:
+ case 11:
+ case 13:
+ case 14:
+ surfaceType = (SurfaceType)map.mazeData()._surfaceTypes[map._currentSurfaceId];
+ if (surfaceType == SURFTYPE_WATER || surfaceType == SURFTYPE_DWATER) {
+ return monsterData._flying || monster._spriteId == 59;
+ } else if (surfaceType == SURFTYPE_SPACE) {
+ return monsterData._flying;
+ } else {
+ return _vm->_files->_isDarkCc || monster._spriteId != 59;
+ }
+ default:
+ return v <= map.mazeData()._difficulties._wallNoPass;
+ }
+ }
+}
+
+void Combat::moveMonster(int monsterId, const Common::Point &moveDelta) {
+ Map &map = *_vm->_map;
+ MazeMonster &monster = map._mobData._monsters[monsterId];
+ Common::Point newPos = monster._position + moveDelta;
+
+ if (_monsterMap[newPos.y][newPos.x] < 3 && !monster._damageType && _moveMonsters) {
+ // Adjust monster's position
+ ++_monsterMap[newPos.y][newPos.x];
+ --_monsterMap[monster._position.y][monster._position.x];
+ monster._position = newPos;
+ _monsterMoved[monsterId] = true;
+ }
+}
+
+void Combat::endAttack() {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ intf._isAttacking = false;
+ IndoorDrawList &indoorList = intf._indoorList;
+ OutdoorDrawList &outdoorList = intf._outdoorList;
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (map._isOutdoors) {
+ outdoorList._attackImgs1[idx]._scale = 0;
+ outdoorList._attackImgs2[idx]._scale = 0;
+ outdoorList._attackImgs3[idx]._scale = 0;
+ outdoorList._attackImgs4[idx]._scale = 0;
+ outdoorList._attackImgs1[idx]._sprites = nullptr;
+ outdoorList._attackImgs2[idx]._sprites = nullptr;
+ outdoorList._attackImgs3[idx]._sprites = nullptr;
+ outdoorList._attackImgs4[idx]._sprites = nullptr;
+ } else {
+ indoorList._attackImgs1[idx]._scale = 0;
+ indoorList._attackImgs2[idx]._scale = 0;
+ indoorList._attackImgs3[idx]._scale = 0;
+ indoorList._attackImgs4[idx]._scale = 0;
+ indoorList._attackImgs1[idx]._sprites = nullptr;
+ indoorList._attackImgs2[idx]._sprites = nullptr;
+ indoorList._attackImgs3[idx]._sprites = nullptr;
+ indoorList._attackImgs4[idx]._sprites = nullptr;
+ }
+ }
+
+ Common::fill(&_shooting[0], &_shooting[MAX_PARTY_COUNT], false);
+}
+
+void Combat::monsterOvercome() {
+ Map &map = *_vm->_map;
+
+ for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
+ MazeMonster &monster = map._mobData._monsters[idx];
+ int dataIndex = monster._spriteId;
+
+ if (monster._damageType != DT_PHYSICAL && monster._damageType != DT_DRAGONSLEEP) {
+ // Do a saving throw for monster
+ if (dataIndex <= _vm->getRandomNumber(1, dataIndex + 50))
+ monster._damageType = 0;
+ }
+ }
+}
+
+void Combat::doMonsterTurn(int monsterId) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ if (_monstersAttacking) {
+ int monsterIndex;
+ switch (_whosTurn - _combatParty.size()) {
+ case 0:
+ monsterIndex = _attackMonsters[0];
+ intf._indoorList[156]._scale = 0;
+ break;
+ case 1:
+ monsterIndex = _attackMonsters[1];
+ intf._indoorList[150]._scale = 0;
+ break;
+ case 2:
+ default:
+ monsterIndex = _attackMonsters[2];
+ intf._indoorList[153]._scale = 0;
+ }
+
+ MazeMonster &monster = map._mobData._monsters[monsterIndex];
+ MonsterStruct &monsterData = *monster._monsterData;
+ if (monster._damageType)
+ return;
+
+ monster._frame = 8;
+ monster._fieldA = 3;
+ monster._field9 = 0;
+ intf.draw3d(true);
+ intf.draw3d(true);
+
+ File f(Common::String::format("%s.voc", monsterData._attackVoc.c_str()));
+ sound.playSample(&f, 0);
+ monsterId = monster._spriteId;
+ }
+
+ MonsterStruct &monsterData = map._monsterData[monsterId];
+ bool flag = false;
+ for (int attackNum = 0; attackNum < monsterData._numberOfAttacks; ++attackNum) {
+ int charNum = -1;
+ bool isHated = false;
+
+ if (monsterData._hatesClass != -1) {
+ if (monsterData._hatesClass == 15)
+ // Monster hates all classes
+ goto loop;
+
+ for (uint charIndex = 0; charIndex < _combatParty.size(); ++charIndex) {
+ Character &c = *_combatParty[charIndex];
+ Condition cond = c.worstCondition();
+ if (cond >= PARALYZED && cond <= ERADICATED)
+ continue;
+
+ isHated = false;
+ switch (monsterData._hatesClass) {
+ case CLASS_KNIGHT:
+ case CLASS_PALADIN:
+ case CLASS_ARCHER:
+ case CLASS_CLERIC:
+ case CLASS_SORCERER:
+ case CLASS_ROBBER:
+ case CLASS_NINJA:
+ case CLASS_BARBARIAN:
+ case CLASS_DRUID:
+ case CLASS_RANGER:
+ isHated = c._class == monsterData._hatesClass;
+ break;
+ case 12:
+ isHated = c._race == DWARF;
+ break;
+ default:
+ break;
+ }
+
+ if (isHated) {
+ charNum = charIndex;
+ break;
+ }
+ }
+ }
+
+ if (!isHated) {
+ // No particularly hated foe, so decide which character to start with
+ switch (_combatParty.size()) {
+ case 1:
+ charNum = 0;
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ charNum = _vm->getRandomNumber(0, _combatParty.size() - 1);
+ break;
+ case 6:
+ if (_vm->getRandomNumber(1, 6) == 6)
+ charNum = 5;
+ else
+ charNum = _vm->getRandomNumber(0, 4);
+ break;
+ }
+ }
+
+ // Attacking loop
+ do {
+ if (!flag) {
+ Condition cond = _combatParty[charNum]->worstCondition();
+
+ if (cond >= PARALYZED && cond <= ERADICATED) {
+ Common::Array<int> ableChars;
+ bool skip = false;
+
+ for (uint idx = 0; idx < _combatParty.size() && !skip; ++idx) {
+ switch (_combatParty[idx]->worstCondition()) {
+ case PARALYZED:
+ case UNCONSCIOUS:
+ if (flag)
+ skip = true;
+ break;
+ case DEAD:
+ case STONED:
+ case ERADICATED:
+ break;
+ default:
+ ableChars.push_back(idx);
+ break;
+ }
+ }
+
+ if (!skip) {
+ if (ableChars.size() == 0) {
+ party._dead = true;
+ _vm->_mode = MODE_1;
+ return;
+ }
+
+ charNum = ableChars[_vm->getRandomNumber(0, ableChars.size() - 1)];
+ }
+ }
+ }
+
+ // Unconditional if to get around goto initialization errors
+ if (true) {
+ Character &c = *_combatParty[charNum];
+ if (monsterData._attackType != DT_PHYSICAL || c._conditions[ASLEEP]) {
+ doCharDamage(c, charNum, monsterId);
+ } else {
+ int v = _vm->getRandomNumber(1, 20);
+ if (v == 1) {
+ sound.playFX(6);
+ } else {
+ if (v == 20)
+ doCharDamage(c, charNum, monsterId);
+ v += monsterData._hitChance / 4 + _vm->getRandomNumber(1,
+ monsterData._hitChance);
+
+ int ac = c.getArmorClass() + (!_charsBlocked[charNum] ? 10 :
+ c.getCurrentLevel() / 2 + 15);
+ if (ac > v) {
+ sound.playFX(6);
+ } else {
+ doCharDamage(c, charNum, monsterId);
+ }
+ }
+ }
+
+ if (flag)
+ break;
+ }
+loop:
+ flag = true;
+ } while (++charNum < (int)_combatParty.size());
+ }
+
+ intf.drawParty(true);
+}
+
+int Combat::stopAttack(const Common::Point &diffPt) {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Direction dir = party._mazeDirection;
+ const Common::Point &mazePos = party._mazePosition;
+
+ if (map._isOutdoors) {
+ if (diffPt.x > 0) {
+ for (int x = 1; x <= diffPt.x; ++x) {
+ int v = map.mazeLookup(Common::Point(mazePos.x + x, mazePos.y), 0, 8);
+ if (v)
+ return 0;
+ }
+ return (dir == DIR_EAST) ? diffPt.x + 1 : 1;
+
+ } else if (diffPt.x < 0) {
+ for (int x = diffPt.x; x < 0; ++x) {
+ int v = map.mazeLookup(Common::Point(mazePos.x + x, mazePos.y), 4);
+ switch (v) {
+ case 0:
+ case 2:
+ case 4:
+ case 5:
+ case 8:
+ case 11:
+ case 13:
+ case 14:
+ break;
+ default:
+ return 0;
+ }
+ }
+ return dir == DIR_WEST ? diffPt.x * -1 + 1 : 1;
+
+ } else if (diffPt.y <= 0) {
+ for (int y = diffPt.y; y < 0; ++y) {
+ int v = map.mazeLookup(Common::Point(mazePos.x, mazePos.y + y), 4);
+ switch (v) {
+ case 0:
+ case 2:
+ case 4:
+ case 5:
+ case 8:
+ case 11:
+ case 13:
+ case 14:
+ break;
+ default:
+ return 0;
+ }
+ }
+ return party._mazeDirection == DIR_SOUTH ? diffPt.y * -1 + 1 : 1;
+
+ } else {
+ for (int y = 1; y <= diffPt.y; ++y) {
+ int v = map.mazeLookup(Common::Point(mazePos.x, mazePos.y + y), 4);
+ switch (v) {
+ case 0:
+ case 2:
+ case 4:
+ case 5:
+ case 8:
+ case 11:
+ case 13:
+ case 14:
+ break;
+ default:
+ return 0;
+ }
+ }
+ return dir == DIR_NORTH ? diffPt.y + 1 : 1;
+ }
+ } else {
+ // Indoors
+ if (diffPt.x > 0) {
+ for (int x = 1; x <= diffPt.x; ++x) {
+ int v = map.mazeLookup(Common::Point(mazePos.x + x, mazePos.y), 0, 8);
+ if (v)
+ return 0;
+ }
+ return dir == DIR_EAST ? diffPt.x + 1 : 1;
+
+ } else if (diffPt.x < 0) {
+ for (int x = diffPt.x; x < 0; ++x) {
+ int v = map.mazeLookup(Common::Point(mazePos.x + x, mazePos.y), 0, 0x800);
+ if (v)
+ return 0;
+ }
+ return dir == DIR_WEST ? diffPt.x * -1 + 1 : 1;
+
+ } else if (diffPt.y <= 0) {
+ for (int y = diffPt.y; y < 0; ++y) {
+ int v = map.mazeLookup(Common::Point(mazePos.x, mazePos.y + y), 0, 0x8000);
+ if (v)
+ return 0;
+ }
+ return dir == DIR_SOUTH ? diffPt.y * -1 + 1 : 1;
+
+ } else {
+ for (int y = 1; y <= diffPt.y; ++y) {
+ int v = map.mazeLookup(Common::Point(mazePos.x, mazePos.y + y), 0, 0x80);
+ if (v)
+ return 0;
+ }
+ return dir == DIR_NORTH ? diffPt.y + 1 : 1;
+ }
+ }
+}
+
+void Combat::setupCombatParty() {
+ Party &party = *_vm->_party;
+
+ _combatParty.clear();
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx)
+ _combatParty.push_back(&party._activeParty[idx]);
+}
+
+void Combat::setSpeedTable() {
+ Map &map = *_vm->_map;
+ Common::Array<int> charSpeeds;
+ bool hasSpeed = _whosSpeed != -1 && _whosSpeed < (int)_speedTable.size();
+ int oldSpeed = hasSpeed ? _speedTable[_whosSpeed] : 0;
+
+ // Set up speeds for party membres
+ int maxSpeed = 0;
+ for (uint charNum = 0; charNum < _combatParty.size(); ++charNum) {
+ Character &c = *_combatParty[charNum];
+ charSpeeds.push_back(c.getStat(SPEED));
+
+ maxSpeed = MAX(charSpeeds[charNum], maxSpeed);
+ }
+
+ // Add in speeds of attacking monsters
+ for (int monsterNum = 0; monsterNum < 3; ++monsterNum) {
+ if (_attackMonsters[monsterNum] != -1) {
+ MazeMonster &monster = map._mobData._monsters[_attackMonsters[monsterNum]];
+ MonsterStruct &monsterData = *monster._monsterData;
+ charSpeeds.push_back(monsterData._speed);
+
+ maxSpeed = MAX(maxSpeed, monsterData._speed);
+ } else {
+ charSpeeds.push_back(0);
+ }
+ }
+
+ // Populate the _speedTable list with the character/monster indexes
+ // in order of attacking speed
+ _speedTable.clear();
+ for (; maxSpeed >= 0; --maxSpeed) {
+ for (uint idx = 0; idx < charSpeeds.size(); ++idx) {
+ if (charSpeeds[idx] == maxSpeed)
+ _speedTable.push_back(idx);
+ }
+ }
+
+ if (hasSpeed) {
+ if (_speedTable[_whosSpeed] != oldSpeed) {
+ for (uint idx = 0; idx < charSpeeds.size(); ++idx) {
+ if (oldSpeed == _speedTable[idx]) {
+ _whosSpeed = idx;
+ break;
+ }
+ }
+ }
+ }
+}
+
+bool Combat::allHaveGone() const {
+ for (uint idx = 0; idx < _charsGone.size(); ++idx) {
+ if (!_charsGone[idx]) {
+ if (idx >= _combatParty.size()) {
+ return false;
+ } else {
+ Condition condition = _combatParty[idx]->worstCondition();
+ if (condition < PARALYZED || condition == NO_CONDITION)
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Combat::charsCantAct() const {
+ for (uint idx = 0; idx < _combatParty.size(); ++idx) {
+ if (!_combatParty[idx]->isDisabledOrDead())
+ return false;
+ }
+
+ return true;
+}
+
+Common::String Combat::getMonsterDescriptions() {
+ Map &map = *_vm->_map;
+ Common::String lines[3];
+
+ // Get names of monsters attacking, if any
+ for (int idx = 0; idx < 3; ++idx) {
+ if (_attackMonsters[idx] != -1) {
+ MazeMonster &monster = map._mobData._monsters[_attackMonsters[idx]];
+ MonsterStruct &monsterData = *monster._monsterData;
+ int textColor = monster.getTextColor();
+
+ Common::String format = "\n\v020\f%2u%s\fd";
+ format.setChar('2' + idx, 3);
+ lines[idx] = Common::String::format(format.c_str(), textColor,
+ monsterData._name.c_str());
+ }
+ }
+
+ if (_monsterIndex == 2 && _attackMonsters[2] != -1) {
+ _monster2Attack = _attackMonsters[2];
+ } if (_monsterIndex == 1 && _attackMonsters[1] != -1) {
+ _monster2Attack = _attackMonsters[1];
+ } else {
+ _monster2Attack = _attackMonsters[0];
+ _monsterIndex = 0;
+ }
+
+ return Common::String::format(COMBAT_DETAILS, lines[0].c_str(),
+ lines[1].c_str(), lines[2].c_str());
+}
+
+void Combat::attack(Character &c, RangeType rangeType) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ int damage = _monsterDamage;
+
+ if (_monster2Attack == -1)
+ return;
+
+ MazeMonster &monster = map._mobData._monsters[_monster2Attack];
+ int monsterDataIndex = monster._spriteId;
+ MonsterStruct &monsterData = map._monsterData[monsterDataIndex];
+
+ if (rangeType) {
+ if (_shootType != ST_1 || _damageType == DT_MAGIC_ARROW) {
+ if (!monsterData._magicResistence || monsterData._magicResistence <=
+ _vm->getRandomNumber(1, 100 + _oldCharacter->getCurrentLevel())) {
+ if (_monsterDamage != 0) {
+ attack2(damage, rangeType);
+ setSpeedTable();
+ } else {
+ switch (_damageType) {
+ case DT_SLEEP:
+ if (monsterData._monsterType == MONSTER_ANIMAL || monsterData._monsterType == MONSTER_HUMANOID) {
+ if (_vm->getRandomNumber(1, 50 + monsterDataIndex) > monsterDataIndex)
+ monster._damageType = DT_SLEEP;
+ }
+ break;
+ case DT_FINGEROFDEATH:
+ if ((monsterData._monsterType == MONSTER_ANIMAL || monsterData._monsterType == MONSTER_HUMANOID)
+ && !monsterSavingThrow(monsterDataIndex)) {
+ damage = MIN(monster._hp, 50);
+ attack2(damage, RT_ALL);
+ setSpeedTable();
+ }
+ break;
+ case DT_HOLYWORD:
+ if (monsterData._monsterType == MONSTER_UNDEAD) {
+ attack2(monster._hp, RT_ALL);
+ setSpeedTable();
+ }
+ break;
+ case DT_MASS_DISTORTION:
+ attack2(MAX(monster._hp / 2, 1), RT_ALL);
+ setSpeedTable();
+ break;
+ case DT_UNDEAD:
+ if (monsterData._monsterType == MONSTER_UNDEAD)
+ damage = 25;
+ else
+ rangeType = RT_ALL;
+ attack2(damage, rangeType);
+ setSpeedTable();
+ break;
+ case DT_BEASTMASTER:
+ if ((monsterData._monsterType == MONSTER_ANIMAL || monsterData._monsterType == MONSTER_HUMANOID)
+ && !monsterSavingThrow(monsterDataIndex)) {
+ monster._damageType = DT_BEASTMASTER;
+ }
+ break;
+ case DT_DRAGONSLEEP:
+ if (monsterData._monsterType == MONSTER_DRAGON && !monsterSavingThrow(monsterDataIndex))
+ monster._damageType = DT_DRAGONSLEEP;
+ break;
+ case DT_GOLEMSTOPPER:
+ if (monsterData._monsterType == MONSTER_GOLEM) {
+ attack2(100, rangeType);
+ setSpeedTable();
+ }
+ break;
+ case DT_HYPNOTIZE:
+ if ((monsterData._monsterType == MONSTER_ANIMAL || monsterData._monsterType == MONSTER_HUMANOID)
+ && !monsterSavingThrow(monsterDataIndex)) {
+ monster._damageType = _damageType;
+ }
+ break;
+ case DT_INSECT_SPRAY:
+ if (monsterData._monsterType == MONSTER_INSECT) {
+ attack2(25, rangeType);
+ setSpeedTable();
+ }
+ break;
+ case DT_MAGIC_ARROW:
+ attack2(8, rangeType);
+ setSpeedTable();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ } else {
+ Common::fill(&_elemPow[0], &_elemPow[PARTY_AND_MONSTERS], ELEM_FIRE);
+ damage = 0;
+
+ for (uint charIndex = 0; charIndex < party._activeParty.size(); ++charIndex) {
+ Character &ch = party._activeParty[charIndex];
+
+ if (_shooting[charIndex] && !_missedShot[charIndex]) {
+ if (!hitMonster(ch, rangeType)) {
+ ++_missedShot[charIndex];
+ } else {
+ damage = _monsterDamage ? _monsterDamage : _weaponDamage;
+ _shooting[charIndex] = 0;
+ attack2(damage, rangeType);
+
+ if (map._isOutdoors) {
+ intf._outdoorList._attackImgs1[charIndex]._scale = 0;
+ intf._outdoorList._attackImgs1[charIndex]._sprites = nullptr;
+ intf._outdoorList._attackImgs2[charIndex]._scale = 0;
+ intf._outdoorList._attackImgs2[charIndex]._sprites = nullptr;
+ intf._outdoorList._attackImgs3[charIndex]._scale = 0;
+ intf._outdoorList._attackImgs3[charIndex]._sprites = nullptr;
+ intf._outdoorList._attackImgs4[charIndex]._scale = 0;
+ intf._outdoorList._attackImgs4[charIndex]._sprites = nullptr;
+ } else {
+ intf._indoorList._attackImgs1[charIndex]._scale = 0;
+ intf._indoorList._attackImgs1[charIndex]._sprites = nullptr;
+ intf._indoorList._attackImgs2[charIndex]._scale = 0;
+ intf._indoorList._attackImgs2[charIndex]._sprites = nullptr;
+ intf._indoorList._attackImgs3[charIndex]._scale = 0;
+ intf._indoorList._attackImgs3[charIndex]._sprites = nullptr;
+ intf._indoorList._attackImgs4[charIndex]._scale = 0;
+ intf._indoorList._attackImgs4[charIndex]._sprites = nullptr;
+ }
+
+ if (_monster2Attack == -1)
+ return;
+ }
+ }
+ }
+ }
+ } else {
+ _damageType = DT_PHYSICAL;
+ int divisor = 0;
+ switch (c._class) {
+ case CLASS_BARBARIAN:
+ divisor = 4;
+ break;
+ case CLASS_KNIGHT:
+ case CLASS_NINJA:
+ divisor = 5;
+ break;
+ case CLASS_PALADIN:
+ case CLASS_ARCHER:
+ case CLASS_ROBBER:
+ case CLASS_RANGER:
+ divisor = 6;
+ break;
+ case CLASS_CLERIC:
+ case CLASS_DRUID:
+ divisor = 7;
+ break;
+ case CLASS_SORCERER:
+ divisor = 8;
+ break;
+ default:
+ break;
+ }
+
+ int numberOfAttacks = c.getCurrentLevel() / divisor + 1;
+ damage = 0;
+
+ while (numberOfAttacks-- > 0) {
+ if (hitMonster(c, RT_SINGLE))
+ damage += getMonsterDamage(c);
+ }
+
+ for (int itemIndex = 0; itemIndex < INV_ITEMS_TOTAL; ++itemIndex) {
+ XeenItem &weapon = c._weapons[itemIndex];
+ if (weapon._frame != 0) {
+ switch (weapon._bonusFlags & ITEMFLAG_BONUS_MASK) {
+ case 1:
+ if (monsterData._monsterType == MONSTER_DRAGON)
+ damage *= 3;
+ break;
+ case 2:
+ if (monsterData._monsterType == MONSTER_UNDEAD)
+ damage *= 3;
+ break;
+ case 3:
+ if (monsterData._monsterType == MONSTER_GOLEM)
+ damage *= 3;
+ break;
+ case 4:
+ if (monsterData._monsterType == MONSTER_INSECT)
+ damage *= 3;
+ break;
+ case 5:
+ if (monsterData._monsterType == MONSTER_0)
+ damage *= 3;
+ break;
+ case 6:
+ if (monsterData._monsterType == MONSTER_ANIMAL)
+ damage *= 3;
+ break;
+ }
+ }
+ }
+
+ attack2(damage, rangeType);
+ setSpeedTable();
+ }
+}
+
+void Combat::attack2(int damage, RangeType rangeType) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ MazeMonster &monster = map._mobData._monsters[_monster2Attack];
+ MonsterStruct &monsterData = *monster._monsterData;
+ bool monsterDied = false;
+
+ if (!isDarkCc && damage && rangeType && monster._spriteId == 89)
+ damage = 0;
+
+ if (!damage) {
+ sound.playSample(&_missVoc, 1);
+ sound.playFX(6);
+ } else {
+ if (!isDarkCc && monster._spriteId == 89)
+ damage += 100;
+ if (monster._damageType == DT_SLEEP || monster._damageType == DT_DRAGONSLEEP)
+ monster._damageType = DT_PHYSICAL;
+
+ if ((!rangeType || !_damageType) && _attackWeaponId != 34) {
+ if (monsterData._phsyicalResistence != 0) {
+ if (monsterData._phsyicalResistence == 100) {
+ damage = 0;
+ } else {
+ // This doesn't seem to have any effect?
+ damage = (damage * 100) / 100;
+ }
+ }
+ }
+
+ if (damage) {
+ _charsArray1[_monsterIndex] = 3;
+ _monPow[_monsterIndex] = _damageType == DT_PHYSICAL && (rangeType == 3 || rangeType == 0);
+ monster._frame = 11;
+ monster._fieldA = 5;
+ }
+
+ int monsterResist = getMonsterResistence(rangeType);
+ damage += monsterResist;
+ if (monsterResist > 0) {
+ _elemPow[_monsterIndex] = _attackWeapon->getElementalCategory();
+ _elemScale[_monsterIndex] = getDamageScale(monsterResist);
+ } else if (rangeType != 3) {
+ _elemPow[_monsterIndex] = ELEM_FIRE;
+ }
+
+ if (rangeType != 0 && rangeType != 3) {
+ monster._effect2 = DAMAGE_TYPE_EFFECTS[_damageType];
+ monster._effect1 = 0;
+ }
+
+ if (rangeType && monsterSavingThrow(monster._spriteId)) {
+ switch (_damageType) {
+ case DT_FINGEROFDEATH:
+ case DT_MASS_DISTORTION:
+ damage = 5;
+ break;
+ case DT_SLEEP:
+ case DT_HOLYWORD:
+ case DT_UNDEAD:
+ case DT_BEASTMASTER:
+ case DT_DRAGONSLEEP:
+ case DT_GOLEMSTOPPER:
+ case DT_HYPNOTIZE:
+ case DT_INSECT_SPRAY:
+ case DT_MAGIC_ARROW:
+ break;
+ default:
+ damage /= 2;
+ break;
+ }
+ }
+
+ if (damage < 1) {
+ sound.playSample(&_missVoc, 1);
+ sound.playFX(6);
+ } else {
+ _monsterScale[_monsterIndex] = getDamageScale(damage);
+ intf.draw3d(true);
+
+ sound.playSample(nullptr, 0);
+ File powVoc(Common::String::format("pow%d.voc",
+ POW_WEAPON_VOCS[_attackWeaponId]));
+ sound.playFX(60 + POW_WEAPON_VOCS[_attackWeaponId]);
+ sound.playSample(&powVoc, 1);
+
+ if (monster._hp > damage) {
+ monster._hp -= damage;
+ } else {
+ monster._hp = 0;
+ monsterDied = true;
+ }
+ }
+ }
+
+ intf.draw3d(true);
+
+ if (monsterDied) {
+ if (!isDarkCc) {
+ if (_monster2Attack == 20 && party._mazeId == 41)
+ party._gameFlags[11] = true;
+ if (_monster2Attack == 8 && party._mazeId == 78) {
+ party._gameFlags[60] = true;
+ party._quests[23] = false;
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx)
+ party._activeParty[idx].setAward(42, true);
+
+ if (_monster2Attack == 27 && party._mazeId == 29)
+ party._gameFlags[104] = true;
+ }
+ }
+
+ giveExperience(monsterData._experience);
+
+ if (party._mazeId != 85) {
+ party._treasure._gold = monsterData._gold;
+ party._treasure._gems = monsterData._gems;
+
+ if (!isDarkCc && monster._spriteId == 89) {
+ party._treasure._weapons[0]._id = 90;
+ party._treasure._weapons[0]._bonusFlags = 0;
+ party._treasure._weapons[0]._material = 0;
+ party._treasure._hasItems = true;
+ party._questItems[8]++;
+ }
+
+ int itemDrop = monsterData._itemDrop;
+ if (itemDrop) {
+ if (MONSTER_ITEM_RANGES[itemDrop] >= _vm->getRandomNumber(1, 100)) {
+ Character tempChar;
+ int category = tempChar.makeItem(itemDrop, 0, 0);
+
+ switch (category) {
+ case CATEGORY_WEAPON:
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ if (party._treasure._weapons[idx]._id == 0) {
+ party._treasure._weapons[idx] = tempChar._weapons[0];
+ party._treasure._hasItems = 1;
+ break;
+ }
+ }
+ break;
+ case CATEGORY_ARMOR:
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ if (party._treasure._armor[idx]._id == 0) {
+ party._treasure._armor[idx] = tempChar._armor[0];
+ party._treasure._hasItems = 1;
+ break;
+ }
+ }
+ break;
+ case CATEGORY_ACCESSORY:
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ if (party._treasure._accessories[idx]._id == 0) {
+ party._treasure._accessories[idx] = tempChar._accessories[0];
+ party._treasure._hasItems = 1;
+ break;
+ }
+ }
+ break;
+ case CATEGORY_MISC:
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ if (party._treasure._accessories[idx]._id == 0) {
+ party._treasure._accessories[idx] = tempChar._accessories[0];
+ party._treasure._hasItems = 1;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ monster._position = Common::Point(0x80, 0x80);
+ _charsArray1[_monsterIndex] = 0;
+ _monster2Attack = -1;
+ intf.draw3d(true);
+
+ if (_attackMonsters[0] != -1) {
+ _monster2Attack = _attackMonsters[0];
+ _monsterIndex = 0;
+ }
+ }
+}
+
+void Combat::block() {
+ _charsBlocked[_whosTurn] = true;
+}
+
+void Combat::quickFight() {
+ Spells &spells = *_vm->_spells;
+ Character *c = _combatParty[_whosTurn];
+
+ switch (c->_quickOption) {
+ case QUICK_ATTACK:
+ attack(*c, RT_SINGLE);
+ break;
+ case QUICK_SPELL:
+ if (c->_currentSpell != -1) {
+ spells.castSpell(c, (MagicSpell)SPELLS_ALLOWED[c->getClassCategory()][c->_currentSpell]);
+ }
+ break;
+ case QUICK_BLOCK:
+ block();
+ break;
+ case QUICK_RUN:
+ run();
+ break;
+ default:
+ break;
+ }
+}
+
+void Combat::run() {
+ Map &map = *_vm->_map;
+ SoundManager &sound = *_vm->_sound;
+
+ if (_vm->getRandomNumber(1, 100) < map.mazeData()._difficulties._chance2Run) {
+ // Remove the character from the combat party
+ _combatParty.remove_at(_whosTurn);
+ setSpeedTable();
+ --_whosSpeed;
+ _whosTurn = -1;
+ _partyRan = true;
+ sound.playFX(51);
+ }
+}
+
+bool Combat::hitMonster(Character &c, RangeType rangeType) {
+ Map &map = *_vm->_map;
+ getWeaponDamage(c, rangeType);
+ int chance = c.statBonus(c.getStat(ACCURACY)) + _hitChanceBonus;
+ int divisor = 0;
+
+ switch (c._class) {
+ case CLASS_KNIGHT:
+ case CLASS_BARBARIAN:
+ divisor = 1;
+ break;
+ case CLASS_PALADIN :
+ case CLASS_ARCHER:
+ case CLASS_ROBBER:
+ case CLASS_NINJA:
+ case CLASS_RANGER:
+ divisor = 2;
+ break;
+ case CLASS_CLERIC:
+ case CLASS_DRUID:
+ divisor = 3;
+ break;
+ case CLASS_SORCERER:
+ divisor = 4;
+ break;
+ default:
+ break;
+ }
+
+ chance += c.getCurrentLevel() / divisor;
+ chance -= c._conditions[CURSED];
+
+ // Add on a random amount
+ int v;
+ do {
+ v = _vm->getRandomNumber(1, 20);
+ chance += v;
+ } while (v == 20);
+
+ assert(_monster2Attack != -1);
+ MazeMonster &monster = map._mobData._monsters[_monster2Attack];
+ MonsterStruct &monsterData = *monster._monsterData;
+
+ if (monster._damageType != DT_PHYSICAL)
+ chance += 20;
+
+ return chance >= (monsterData._accuracy + 10);
+}
+
+void Combat::getWeaponDamage(Character &c, RangeType rangeType) {
+ Party &party = *_vm->_party;
+ _attackWeapon = nullptr;
+ _weaponDie = _weaponDice = 0;
+ _weaponDamage = 0;
+ _hitChanceBonus = 0;
+
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ bool flag;
+ if (rangeType) {
+ flag = c._weapons[idx]._frame == 4;
+ } else {
+ flag = c._weapons[idx]._frame == 1 || c._weapons[idx]._frame == 13;
+ }
+
+ if (flag) {
+ if (!(c._weapons[idx]._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED))) {
+ _attackWeapon = &c._weapons[idx];
+
+ if (c._weapons[idx]._material >= 37 && c._weapons[idx]._material < 59) {
+ _hitChanceBonus = METAL_DAMAGE_PERCENT[c._weapons[idx]._material - 37];
+ _weaponDamage = METAL_DAMAGE[c._weapons[idx]._material - 37];
+ }
+ }
+
+ _hitChanceBonus += party._heroism;
+ _attackWeaponId = c._weapons[idx]._id;
+ _weaponDice = WEAPON_DAMAGE_BASE[_attackWeaponId];
+ _weaponDie = WEAPON_DAMAGE_MULTIPLIER[_attackWeaponId];
+
+ for (int diceIdx = 0; diceIdx < _weaponDice; ++diceIdx)
+ _weaponDamage += _vm->getRandomNumber(1, _weaponDie);
+ }
+ }
+
+ if (_weaponDamage < 1)
+ _weaponDamage = 0;
+ if (!party._difficulty) {
+ _hitChanceBonus += 5;
+ _weaponDamage *= 3;
+ }
+}
+
+int Combat::getMonsterDamage(Character &c) {
+ return MAX(c.statBonus(c.getStat(MIGHT)) + _weaponDamage, 1);
+}
+
+int Combat::getDamageScale(int v) {
+ if (v < 10)
+ return 5;
+ else if (v < 100)
+ return 0;
+ else
+ return 0x8000;
+}
+
+int Combat::getMonsterResistence(RangeType rangeType) {
+ Map &map = *_vm->_map;
+ assert(_monster2Attack != -1);
+ MazeMonster &monster = map._mobData._monsters[_monster2Attack];
+ MonsterStruct &monsterData = *monster._monsterData;
+ int resistence = 0, damage = 0;
+
+ if (rangeType != RT_SINGLE && rangeType != RT_3) {
+ switch (_damageType) {
+ case DT_PHYSICAL:
+ resistence = monsterData._phsyicalResistence;
+ break;
+ case DT_MAGICAL:
+ resistence = monsterData._magicResistence;
+ break;
+ case DT_FIRE:
+ resistence = monsterData._fireResistence;
+ break;
+ case DT_ELECTRICAL:
+ resistence = monsterData._electricityResistence;
+ break;
+ case DT_COLD:
+ resistence = monsterData._coldResistence;
+ break;
+ case DT_POISON:
+ resistence = monsterData._poisonResistence;
+ break;
+ case DT_ENERGY:
+ resistence = monsterData._energyResistence;
+ break;
+ default:
+ break;
+ }
+ } else {
+ int material = !_attackWeapon ? 0 : _attackWeapon->_material;
+ damage = ELEMENTAL_DAMAGE[material];
+
+ if (material != 0) {
+ if (material < 9)
+ resistence = monsterData._fireResistence;
+ else if (material < 16)
+ resistence = monsterData._electricityResistence;
+ else if (material < 21)
+ resistence = monsterData._coldResistence;
+ else if (material < 26)
+ resistence = monsterData._poisonResistence;
+ else if (material < 34)
+ resistence = monsterData._energyResistence;
+ else
+ resistence = monsterData._magicResistence;
+ }
+ }
+
+ if (resistence != 0) {
+ if (resistence == 100)
+ return 0;
+ else
+ return ((100 - resistence) * damage) / 100;
+ }
+
+ return damage;
+}
+
+void Combat::giveExperience(int experience) {
+ Party &party = *_vm->_party;
+ bool inCombat = _vm->_mode == MODE_COMBAT;
+ int count = 0;
+
+ // Two loops: first to figure out how many active characters there are,
+ // and the second to distribute the experience between them
+ for (int loopNum = 0; loopNum < 2; ++loopNum) {
+ for (uint charIndex = 0; charIndex < (inCombat ? _combatParty.size() :
+ party._activeParty.size()); ++charIndex) {
+ Character &c = inCombat ? *_combatParty[charIndex] : party._activeParty[charIndex];
+ Condition condition = c.worstCondition();
+
+ if (condition != DEAD && condition != STONED && condition != ERADICATED) {
+ if (loopNum == 0) {
+ ++count;
+ } else {
+ int exp = experience / count;
+ if (c._level._permanent < 15)
+ exp /= 2;
+ c._experience += exp;
+ }
+ }
+ }
+ }
+}
+
+void Combat::multiAttack(int powNum) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ if (_damageType == DT_POISON_VOLLEY) {
+ _damageType = DT_POISON;
+ _shootType = ST_1;
+ Common::fill(&_shooting[0], &_shooting[6], 1);
+ } else if (powNum == 11) {
+ _shootType = ST_1;
+ bool flag = false;
+
+ if (_damageType == DT_PHYSICAL) {
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ Character &c = party._activeParty[idx];
+ if (c.hasMissileWeapon()) {
+ _shooting[idx] = 1;
+ flag = true;
+ }
+ }
+ } else {
+ _shooting[0] = 1;
+ flag = true;
+ }
+
+ if (!flag) {
+ sound.playFX(21);
+ return;
+ }
+ } else {
+ _shooting[0] = 1;
+ _shootType = ST_0;
+ }
+
+ intf._charsShooting = true;
+ _powSprites.load(Common::String::format("pow%d.icn", powNum));
+ int monsterIndex = _monsterIndex;
+ int monster2Attack = _monster2Attack;
+ bool attackedFlag = false;
+
+ Common::Array<int> attackMonsters;
+ for (int idx = 0; idx < 3; ++idx) {
+ if (_attackMonsters[idx] != -1)
+ attackMonsters.push_back(_attackMonsters[idx]);
+ }
+
+ _monsterIndex = -1;
+ if (_monster2Attack != -1) {
+ _monsterIndex--;
+ if (attackMonsters.empty())
+ attackMonsters.resize(1);
+ attackMonsters[0] = monster2Attack;
+ }
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (_shooting[idx]) {
+ if (map._isOutdoors) {
+ intf._outdoorList._attackImgs1[idx]._scale = 0;
+ intf._outdoorList._attackImgs2[idx]._scale = 4;
+ intf._outdoorList._attackImgs3[idx]._scale = 8;
+ intf._outdoorList._attackImgs4[idx]._scale = 12;
+ intf._outdoorList._attackImgs1[idx]._sprites = &_powSprites;
+ intf._outdoorList._attackImgs2[idx]._sprites = nullptr;
+ intf._outdoorList._attackImgs3[idx]._sprites = nullptr;
+ intf._outdoorList._attackImgs4[idx]._sprites = nullptr;
+ } else {
+ intf._indoorList._attackImgs1[idx]._scale = 0;
+ intf._indoorList._attackImgs2[idx]._scale = 4;
+ intf._indoorList._attackImgs3[idx]._scale = 8;
+ intf._indoorList._attackImgs4[idx]._scale = 12;
+ intf._indoorList._attackImgs1[idx]._sprites = &_powSprites;
+ intf._indoorList._attackImgs2[idx]._sprites = nullptr;
+ intf._indoorList._attackImgs3[idx]._sprites = nullptr;
+ intf._indoorList._attackImgs4[idx]._sprites = nullptr;
+ }
+ }
+ }
+
+ intf.draw3d(true);
+
+ ++_monsterIndex;
+ for (uint monIdx = 0; monIdx < attackMonsters.size(); ++monIdx, ++_monsterIndex) {
+ Common::fill(&_missedShot[0], &_missedShot[8], false);
+ _monster2Attack = attackMonsters[monIdx];
+ attack(*_oldCharacter, RT_GROUP);
+ attackedFlag = true;
+
+ if (_rangeType == RT_SINGLE)
+ // Only single shot, so exit now that the attack is done
+ goto finished;
+ }
+
+ if (attackedFlag && _rangeType == RT_GROUP)
+ // Finished group attack, so exit
+ goto finished;
+
+ if (map._isOutdoors) {
+ map.getCell(7);
+ switch (map._currentWall) {
+ case 1:
+ case 3:
+ case 6:
+ case 7:
+ case 9:
+ case 10:
+ case 12:
+ sound.playFX(46);
+ goto finished;
+ default:
+ break;
+ }
+ } else {
+ int cell = map.getCell(2);
+ if (cell < map.mazeData()._difficulties._wallNoPass) {
+ sound.playFX(46);
+ goto finished;
+ }
+ }
+ if (!intf._isAttacking)
+ goto finished;
+
+ intf.draw3d(true);
+
+ // Start handling second teir of monsters in the back
+ attackMonsters.clear();
+ for (uint idx = 3; idx < 6; ++idx) {
+ if (_attackMonsters[idx] != -1)
+ attackMonsters.push_back(_attackMonsters[idx]);
+ }
+
+ ++_monsterIndex;
+ for (uint monIdx = 0; monIdx < attackMonsters.size(); ++monIdx, ++_monsterIndex) {
+ Common::fill(&_missedShot[0], &_missedShot[8], false);
+ _monster2Attack = attackMonsters[monIdx];
+ attack(*_oldCharacter, RT_GROUP);
+ attackedFlag = true;
+
+ if (_rangeType == RT_SINGLE)
+ // Only single shot, so exit now that the attack is done
+ goto finished;
+ }
+
+ if (attackedFlag && _rangeType == RT_GROUP)
+ // Finished group attack, so exit
+ goto finished;
+
+ if (map._isOutdoors) {
+ map.getCell(14);
+ switch (map._currentWall) {
+ case 1:
+ case 3:
+ case 6:
+ case 7:
+ case 9:
+ case 10:
+ case 12:
+ sound.playFX(46);
+ goto finished;
+ default:
+ break;
+ }
+ } else {
+ int cell = map.getCell(7);
+ if (cell < map.mazeData()._difficulties._wallNoPass) {
+ sound.playFX(46);
+ goto finished;
+ }
+ }
+ if (!intf._isAttacking)
+ goto finished;
+
+ intf.draw3d(true);
+
+ // Start handling third teir of monsters in the back
+ attackMonsters.clear();
+ for (uint idx = 6; idx < 9; ++idx) {
+ if (_attackMonsters[idx] != -1)
+ attackMonsters.push_back(_attackMonsters[idx]);
+ }
+
+ ++_monsterIndex;
+ for (uint monIdx = 0; monIdx < attackMonsters.size(); ++monIdx, ++_monsterIndex) {
+ Common::fill(&_missedShot[0], &_missedShot[8], false);
+ _monster2Attack = attackMonsters[monIdx];
+ attack(*_oldCharacter, RT_GROUP);
+ attackedFlag = true;
+
+ if (_rangeType == RT_SINGLE)
+ // Only single shot, so exit now that the attack is done
+ goto finished;
+ }
+
+ if (attackedFlag && _rangeType == RT_GROUP)
+ // Finished group attack, so exit
+ goto finished;
+
+ if (map._isOutdoors) {
+ map.getCell(27);
+ switch (map._currentWall) {
+ case 1:
+ case 3:
+ case 6:
+ case 7:
+ case 9:
+ case 10:
+ case 12:
+ sound.playFX(46);
+ goto finished;
+ default:
+ break;
+ }
+ } else {
+ int cell = map.getCell(14);
+ if (cell < map.mazeData()._difficulties._wallNoPass) {
+ sound.playFX(46);
+ goto finished;
+ }
+ }
+ if (!intf._isAttacking)
+ goto finished;
+
+ intf.draw3d(true);
+
+ // Fourth tier
+ attackMonsters.clear();
+ for (uint idx = 9; idx < 12; ++idx) {
+ if (_attackMonsters[idx] != -1)
+ attackMonsters.push_back(_attackMonsters[idx]);
+ }
+
+ ++_monsterIndex;
+ for (uint monIdx = 0; monIdx < attackMonsters.size(); ++monIdx, ++_monsterIndex) {
+ Common::fill(&_missedShot[0], &_missedShot[8], false);
+ _monster2Attack = attackMonsters[monIdx];
+ attack(*_oldCharacter, RT_GROUP);
+ attackedFlag = true;
+
+ if (_rangeType == RT_SINGLE)
+ // Only single shot, so exit now that the attack is done
+ goto finished;
+ }
+
+ if (!(attackedFlag && _rangeType == RT_GROUP))
+ goto done;
+
+finished:
+ endAttack();
+done:
+ Common::fill(&_shooting[0], &_shooting[MAX_PARTY_COUNT], 0);
+ _monster2Attack = monster2Attack;
+ _monsterIndex = monsterIndex;
+ party.giveTreasure();
+}
+
+void Combat::shootRangedWeapon() {
+ _rangeType = RT_ALL;
+ _damageType = DT_PHYSICAL;
+ multiAttack(11);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/combat.h b/engines/xeen/combat.h
new file mode 100644
index 0000000000..8a7c676018
--- /dev/null
+++ b/engines/xeen/combat.h
@@ -0,0 +1,221 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_COMBAT_H
+#define XEEN_COMBAT_H
+
+#include "common/scummsys.h"
+#include "common/rect.h"
+#include "xeen/files.h"
+#include "xeen/sprites.h"
+
+namespace Xeen {
+
+#define MAX_NUM_MONSTERS 107
+#define PARTY_AND_MONSTERS 12
+
+enum DamageType {
+ DT_PHYSICAL = 0, DT_MAGICAL = 1, DT_FIRE = 2, DT_ELECTRICAL = 3,
+ DT_COLD = 4, DT_POISON = 5, DT_ENERGY = 6, DT_SLEEP = 7,
+ DT_FINGEROFDEATH = 8, DT_HOLYWORD = 9, DT_MASS_DISTORTION = 10,
+ DT_UNDEAD = 11, DT_BEASTMASTER = 12, DT_DRAGONSLEEP = 13,
+ DT_GOLEMSTOPPER = 14, DT_HYPNOTIZE = 15, DT_INSECT_SPRAY = 16,
+ DT_POISON_VOLLEY = 17, DT_MAGIC_ARROW = 18
+};
+
+enum SpecialAttack {
+ SA_NONE = 0, SA_MAGIC = 1, SA_FIRE = 2, SA_ELEC = 3, SA_COLD = 4,
+ SA_POISON = 5, SA_ENERGY = 6, SA_DISEASE = 7, SA_INSANE = 8,
+ SA_SLEEP = 9, SA_CURSEITEM = 10, SA_INLOVE = 11, SA_DRAINSP = 12,
+ SA_CURSE = 13, SA_PARALYZE = 14, SA_UNCONSCIOUS = 15,
+ SA_CONFUSE = 16, SA_BREAKWEAPON = 17, SA_WEAKEN = 18,
+ SA_ERADICATE = 19, SA_AGING = 20, SA_DEATH = 21, SA_STONE = 22
+};
+
+enum ElementalCategory {
+ ELEM_FIRE = 0, ELEM_ELECTRICITY = 1, ELEM_COLD = 2,
+ ELEM_ACID_POISON = 3, ELEM_ENERGY = 4, ELEM_MAGIC = 5
+};
+
+enum RangeType {
+ RT_SINGLE = 0, RT_GROUP = 1, RT_ALL = 2, RT_3 = 3
+};
+
+enum ShootType {
+ ST_0 = 0, ST_1 = 1
+};
+
+enum CombatMode {
+ COMBATMODE_0 = 0, COMBATMODE_1 = 1, COMBATMODE_2 = 2
+};
+
+class XeenEngine;
+class Character;
+class XeenItem;
+
+class Combat {
+private:
+ XeenEngine *_vm;
+
+ void attack2(int damage, RangeType rangeType);
+
+ bool hitMonster(Character &c, RangeType rangeType);
+
+ void getWeaponDamage(Character &c, RangeType rangeType);
+
+ int getMonsterDamage(Character &c);
+
+ int getDamageScale(int v);
+
+ int getMonsterResistence(RangeType rangeType);
+
+ /**
+ * Distribute experience between active party members
+ */
+ void giveExperience(int experience);
+public:
+ Common::Array<Character *> _combatParty;
+ Common::Array<bool> _charsBlocked;
+ Common::Array<int> _charsGone;
+ SpriteResource _powSprites;
+ int _attackMonsters[26];
+ int _monster2Attack;
+ int _charsArray1[PARTY_AND_MONSTERS];
+ bool _monPow[PARTY_AND_MONSTERS];
+ int _monsterScale[PARTY_AND_MONSTERS];
+ ElementalCategory _elemPow[PARTY_AND_MONSTERS];
+ int _elemScale[PARTY_AND_MONSTERS];
+ int _missedShot[8];
+ Common::Array<int> _speedTable;
+ int _shooting[8];
+ int _globalCombat;
+ int _whosTurn;
+ bool _itemFlag;
+ int _monsterMap[32][32];
+ bool _monsterMoved[MAX_NUM_MONSTERS];
+ bool _rangeAttacking[MAX_NUM_MONSTERS];
+ int _gmonHit[36];
+ bool _monstersAttacking;
+ CombatMode _combatMode;
+ int _monsterIndex;
+ bool _partyRan;
+ int _whosSpeed;
+ DamageType _damageType;
+ Character *_oldCharacter;
+ int _monsterDamage;
+ int _weaponDamage;
+ int _weaponDie, _weaponDice;
+ XeenItem *_attackWeapon;
+ int _attackWeaponId;
+ File _missVoc, _pow1Voc;
+ int _hitChanceBonus;
+ bool _dangerPresent;
+ bool _moveMonsters;
+ RangeType _rangeType;
+ ShootType _shootType;
+public:
+ Combat(XeenEngine *vm);
+
+ void clear();
+
+ void giveCharDamage(int damage, DamageType attackType, int charIndex);
+
+ /**
+ * Do damage to a specific character
+ */
+ void doCharDamage(Character &c, int charNum, int monsterDataIndex);
+
+ void moveMonsters();
+
+ /**
+ * Setup the combat party with a copy of the currently active party
+ */
+ void setupCombatParty();
+
+ void setSpeedTable();
+
+ /**
+ * Returns true if all participants in the combat are disabled
+ */
+ bool allHaveGone() const;
+
+ /**
+ * Returns true if all the characters of the party are disabled
+ */
+ bool charsCantAct() const;
+
+ /**
+ * Return a description of the monsters being faced
+ */
+ Common::String getMonsterDescriptions();
+
+ void attack(Character &c, RangeType rangeType);
+
+ /**
+ * Flag the currently active character as blocking/defending
+ */
+ void block();
+
+ /**
+ * Perform whatever the current combat character's quick action is
+ */
+ void quickFight();
+
+ /**
+ * Current selected character is trying to run away
+ */
+ void run();
+
+ void monstersAttack();
+
+ void setupMonsterAttack(int monsterDataIndex, const Common::Point &pt);
+
+ /**
+ * Determines whether a given monster can move
+ */
+ bool monsterCanMove(const Common::Point &pt, int wallShift,
+ int v1, int v2, int monsterId);
+
+ /**
+ * Moves a monster by a given delta amount if it's a valid move
+ */
+ void moveMonster(int monsterId, const Common::Point &moveDelta);
+
+ void doMonsterTurn(int monsterId);
+
+ void endAttack();
+
+ void monsterOvercome();
+
+ int stopAttack(const Common::Point &diffPt);
+
+ void multiAttack(int powNum);
+
+ /**
+ * Fires off a ranged attack at all oncoming monsters
+ */
+ void shootRangedWeapon();
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_COMBAT_H */
diff --git a/engines/xeen/configure.engine b/engines/xeen/configure.engine
new file mode 100644
index 0000000000..489254f269
--- /dev/null
+++ b/engines/xeen/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 xeen "World of Xeen" no
diff --git a/engines/xeen/debugger.cpp b/engines/xeen/debugger.cpp
new file mode 100644
index 0000000000..d9b4990e97
--- /dev/null
+++ b/engines/xeen/debugger.cpp
@@ -0,0 +1,85 @@
+/* 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.
+ *
+ */
+
+#include "common/file.h"
+#include "xeen/xeen.h"
+#include "xeen/debugger.h"
+
+namespace Xeen {
+
+static int strToInt(const char *s) {
+ if (!*s)
+ // No string at all
+ return 0;
+ else if (toupper(s[strlen(s) - 1]) != 'H')
+ // Standard decimal string
+ return atoi(s);
+
+ // Hexadecimal string
+ uint tmp = 0;
+ int read = sscanf(s, "%xh", &tmp);
+ if (read < 1)
+ error("strToInt failed on string \"%s\"", s);
+ return (int)tmp;
+}
+
+/*------------------------------------------------------------------------*/
+
+Debugger::Debugger(XeenEngine *vm) : GUI::Debugger(), _vm(vm) {
+ registerCmd("continue", WRAP_METHOD(Debugger, cmdExit));
+ registerCmd("spell", WRAP_METHOD(Debugger, cmdSpell));
+
+ _spellId = -1;
+}
+
+void Debugger::update() {
+ Party &party = *_vm->_party;
+ Spells &spells = *_vm->_spells;
+
+ if (_spellId != -1) {
+ // Cast any specified spell
+ MagicSpell spellId = (MagicSpell)_spellId;
+ _spellId = -1;
+ Character *c = &party._activeParty[0];
+ c->_currentSp = 99;
+ spells.castSpell(c, spellId);
+ }
+
+ onFrame();
+}
+
+bool Debugger::cmdSpell(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: spell <spell-id>");
+ return true;
+ } else {
+ int spellId = strToInt(argv[1]);
+ if (spellId >= MS_AcidSpray && spellId <= MS_WizardEye) {
+ _spellId = spellId;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/debugger.h b/engines/xeen/debugger.h
new file mode 100644
index 0000000000..e7f8ef6b7d
--- /dev/null
+++ b/engines/xeen/debugger.h
@@ -0,0 +1,47 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DEBUGGER_H
+#define XEEN_DEBUGGER_H
+
+#include "common/scummsys.h"
+#include "gui/debugger.h"
+
+namespace Xeen {
+
+class XeenEngine;
+
+class Debugger : public GUI::Debugger {
+private:
+ XeenEngine *_vm;
+ int _spellId;
+
+ bool cmdSpell(int argc, const char **argv);
+public:
+ Debugger(XeenEngine *vm);
+
+ void update();
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DEBUGGER_H */
diff --git a/engines/xeen/detection.cpp b/engines/xeen/detection.cpp
new file mode 100644
index 0000000000..e5b137c394
--- /dev/null
+++ b/engines/xeen/detection.cpp
@@ -0,0 +1,196 @@
+/* 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.
+ *
+ */
+
+#include "xeen/xeen.h"
+#include "xeen/worldofxeen/worldofxeen.h"
+
+#include "base/plugins.h"
+#include "common/savefile.h"
+#include "engines/advancedDetector.h"
+#include "common/system.h"
+
+#define MAX_SAVES 99
+
+namespace Xeen {
+
+struct XeenGameDescription {
+ ADGameDescription desc;
+
+ int gameID;
+ uint32 features;
+};
+
+uint32 XeenEngine::getGameID() const {
+ return _gameDescription->gameID;
+}
+
+uint32 XeenEngine::getGameFeatures() const {
+ return _gameDescription->features;
+}
+
+uint32 XeenEngine::getFeatures() const {
+ return _gameDescription->desc.flags;
+}
+
+Common::Language XeenEngine::getLanguage() const {
+ return _gameDescription->desc.language;
+}
+
+Common::Platform XeenEngine::getPlatform() const {
+ return _gameDescription->desc.platform;
+}
+
+} // End of namespace Xeen
+
+static const PlainGameDescriptor XeenGames[] = {
+ { "xeen", "Xeen" },
+ { "clouds", "Clouds of Xeen" },
+ { "darkside", "Dark Side of Xeen" },
+ { "worldofxeen", "World of Xeen" },
+ {0, 0}
+};
+
+#include "xeen/detection_tables.h"
+
+class XeenMetaEngine : public AdvancedMetaEngine {
+public:
+ XeenMetaEngine() : AdvancedMetaEngine(Xeen::gameDescriptions, sizeof(Xeen::XeenGameDescription), XeenGames) {
+ _maxScanDepth = 3;
+ }
+
+ virtual const char *getName() const {
+ return "Xeen Engine";
+ }
+
+ virtual const char *getOriginalCopyright() const {
+ return "Xeen Engine (c) ???";
+ }
+
+ virtual bool hasFeature(MetaEngineFeature f) const;
+ virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const;
+ virtual SaveStateList listSaves(const char *target) const;
+ virtual int getMaximumSaveSlot() const;
+ virtual void removeSaveState(const char *target, int slot) const;
+ SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const;
+};
+
+bool XeenMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return
+ (f == kSupportsListSaves) ||
+ (f == kSupportsLoadingDuringStartup) ||
+ (f == kSupportsDeleteSave) ||
+ (f == kSavesSupportMetaInfo) ||
+ (f == kSavesSupportThumbnail);
+}
+
+bool Xeen::XeenEngine::hasFeature(EngineFeature f) const {
+ return
+ (f == kSupportsRTL) ||
+ (f == kSupportsLoadingDuringRuntime) ||
+ (f == kSupportsSavingDuringRuntime);
+}
+
+bool XeenMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+ const Xeen::XeenGameDescription *gd = (const Xeen::XeenGameDescription *)desc;
+
+ switch (gd->gameID) {
+ case Xeen::GType_Clouds:
+ case Xeen::GType_DarkSide:
+ case Xeen::GType_WorldOfXeen:
+ *engine = new Xeen::WorldOfXeenEngine(syst, gd);
+ break;
+ default:
+ break;
+ }
+
+ return gd != 0;
+}
+
+SaveStateList XeenMetaEngine::listSaves(const char *target) const {
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+ Common::StringArray filenames;
+ Common::String saveDesc;
+ Common::String pattern = Common::String::format("%s.0??", target);
+ Xeen::XeenSavegameHeader header;
+
+ filenames = saveFileMan->listSavefiles(pattern);
+ sort(filenames.begin(), filenames.end()); // Sort to get the files in numerical order
+
+ SaveStateList saveList;
+ for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
+ const char *ext = strrchr(file->c_str(), '.');
+ int slot = ext ? atoi(ext + 1) : -1;
+
+ if (slot >= 0 && slot < MAX_SAVES) {
+ Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file);
+
+ if (in) {
+ Xeen::XeenEngine::readSavegameHeader(in, header);
+ saveList.push_back(SaveStateDescriptor(slot, header._saveName));
+
+ header._thumbnail->free();
+ delete header._thumbnail;
+ delete in;
+ }
+ }
+ }
+
+ return saveList;
+}
+
+int XeenMetaEngine::getMaximumSaveSlot() const {
+ return MAX_SAVES;
+}
+
+void XeenMetaEngine::removeSaveState(const char *target, int slot) const {
+ Common::String filename = Common::String::format("%s.%03d", target, slot);
+ g_system->getSavefileManager()->removeSavefile(filename);
+}
+
+SaveStateDescriptor XeenMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+ Common::String filename = Common::String::format("%s.%03d", target, slot);
+ Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(filename);
+
+ if (f) {
+ Xeen::XeenSavegameHeader header;
+ Xeen::XeenEngine::readSavegameHeader(f, header);
+ delete f;
+
+ // Create the return descriptor
+ SaveStateDescriptor desc(slot, header._saveName);
+ desc.setThumbnail(header._thumbnail);
+ desc.setSaveDate(header._year, header._month, header._day);
+ desc.setSaveTime(header._hour, header._minute);
+ desc.setPlayTime(header._totalFrames * GAME_FRAME_TIME);
+
+ return desc;
+ }
+
+ return SaveStateDescriptor();
+}
+
+
+#if PLUGIN_ENABLED_DYNAMIC(XEEN)
+ REGISTER_PLUGIN_DYNAMIC(XEEN, PLUGIN_TYPE_ENGINE, XeenMetaEngine);
+#else
+ REGISTER_PLUGIN_STATIC(XEEN, PLUGIN_TYPE_ENGINE, XeenMetaEngine);
+#endif
diff --git a/engines/xeen/detection_tables.h b/engines/xeen/detection_tables.h
new file mode 100644
index 0000000000..03290b96d8
--- /dev/null
+++ b/engines/xeen/detection_tables.h
@@ -0,0 +1,67 @@
+/* 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.
+ *
+ */
+
+namespace Xeen {
+
+static const XeenGameDescription gameDescriptions[] = {
+ {
+ // World of Xeen
+ {
+ "worldofxeen",
+ nullptr,
+ {
+ { "xeen.cc", 0, "0cffbab533d9afe140e69ec93096f43e", 13435646 },
+ { "dark.cc", 0, "df194483ecea6abc0511637d712ced7c", 11217676 },
+ AD_LISTEND
+ },
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO1(GUIO_NONE)
+ },
+ GType_WorldOfXeen,
+ 0
+ },
+
+ {
+ // World of Xeen (German)
+ {
+ "worldofxeen",
+ nullptr,
+ {
+ {"xeen.cc", 0, "f4e4b3ddc43bd102dbe1637f480f1fa1", 13214150},
+ {"dark.cc", 0, "84a86bbbc5f2fe96c0b0325485ed8203", 11173657},
+ AD_LISTEND
+ },
+ Common::DE_DEU,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO1(GUIO_NONE),
+ },
+ GType_WorldOfXeen,
+ 0
+ },
+
+ { AD_TABLE_END_MARKER, 0, 0 }
+};
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs.cpp b/engines/xeen/dialogs.cpp
new file mode 100644
index 0000000000..4ecd1c775c
--- /dev/null
+++ b/engines/xeen/dialogs.cpp
@@ -0,0 +1,260 @@
+/* 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.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "xeen/dialogs.h"
+#include "xeen/events.h"
+#include "xeen/resources.h"
+#include "xeen/screen.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void ButtonContainer::saveButtons() {
+ _savedButtons.push(_buttons);
+ clearButtons();
+}
+
+/*
+ * Clears the current list of defined buttons
+ */
+void ButtonContainer::clearButtons() {
+ _buttons.clear();
+}
+
+void ButtonContainer::restoreButtons() {
+ _buttons = _savedButtons.pop();
+}
+
+void ButtonContainer::addButton(const Common::Rect &bounds, int val,
+ SpriteResource *sprites) {
+ _buttons.push_back(UIButton(bounds, val, sprites, true));
+}
+
+void ButtonContainer::addButton(const Common::Rect &bounds, int val) {
+ _buttons.push_back(UIButton(bounds, val, nullptr, false));
+}
+
+void ButtonContainer::addPartyButtons(XeenEngine *vm) {
+ for (uint idx = 0; idx < MAX_ACTIVE_PARTY; ++idx) {
+ addButton(Common::Rect(CHAR_FACES_X[idx], 150, CHAR_FACES_X[idx] + 32, 182),
+ Common::KEYCODE_F1 + idx);
+ }
+}
+
+bool ButtonContainer::checkEvents(XeenEngine *vm) {
+ EventsManager &events = *vm->_events;
+ _buttonValue = 0;
+
+ if (events._leftButton) {
+ // Check whether any button is selected
+ Common::Point pt = events._mousePos;
+
+ for (uint i = 0; i < _buttons.size(); ++i) {
+ if (_buttons[i]._bounds.contains(pt)) {
+ events.debounceMouse();
+
+ _buttonValue = _buttons[i]._value;
+ return true;
+ }
+ }
+ } else if (events.isKeyPending()) {
+ Common::KeyState keyState;
+ events.getKey(keyState);
+
+ _buttonValue = keyState.keycode;
+ if (_buttonValue == Common::KEYCODE_KP8)
+ _buttonValue = Common::KEYCODE_UP;
+ else if (_buttonValue == Common::KEYCODE_KP2)
+ _buttonValue = Common::KEYCODE_DOWN;
+ else if (_buttonValue == Common::KEYCODE_KP_ENTER)
+ _buttonValue = Common::KEYCODE_RETURN;
+
+ _buttonValue |= (keyState.flags << 8);
+ if (_buttonValue)
+ return true;
+ }
+
+ return false;
+}
+
+void ButtonContainer::doScroll(XeenEngine *vm, bool drawFlag, bool doFade) {
+ Screen &screen = *vm->_screen;
+ EventsManager &events = *vm->_events;
+
+ if (vm->getGameID() != GType_Clouds) {
+ if (doFade) {
+ screen.fadeIn(2);
+ }
+ return;
+ }
+
+ const int SCROLL_L[8] = { 29, 23, 15, 251, 245, 233, 207, 185 };
+ const int SCROLL_R[8] = { 165, 171, 198, 218, 228, 245, 264, 281 };
+
+ saveButtons();
+ clearButtons();
+ screen.saveBackground();
+
+ // Load hand vga files
+ SpriteResource *hand[16];
+ for (int i = 0; i < 16; ++i) {
+ Common::String name = Common::String::format("hand%02d.vga", i);
+ hand[i] = new SpriteResource(name);
+ }
+
+ // Load marb vga files
+ SpriteResource *marb[5];
+ for (int i = 1; i < 5; ++i) {
+ Common::String name = Common::String::format("marb%02d.vga", i);
+ marb[i] = new SpriteResource(name);
+ }
+
+ if (drawFlag) {
+ for (int i = 22; i > 0; --i) {
+ events.updateGameCounter();
+ screen.restoreBackground();
+
+ if (i > 0 && i <= 14) {
+ hand[i - 1]->draw(screen, 0);
+ }
+ else {
+ // TODO: Check '800h'.. horizontal reverse maybe?
+ hand[14]->draw(screen, 0, Common::Point(SCROLL_L[i - 14], 0));
+ marb[15]->draw(screen, 0, Common::Point(SCROLL_R[i - 14], 0));
+ }
+
+ if (i <= 20) {
+ marb[i / 5]->draw(screen, i % 5);
+ }
+
+ while (!vm->shouldQuit() && events.timeElapsed() == 0)
+ events.pollEventsAndWait();
+
+ screen._windows[0].update();
+ if (i == 0 && doFade)
+ screen.fadeIn(2);
+ }
+ } else {
+ for (int i = 0; i < 22 && !events.isKeyMousePressed(); ++i) {
+ events.updateGameCounter();
+ screen.restoreBackground();
+
+ if (i < 14) {
+ hand[i]->draw(screen, 0);
+ } else {
+ // TODO: Check '800h'.. horizontal reverse maybe?
+ hand[14]->draw(screen, 0, Common::Point(SCROLL_L[i - 7], 0));
+ marb[15]->draw(screen, 0, Common::Point(SCROLL_R[i - 7], 0));
+ }
+
+ if (i < 20) {
+ marb[i / 5]->draw(screen, i % 5);
+ }
+
+ while (!vm->shouldQuit() && events.timeElapsed() == 0)
+ events.pollEventsAndWait();
+
+ screen._windows[0].update();
+ if (i == 0 && doFade)
+ screen.fadeIn(2);
+ }
+ }
+
+ if (drawFlag) {
+ hand[0]->draw(screen, 0);
+ marb[0]->draw(screen, 0);
+ }
+ else {
+ screen.restoreBackground();
+ }
+
+ screen._windows[0].update();
+ restoreButtons();
+
+ // Free resources
+ for (int i = 1; i < 5; ++i)
+ delete marb[i];
+ for (int i = 0; i < 16; ++i)
+ delete hand[i];
+}
+
+void ButtonContainer::drawButtons(XSurface *surface) {
+ for (uint btnIndex = 0; btnIndex < _buttons.size(); ++btnIndex) {
+ UIButton &btn = _buttons[btnIndex];
+ if (btn._draw) {
+ btn._sprites->draw(*surface, btnIndex * 2,
+ Common::Point(btn._bounds.left, btn._bounds.top));
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+void SettingsBaseDialog::showContents(SpriteResource &title1, bool waitFlag) {
+ _vm->_events->pollEventsAndWait();
+ checkEvents(_vm);
+}
+
+/*------------------------------------------------------------------------*/
+
+void CreditsScreen::show(XeenEngine *vm) {
+ CreditsScreen *dlg = new CreditsScreen(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void CreditsScreen::execute() {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+
+ // Handle drawing the credits screen
+ doScroll(_vm, true, false);
+ screen._windows[GAME_WINDOW].close();
+
+ screen.loadBackground("marb.raw");
+ screen._windows[0].writeString(CREDITS);
+ doScroll(_vm, false, false);
+
+ events.setCursor(0);
+ screen._windows[0].update();
+ clearButtons();
+
+ // Wait for keypress
+ while (!events.isKeyMousePressed())
+ events.pollEventsAndWait();
+
+ doScroll(_vm, true, false);
+}
+
+/*------------------------------------------------------------------------*/
+
+void PleaseWait::show(XeenEngine *vm) {
+ if (vm->_mode != MODE_0) {
+ Window &w = vm->_screen->_windows[9];
+ w.open();
+ w.writeString(PLEASE_WAIT);
+ w.update();
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs.h b/engines/xeen/dialogs.h
new file mode 100644
index 0000000000..51eafa5f54
--- /dev/null
+++ b/engines/xeen/dialogs.h
@@ -0,0 +1,115 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_H
+#define XEEN_DIALOGS_H
+
+#include "common/array.h"
+#include "common/stack.h"
+#include "common/rect.h"
+#include "xeen/sprites.h"
+#include "xeen/xsurface.h"
+
+namespace Xeen {
+
+class XeenEngine;
+
+class UIButton {
+public:
+ Common::Rect _bounds;
+ SpriteResource *_sprites;
+ int _value;
+ bool _draw;
+
+ UIButton(const Common::Rect &bounds, int value, SpriteResource *sprites, bool draw) :
+ _bounds(bounds), _value(value), _sprites(sprites), _draw(draw) {}
+
+ UIButton() : _value(0), _sprites(nullptr), _draw(false) {}
+};
+
+class ButtonContainer {
+private:
+ Common::Stack< Common::Array<UIButton> > _savedButtons;
+protected:
+ Common::Array<UIButton> _buttons;
+ int _buttonValue;
+
+ /**
+ * Draws the scroll in the background
+ */
+ void doScroll(XeenEngine *vm, bool drawFlag, bool doFade);
+
+ bool checkEvents(XeenEngine *vm);
+public:
+ ButtonContainer() : _buttonValue(0) {}
+
+ /**
+ * Saves the current list of buttons
+ */
+ void saveButtons();
+
+ void clearButtons();
+
+ void restoreButtons();
+
+ void addButton(const Common::Rect &bounds, int val, SpriteResource *sprites);
+
+ void addButton(const Common::Rect &bounds, int val);
+
+ void addPartyButtons(XeenEngine *vm);
+
+ /**
+ * Draws the buttons onto the passed surface
+ */
+ void drawButtons(XSurface *surface);
+};
+
+class SettingsBaseDialog : public ButtonContainer {
+protected:
+ XeenEngine *_vm;
+
+ virtual void showContents(SpriteResource &title1, bool mode);
+public:
+ SettingsBaseDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ virtual ~SettingsBaseDialog() {}
+};
+
+class CreditsScreen: public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ CreditsScreen(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+public:
+ static void show(XeenEngine *vm);
+};
+
+class PleaseWait {
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_H */
diff --git a/engines/xeen/dialogs_automap.cpp b/engines/xeen/dialogs_automap.cpp
new file mode 100644
index 0000000000..d9b7e8d6c7
--- /dev/null
+++ b/engines/xeen/dialogs_automap.cpp
@@ -0,0 +1,428 @@
+/* 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.
+ *
+ */
+
+#include "xeen/dialogs_automap.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+
+void AutoMapDialog::show(XeenEngine *vm) {
+ AutoMapDialog *dlg = new AutoMapDialog(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void AutoMapDialog::execute() {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ int frame2 = intf._overallFrame * 2;
+ bool frameEndFlag = false;
+
+ Common::Point pt = party._mazePosition;
+ Common::Point arrowPt;
+ SpriteResource globalSprites;
+ globalSprites.load("global.icn");
+
+ if (pt.x < 8 && map.mazeData()._surroundingMazes._west == 0) {
+ arrowPt.x = pt.x * 10 + 4;
+ } else if (pt.x > 23) {
+ arrowPt.x = pt.x * 10 + 100;
+ pt.x = 23;
+ } else if (pt.x > 8 && map.mazeData()._surroundingMazes._east == 0) {
+ arrowPt.x = pt.x * 10 + 4;
+ pt.x = 7;
+ } else {
+ arrowPt.x = 74;
+ }
+
+ if (pt.y < 8 && map.mazeData()._surroundingMazes._south == 0) {
+ arrowPt.y = ((15 - pt.y) << 3) + 13;
+ pt.y = 8;
+ } else if (pt.y > 24) {
+ arrowPt.y = ((15 - (pt.y - 24)) << 3) + 13;
+ pt.y = 24;
+ } else if (pt.y >= 8 && map.mazeData()._surroundingMazes._north == 0) {
+ arrowPt.y = ((15 - pt.y) << 3) + 13;
+ pt.y = 8;
+ } else {
+ arrowPt.y = 69;
+ }
+
+ screen._windows[5].open();
+// MazeData &mazeData = map.mazeDataCurrent();
+ bool drawFlag = true;
+ int v;
+
+ events.updateGameCounter();
+ do {
+ if (drawFlag)
+ intf.draw3d(false);
+ screen._windows[5].writeString("\n");
+
+ if (map._isOutdoors) {
+ // Draw outdoors map
+ for (int yp = 38, yDiff = pt.y + 7; pt.y < 166; --yDiff, yp += 8) {
+ for (int xp = 80, xDiff = pt.x - 7; xp < 240; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 0);
+
+ if (map._currentSteppedOn) {
+ map._tileSprites.draw(screen, map.mazeDataCurrent()._surfaceTypes[v],
+ Common::Point(xp, yp));
+ }
+ }
+ }
+
+ for (int yp = 38, yDiff = pt.y + 7; yp < 166; --yDiff, yp += 8) {
+ for (int xp = 80, xDiff = pt.x - 7; xp < 240; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 4);
+ int wallType = map.mazeDataCurrent()._wallTypes[v];
+
+ if (wallType && map._currentSteppedOn)
+ map._tileSprites.draw(screen, wallType, Common::Point(xp, yp));
+ }
+ }
+
+
+ for (int yp = 38, yDiff = pt.y + 7; yp < 166; yp += 8, --yDiff) {
+ for (int xp = 80, xDiff = -7; xp < 240; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 8);
+
+ if (v && map._currentSteppedOn)
+ map._tileSprites.draw(screen, 1, Common::Point(xp, yp));
+ }
+ }
+ } else {
+ // Draw indoors map
+ frame2 = (frame2 + 2) % 8;
+
+ // Draw default ground for all the valid explored areas
+ for (int yp = 38, yDiff = pt.y + 7; yp < 166; yp += 8, --yDiff) {
+ for (int xp = 80, xDiff = pt.x - 7; xp < 240; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 0, 0xffff);
+
+ if (v != INVALID_CELL && map._currentSteppedOn)
+ map._tileSprites.draw(screen, 0, Common::Point(xp, yp));
+ }
+ }
+
+ // Draw thinner ground tiles on the left edge of the map
+ for (int yp = 43, yDiff = pt.y + 7; yp < 171; yp += 8, --yDiff) {
+ v = map.mazeLookup(Common::Point(pt.x - 8, yDiff), 0, 0xffff);
+
+ if (v != INVALID_CELL && map._currentSurfaceId != 0 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, 36 + map.mazeData()._surfaceTypes[
+ map._currentSurfaceId], Common::Point(75, yp));
+ }
+
+ // Draw thin tile portion on top-left corner of map
+ v = map.mazeLookup(Common::Point(pt.x - 8, pt.y + 8), 0, 0xffff);
+ if (v != INVALID_CELL && map._currentSurfaceId != 0 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, 36 + map.mazeData()._surfaceTypes[
+ map._currentSurfaceId], Common::Point(75, 35));
+
+ // Draw any thin tiles at the very top of the map
+ for (int xp = 85, xDiff = pt.x - 7; xp < 245; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, pt.y + 8), 0, 0xffff);
+
+ if (v != INVALID_CELL && map._currentSurfaceId != 0 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, 36 + map.mazeData()._surfaceTypes[
+ map._currentSurfaceId], Common::Point(xp, 35));
+ }
+
+ // Draw the default ground tiles
+ for (int yp = 43, yDiff = pt.y + 7; yp < 171; yp += 8, --yDiff) {
+ for (int xp = 85, xDiff = pt.x - 7; xp < 245; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 0, 0xffff);
+
+ if (v != INVALID_CELL && map._currentSurfaceId && map._currentSteppedOn)
+ map._tileSprites.draw(screen, map.mazeData()._surfaceTypes[
+ map._currentSurfaceId], Common::Point(xp, yp));
+ }
+ }
+
+ // Draw walls on left and top edges of map
+ for (int xp = 80, yp = 158, xDiff = pt.x - 7, yDiff = pt.y - 8; xp < 250;
+ xp += 10, yp -= 8, ++xDiff, ++yDiff) {
+ // Draw walls on left edge of map
+ v = map.mazeLookup(Common::Point(pt.x - 8, yDiff), 12);
+
+ int frame;
+ switch (v) {
+ case SURFTYPE_DIRT:
+ frame = 18;
+ break;
+ case SURFTYPE_SNOW:
+ frame = 22;
+ break;
+ case SURFTYPE_SWAMP:
+ case SURFTYPE_CLOUD:
+ frame = 16;
+ break;
+ case SURFTYPE_LAVA:
+ case SURFTYPE_DWATER:
+ frame = 2;
+ break;
+ case SURFTYPE_DESERT:
+ frame = 30;
+ break;
+ case SURFTYPE_ROAD:
+ frame = 32;
+ break;
+ case SURFTYPE_TFLR:
+ frame = 20;
+ break;
+ case SURFTYPE_SKY:
+ frame = 28;
+ break;
+ case SURFTYPE_CROAD:
+ frame = 14;
+ break;
+ case SURFTYPE_SEWER:
+ frame = frame2 + 4;
+ break;
+ case SURFTYPE_SCORCH:
+ frame = 24;
+ break;
+ case SURFTYPE_SPACE:
+ frame = 26;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, frame, Common::Point(70, yp));
+
+ // Draw walls on top edge of map
+ v = map.mazeLookup(Common::Point(xDiff, pt.y + 8), 0);
+
+ switch (v) {
+ case SURFTYPE_DIRT:
+ frame = 19;
+ break;
+ case SURFTYPE_GRASS:
+ frame = 35;
+ break;
+ case SURFTYPE_SNOW:
+ frame = 23;
+ break;
+ case SURFTYPE_SWAMP:
+ case SURFTYPE_CLOUD:
+ frame = 17;
+ break;
+ case SURFTYPE_LAVA:
+ case SURFTYPE_DWATER:
+ frame = 3;
+ break;
+ case SURFTYPE_DESERT:
+ frame = 31;
+ break;
+ case SURFTYPE_ROAD:
+ frame = 33;
+ break;
+ case SURFTYPE_TFLR:
+ frame = 21;
+ break;
+ case SURFTYPE_SKY:
+ frame = 29;
+ break;
+ case SURFTYPE_CROAD:
+ frame = 15;
+ break;
+ case SURFTYPE_SEWER:
+ frame = frame2 + 5;
+ break;
+ case SURFTYPE_SCORCH:
+ frame = 25;
+ break;
+ case SURFTYPE_SPACE:
+ frame = 27;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, frame, Common::Point(xp, 30));
+ }
+
+ // Draw any walls on the cells
+ for (int yCtr = 0, yp = 38, yDiff = pt.y + 7; yCtr < 16; ++yCtr, yp += 8, --yDiff) {
+ for (int xCtr = 0, xp = 80, xDiff = pt.x - 7; xCtr < 16; ++xCtr, xp += 10, ++xDiff) {
+ // Draw the arrow if at the correct position
+ if ((arrowPt.x / 10) == xCtr && (14 - (arrowPt.y / 10)) == yCtr && frameEndFlag) {
+ globalSprites.draw(screen, party._mazeDirection + 1,
+ Common::Point(arrowPt.x + 81, arrowPt.y + 29));
+ }
+
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 12);
+ int frame;
+ switch (v) {
+ case SURFTYPE_DIRT:
+ frame = 18;
+ break;
+ case SURFTYPE_GRASS:
+ frame = 34;
+ break;
+ case SURFTYPE_SNOW:
+ frame = 22;
+ break;
+ case SURFTYPE_SWAMP:
+ case SURFTYPE_CLOUD:
+ frame = 16;
+ break;
+ case SURFTYPE_LAVA:
+ case SURFTYPE_DWATER:
+ frame = 2;
+ break;
+ case SURFTYPE_DESERT:
+ frame = 30;
+ break;
+ case SURFTYPE_ROAD:
+ frame = 32;
+ break;
+ case SURFTYPE_TFLR:
+ frame = 20;
+ break;
+ case SURFTYPE_SKY:
+ frame = 28;
+ break;
+ case SURFTYPE_CROAD:
+ frame = 14;
+ break;
+ case SURFTYPE_SEWER:
+ frame = frame2 + 4;
+ break;
+ case SURFTYPE_SCORCH:
+ frame = 24;
+ break;
+ case SURFTYPE_SPACE:
+ frame = 26;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, frame, Common::Point(xp, yp));
+
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 0);
+ switch (v) {
+ case SURFTYPE_DIRT:
+ frame = 19;
+ break;
+ case SURFTYPE_GRASS:
+ frame = 35;
+ break;
+ case SURFTYPE_SNOW:
+ frame = 23;
+ break;
+ case SURFTYPE_SWAMP:
+ case SURFTYPE_CLOUD:
+ frame = 17;
+ break;
+ case SURFTYPE_LAVA:
+ case SURFTYPE_DWATER:
+ frame = 3;
+ break;
+ case SURFTYPE_DESERT:
+ frame = 31;
+ break;
+ case SURFTYPE_ROAD:
+ frame = 33;
+ break;
+ case SURFTYPE_TFLR:
+ frame = 21;
+ break;
+ case SURFTYPE_SKY:
+ frame = 29;
+ break;
+ case SURFTYPE_CROAD:
+ frame = 15;
+ break;
+ case SURFTYPE_SEWER:
+ frame = frame2 + 5;
+ break;
+ case SURFTYPE_SCORCH:
+ frame = 25;
+ break;
+ case SURFTYPE_SPACE:
+ frame = 27;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, frame, Common::Point(xp, yp));
+ }
+ }
+
+ // Draw overlay on cells that haven't been stepped on yet
+ for (int yDiff = pt.y + 7, yp = 38; yp < 166; --yDiff, yp += 8) {
+ for (int xp = 80, xDiff = pt.x - 7; xp < 240; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 0, 0xffff);
+
+ if (v == INVALID_CELL || !map._currentSteppedOn)
+ map._tileSprites.draw(screen, 1, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ screen._windows[5].frame();
+ if (!map._isOutdoors) {
+ map._tileSprites.draw(screen, 52, Common::Point(76, 30));
+ } else if (frameEndFlag) {
+ globalSprites.draw(screen, party._mazeDirection + 1,
+ Common::Point(arrowPt.x + 76, arrowPt.y + 25));
+ }
+
+ if (events.timeElapsed() > 5) {
+ // Set the flag to make the basic arrow blinking effect
+ frameEndFlag = !frameEndFlag;
+ events.updateGameCounter();
+ }
+
+ screen._windows[5].writeString(Common::String::format(MAP_TEXT,
+ map._mazeName.c_str(), party._mazePosition.x,
+ party._mazePosition.y, DIRECTION_TEXT[party._mazeDirection]));
+ screen._windows[5].update();
+ screen._windows[3].update();
+
+ events.pollEvents();
+ drawFlag = false;
+ } while (!_vm->shouldQuit() && !events.isKeyMousePressed());
+
+ events.clearEvents();
+ screen._windows[5].close();
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_automap.h b/engines/xeen/dialogs_automap.h
new file mode 100644
index 0000000000..f20f9b0104
--- /dev/null
+++ b/engines/xeen/dialogs_automap.h
@@ -0,0 +1,45 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_AUTOMAP_H
+#define XEEN_DIALOGS_AUTOMAP_H
+
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class XeenEngine;
+
+class AutoMapDialog: public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ AutoMapDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_AUTOMAP_H */
diff --git a/engines/xeen/dialogs_char_info.cpp b/engines/xeen/dialogs_char_info.cpp
new file mode 100644
index 0000000000..0494c22b9c
--- /dev/null
+++ b/engines/xeen/dialogs_char_info.cpp
@@ -0,0 +1,568 @@
+/* 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.
+ *
+ */
+
+#include "xeen/dialogs_char_info.h"
+#include "xeen/dialogs_exchange.h"
+#include "xeen/dialogs_items.h"
+#include "xeen/dialogs_quick_ref.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void CharacterInfo::show(XeenEngine *vm, int charIndex) {
+ CharacterInfo *dlg = new CharacterInfo(vm);
+ dlg->execute(charIndex);
+ delete dlg;
+}
+
+void CharacterInfo::execute(int charIndex) {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+
+ bool redrawFlag = true;
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_CHARACTER_INFO;
+ loadDrawStructs();
+ addButtons();
+
+ Character *c = (oldMode != MODE_COMBAT) ? &party._activeParty[charIndex] : combat._combatParty[charIndex];
+ intf.highlightChar(charIndex);
+ Window &w = screen._windows[24];
+ w.open();
+
+ do {
+ if (redrawFlag) {
+ Common::String charDetails = loadCharacterDetails(*c);
+ w.writeString(Common::String::format(CHARACTER_TEMPLATE, charDetails.c_str()));
+ w.drawList(_drawList, 24);
+ w.update();
+ redrawFlag = false;
+ }
+
+ // Wait for keypress, showing a blinking cursor
+ events.updateGameCounter();
+ bool cursorFlag = false;
+ _buttonValue = 0;
+ while (!_vm->shouldQuit() && !_buttonValue) {
+ events.pollEventsAndWait();
+ if (events.timeElapsed() > 4) {
+ cursorFlag = !cursorFlag;
+ events.updateGameCounter();
+ }
+
+ showCursor(cursorFlag);
+ w.update();
+ checkEvents(_vm);
+ }
+ events.clearEvents();
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)(oldMode == MODE_COMBAT ? combat._combatParty.size() : party._activeParty.size())) {
+ charIndex = _buttonValue;
+ c = (oldMode != MODE_COMBAT) ? &party._activeParty[charIndex] : combat._combatParty[charIndex];
+ } else {
+ _vm->_mode = MODE_CHARACTER_INFO;
+ }
+ redrawFlag = true;
+ break;
+
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ if (_cursorCell > 0) {
+ showCursor(false);
+ --_cursorCell;
+ showCursor(true);
+ }
+ w.update();
+ break;
+
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ if (_cursorCell < 20) {
+ showCursor(false);
+ ++_cursorCell;
+ showCursor(true);
+ }
+ w.update();
+ break;
+
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP4:
+ if (_cursorCell >= 5) {
+ showCursor(false);
+ _cursorCell -= 5;
+ showCursor(true);
+ }
+ w.update();
+ break;
+
+ case Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP6:
+ if (_cursorCell <= 15) {
+ showCursor(false);
+ _cursorCell += 5;
+ showCursor(true);
+ }
+ w.update();
+ break;
+
+ case Common::KEYCODE_RETURN:
+ case Common::KEYCODE_KP_ENTER:
+ _buttonValue = _cursorCell + Common::KEYCODE_a;
+ // Deliberate fall-through
+
+ case 1001:
+ case 1002:
+ case 1003:
+ case 1004:
+ case 1005:
+ case 1006:
+ case 1007:
+ case 1008:
+ case 1009:
+ case 1010:
+ case 1011:
+ case 1012:
+ case 1013:
+ case 1014:
+ case 1015:
+ case 1016:
+ case 1017:
+ case 1018:
+ case 1019:
+ case 1020: {
+ showCursor(false);
+ _cursorCell = _buttonValue - 1001;
+ showCursor(true);
+ w.update();
+
+ bool result = expandStat(_cursorCell, *c);
+ _vm->_mode = MODE_COMBAT;
+ if (result)
+ redrawFlag = true;
+ break;
+ }
+
+ case Common::KEYCODE_e:
+ if (oldMode == MODE_COMBAT) {
+ ErrorScroll::show(_vm, EXCHANGING_IN_COMBAT, WT_FREEZE_WAIT);
+ } else {
+ _vm->_mode = oldMode;
+ ExchangeDialog::show(_vm, c, charIndex);
+ _vm->_mode = MODE_CHARACTER_INFO;
+ redrawFlag = true;
+ }
+ break;
+
+ case Common::KEYCODE_i:
+ _vm->_mode = oldMode;
+ _vm->_combat->_itemFlag = _vm->_mode == MODE_COMBAT;
+ c = ItemsDialog::show(_vm, c, ITEMMODE_CHAR_INFO);
+
+ if (!c) {
+ party._stepped = true;
+ goto exit;
+ }
+
+ _vm->_mode = MODE_CHARACTER_INFO;
+ break;
+
+ case Common::KEYCODE_q:
+ QuickReferenceDialog::show(_vm);
+ redrawFlag = true;
+ break;
+
+ case Common::KEYCODE_ESCAPE:
+ goto exit;
+ }
+ } while (!_vm->shouldQuit());
+exit:
+ w.close();
+ intf.unhighlightChar();
+ _vm->_mode = oldMode;
+ _vm->_combat->_itemFlag = false;
+}
+
+void CharacterInfo::loadDrawStructs() {
+ _drawList[0] = DrawStruct(0, 2, 16);
+ _drawList[1] = DrawStruct(2, 2, 39);
+ _drawList[2] = DrawStruct(4, 2, 62);
+ _drawList[3] = DrawStruct(6, 2, 85);
+ _drawList[4] = DrawStruct(8, 2, 108);
+ _drawList[5] = DrawStruct(10, 53, 16);
+ _drawList[6] = DrawStruct(12, 53, 39);
+ _drawList[7] = DrawStruct(14, 53, 62);
+ _drawList[8] = DrawStruct(16, 53, 85);
+ _drawList[9] = DrawStruct(18, 53, 108);
+ _drawList[10] = DrawStruct(20, 104, 16);
+ _drawList[11] = DrawStruct(22, 104, 39);
+ _drawList[12] = DrawStruct(24, 104, 62);
+ _drawList[13] = DrawStruct(26, 104, 85);
+ _drawList[14] = DrawStruct(28, 104, 108);
+ _drawList[15] = DrawStruct(30, 169, 16);
+ _drawList[16] = DrawStruct(32, 169, 39);
+ _drawList[17] = DrawStruct(34, 169, 62);
+ _drawList[18] = DrawStruct(36, 169, 85);
+ _drawList[19] = DrawStruct(38, 169, 108);
+ _drawList[20] = DrawStruct(40, 277, 3);
+ _drawList[21] = DrawStruct(42, 277, 35);
+ _drawList[22] = DrawStruct(44, 277, 67);
+ _drawList[23] = DrawStruct(46, 277, 99);
+
+ _iconSprites.load("view.icn");
+ for (int idx = 0; idx < 24; ++idx)
+ _drawList[idx]._sprites = &_iconSprites;
+}
+
+void CharacterInfo::addButtons() {
+ addButton(Common::Rect(10, 24, 34, 44), 1001, &_iconSprites);
+ addButton(Common::Rect(10, 47, 34, 67), 1002, &_iconSprites);
+ addButton(Common::Rect(10, 70, 34, 90), 1003, &_iconSprites);
+ addButton(Common::Rect(10, 93, 34, 113), 1004, &_iconSprites);
+ addButton(Common::Rect(10, 116, 34, 136), 1005, &_iconSprites);
+ addButton(Common::Rect(61, 24, 85, 44), 1006, &_iconSprites);
+ addButton(Common::Rect(61, 47, 85, 67), 1007, &_iconSprites);
+ addButton(Common::Rect(61, 70, 85, 90), 1008, &_iconSprites);
+ addButton(Common::Rect(61, 93, 85, 113), 1009, &_iconSprites);
+ addButton(Common::Rect(61, 116, 85, 136), 1010, &_iconSprites);
+ addButton(Common::Rect(112, 24, 136, 44), 1011, &_iconSprites);
+ addButton(Common::Rect(112, 47, 136, 67), 1012, &_iconSprites);
+ addButton(Common::Rect(112, 70, 136, 90), 1013, &_iconSprites);
+ addButton(Common::Rect(112, 93, 136, 113), 1014, &_iconSprites);
+ addButton(Common::Rect(112, 116, 136, 136), 1015, &_iconSprites);
+ addButton(Common::Rect(177, 24, 201, 44), 1016, &_iconSprites);
+ addButton(Common::Rect(177, 47, 201, 67), 1017, &_iconSprites);
+ addButton(Common::Rect(177, 70, 201, 90), 1018, &_iconSprites);
+ addButton(Common::Rect(177, 93, 201, 113), 1019, &_iconSprites);
+ addButton(Common::Rect(177, 116, 201, 136), 1020, &_iconSprites);
+ addButton(Common::Rect(285, 11, 309, 31), Common::KEYCODE_i, &_iconSprites);
+ addButton(Common::Rect(285, 43, 309, 63), Common::KEYCODE_q, &_iconSprites);
+ addButton(Common::Rect(285, 75, 309, 95), Common::KEYCODE_e, &_iconSprites);
+ addButton(Common::Rect(285, 107, 309, 127), Common::KEYCODE_ESCAPE, &_iconSprites);
+ addPartyButtons(_vm);
+}
+
+Common::String CharacterInfo::loadCharacterDetails(const Character &c) {
+ Condition condition = c.worstCondition();
+ Party &party = *_vm->_party;
+ int foodVal = party._food / party._activeParty.size() / 3;
+
+ int totalResist =
+ c._fireResistence._permanent + c.itemScan(11) + c._fireResistence._temporary +
+ c._coldResistence._permanent + c.itemScan(13) + c._coldResistence._temporary +
+ c._electricityResistence._permanent + c.itemScan(12) + c._electricityResistence._temporary +
+ c._poisonResistence._permanent + c.itemScan(14) + c._poisonResistence._temporary +
+ c._energyResistence._permanent + c.itemScan(15) + c._energyResistence._temporary +
+ c._magicResistence._permanent + c.itemScan(16) + c._magicResistence._temporary;
+
+ return Common::String::format(CHARACTER_DETAILS,
+ PARTY_GOLD, c._name.c_str(), SEX_NAMES[c._sex],
+ RACE_NAMES[c._race], CLASS_NAMES[c._class],
+ c.statColor(c.getStat(MIGHT), c.getStat(MIGHT, true)), c.getStat(MIGHT),
+ c.statColor(c.getStat(ACCURACY), c.getStat(ACCURACY, true)), c.getStat(ACCURACY),
+ c.statColor(c._currentHp, c.getMaxHP()), c._currentHp,
+ c.getCurrentExperience(),
+ c.statColor(c.getStat(INTELLECT), c.getStat(INTELLECT, true)), c.getStat(INTELLECT),
+ c.statColor(c.getStat(LUCK), c.getStat(LUCK, true)), c.getStat(LUCK),
+ c.statColor(c._currentSp, c.getMaxSP()), c._currentSp,
+ party._gold,
+ c.statColor(c.getStat(PERSONALITY), c.getStat(PERSONALITY, true)), c.getStat(PERSONALITY),
+ c.statColor(c.getAge(), c.getAge(true)), c.getAge(),
+ totalResist,
+ party._gems,
+ c.statColor(c.getStat(ENDURANCE), c.getStat(ENDURANCE, true)), c.getStat(ENDURANCE),
+ c.statColor(c.getCurrentLevel(), c._level._permanent), c.getCurrentLevel(),
+ c.getNumSkills(),
+ foodVal, (foodVal == 1) ? ' ' : 's',
+ c.statColor(c.getStat(SPEED), c.getStat(SPEED, true)), c.getStat(SPEED),
+ c.statColor(c.getArmorClass(), c.getArmorClass(true)), c.getArmorClass(),
+ c.getNumAwards(),
+ CONDITION_COLORS[condition], CONDITION_NAMES[condition],
+ condition == NO_CONDITION && party._blessed ? PLUS_14 : "",
+ condition == NO_CONDITION && party._powerShield ? PLUS_14 : "",
+ condition == NO_CONDITION && party._holyBonus ? PLUS_14 : "",
+ condition == NO_CONDITION && party._heroism ? PLUS_14 : ""
+ );
+}
+
+void CharacterInfo::showCursor(bool flag) {
+ Screen &screen = *_vm->_screen;
+ const int CURSOR_X[5] = { 9, 60, 111, 176, 0 };
+ const int CURSOR_Y[5] = { 23, 46, 69, 92, 115 };
+
+ if (_cursorCell < 20) {
+ _iconSprites.draw(screen, flag ? 49 : 48,
+ Common::Point(CURSOR_X[_cursorCell / 5], CURSOR_Y[_cursorCell % 5]));
+ }
+}
+
+bool CharacterInfo::expandStat(int attrib, const Character &c) {
+ const int STAT_POS[2][20] = {
+ {
+ 61, 61, 61, 61, 61, 112, 112, 112, 112, 112,
+ 177, 177, 177, 177, 177, 34, 34, 34, 34, 34
+ }, {
+ 24, 47, 70, 93, 116, 24, 47, 70, 93, 116,
+ 24, 47, 70, 93, 116, 24, 47, 70, 93, 116
+ }
+ };
+ assert(attrib < 20);
+ Common::Rect bounds(STAT_POS[0][attrib], STAT_POS[1][attrib],
+ STAT_POS[0][attrib] + 143, STAT_POS[1][attrib] + 52);
+ Party &party = *_vm->_party;
+ uint stat1, stat2;
+ uint idx;
+ Common::String msg;
+
+ switch (attrib) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ // Basic attributes
+ stat1 = c.getStat((Attribute)attrib, false);
+ stat2 = c.getStat((Attribute)attrib, true);
+ idx = 0;
+ while (STAT_VALUES[idx] <= stat1)
+ ++idx;
+
+ msg = Common::String::format(CURRENT_MAXIMUM_RATING_TEXT, STAT_NAMES[attrib],
+ stat1, stat2, RATING_TEXT[idx]);
+ break;
+
+ case 7:
+ // Age
+ stat1 = c.getAge(false);
+ stat2 = c.getAge(true);
+ msg = Common::String::format(AGE_TEXT, STAT_NAMES[attrib],
+ stat1, stat2, c._birthDay, c._birthYear);
+ break;
+
+ case 8: {
+ // Level
+ const int CLASS_ATTACK_GAINS[10] = { 5, 6, 6, 7, 8, 6, 5, 4, 7, 6 };
+ idx = c.getCurrentLevel() / CLASS_ATTACK_GAINS[c._class] + 1;
+
+ msg = Common::String::format(LEVEL_TEXT, STAT_NAMES[attrib],
+ c.getCurrentLevel(), c._level._permanent,
+ idx, idx > 1 ? "s" : "",
+ c._level._permanent);
+ break;
+ }
+
+ case 9:
+ // Armor Class
+ stat1 = c.getArmorClass(false);
+ stat2 = c.getArmorClass(true);
+ msg = Common::String::format(CURRENT_MAXIMUM_TEXT, STAT_NAMES[attrib],
+ stat1, stat2);
+ bounds.setHeight(42);
+ break;
+
+ case 10:
+ // Hit Points
+ stat1 = c._currentHp;
+ stat2 = c.getMaxHP();
+ msg = Common::String::format(CURRENT_MAXIMUM_TEXT, STAT_NAMES[attrib],
+ stat1, stat2);
+ bounds.setHeight(42);
+ break;
+
+ case 11:
+ // Spell Points
+ stat1 = c._currentSp;
+ stat2 = c.getMaxSP();
+ msg = Common::String::format(CURRENT_MAXIMUM_TEXT, STAT_NAMES[attrib],
+ stat1, stat2);
+ bounds.setHeight(42);
+ break;
+
+ case 12:
+ // Resistences
+ msg = Common::String::format(RESISTENCES_TEXT, STAT_NAMES[attrib],
+ c._fireResistence._permanent + c.itemScan(11) + c._fireResistence._temporary,
+ c._coldResistence._permanent + c.itemScan(13) + c._coldResistence._temporary,
+ c._electricityResistence._permanent + c.itemScan(12) + c._electricityResistence._temporary,
+ c._poisonResistence._permanent + c.itemScan(14) + c._poisonResistence._temporary,
+ c._energyResistence._permanent + c.itemScan(15) + c._energyResistence._temporary,
+ c._magicResistence._permanent + c.itemScan(16) + c._magicResistence._temporary);
+ bounds.setHeight(80);
+ break;
+
+ case 13: {
+ // Skills
+ Common::String lines[20];
+ int numLines = c.getNumSkills();
+ if (numLines > 0) {
+ for (int skill = THIEVERY; skill <= DANGER_SENSE; ++skill) {
+ if (c._skills[skill]) {
+ if (skill == THIEVERY) {
+ lines[0] = Common::String::format("\n\t020%s%u",
+ SKILL_NAMES[THIEVERY], c.getThievery());
+ } else {
+ lines[skill] = Common::String::format("\n\t020%s", SKILL_NAMES[skill]);
+ }
+ }
+ }
+ } else {
+ lines[0] = NONE;
+ numLines = 1;
+ }
+
+ msg = Common::String::format("\x2\x3""c%s\x3l%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+ STAT_NAMES[attrib], lines[0].c_str(), lines[1].c_str(),
+ lines[2].c_str(), lines[3].c_str(), lines[4].c_str(), lines[5].c_str(),
+ lines[17].c_str(), lines[6].c_str(), lines[7].c_str(), lines[8].c_str(),
+ lines[9].c_str(), lines[10].c_str(), lines[11].c_str(), lines[12].c_str(),
+ lines[13].c_str(), lines[16].c_str(), lines[14].c_str(), lines[15].c_str());
+
+ bounds.top -= (numLines / 2) * 8;
+ bounds.setHeight(numLines * 9 + 26);
+ if (bounds.bottom >= SCREEN_HEIGHT)
+ bounds.moveTo(bounds.left, SCREEN_HEIGHT - bounds.height() - 1);
+ break;
+ }
+
+ case 14:
+ // Awards
+ error("AwardsDialog::show");
+ return false;
+
+ case 15:
+ // Experience
+ stat1 = c.getCurrentExperience();
+ stat2 = c.experienceToNextLevel();
+ msg = Common::String::format(EXPERIENCE_TEXT,
+ STAT_NAMES[attrib], stat1,
+ stat2 == 0 ? ELIGIBLE : Common::String::format("%d", stat2).c_str()
+ );
+ bounds.setHeight(43);
+ break;
+
+ case 16:
+ // Gold
+ msg = Common::String::format(IN_PARTY_IN_BANK, STAT_NAMES[attrib],
+ party._gold, party._bankGold);
+ bounds.setHeight(43);
+ break;
+
+ case 17:
+ // Gems
+ msg = Common::String::format(IN_PARTY_IN_BANK, STAT_NAMES[attrib],
+ party._gems, party._bankGems);
+ bounds.setHeight(43);
+ break;
+
+ case 18: {
+ // Food
+ int food = (party._food / party._activeParty.size()) / 3;
+ msg = Common::String::format(FOOD_TEXT, STAT_NAMES[attrib],
+ party._food, food, food != 1 ? "s" : "");
+ break;
+ }
+
+ case 19: {
+ // Conditions
+ Common::String lines[20];
+ int total = 0;
+ for (int condition = CURSED; condition <= ERADICATED; ++condition) {
+ if (c._conditions[condition]) {
+ if (condition >= UNCONSCIOUS) {
+ lines[condition] = Common::String::format("\n\t020%s",
+ CONDITION_NAMES[condition]);
+ } else {
+ lines[condition] = Common::String::format("\n\t020%s\t095-%d",
+ CONDITION_NAMES[condition], c._conditions[condition]);
+ }
+
+ ++total;
+ }
+ }
+
+ Condition condition = c.worstCondition();
+ if (condition == NO_CONDITION) {
+ lines[0] = Common::String::format("\n\t020%s", GOOD);
+ ++total;
+ }
+
+ if (party._blessed)
+ lines[16] = Common::String::format(BLESSED, party._blessed);
+ if (party._powerShield)
+ lines[17] = Common::String::format(POWER_SHIELD, party._powerShield);
+ if (party._holyBonus)
+ lines[18] = Common::String::format(HOLY_BONUS, party._holyBonus);
+ if (party._heroism)
+ lines[19] = Common::String::format(HEROISM, party._heroism);
+
+ msg = Common::String::format("\x2\x3""c%s\x3l%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\x1",
+ STAT_NAMES[attrib], lines[0].c_str(), lines[1].c_str(),
+ lines[2].c_str(), lines[3].c_str(), lines[4].c_str(),
+ lines[5].c_str(), lines[6].c_str(), lines[7].c_str(),
+ lines[8].c_str(), lines[9].c_str(), lines[10].c_str(),
+ lines[11].c_str(), lines[12].c_str(), lines[13].c_str(),
+ lines[14].c_str(), lines[15].c_str(), lines[16].c_str(),
+ lines[17].c_str(), lines[18].c_str(), lines[19].c_str()
+ );
+
+ bounds.top -= ((total - 1) / 2) * 8;
+ bounds.setHeight(total * 9 + 26);
+ if (bounds.bottom >= SCREEN_HEIGHT)
+ bounds.moveTo(bounds.left, SCREEN_HEIGHT - bounds.height() - 1);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ // Write the data for the stat display
+ Window &w = _vm->_screen->_windows[28];
+ w.setBounds(bounds);
+ w.open();
+ w.writeString(msg);
+ w.update();
+
+ // Wait for a user key/click
+ EventsManager &events = *_vm->_events;
+ while (!_vm->shouldQuit() && !events.isKeyMousePressed())
+ events.pollEventsAndWait();
+ events.clearEvents();
+
+ w.close();
+ return false;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_char_info.h b/engines/xeen/dialogs_char_info.h
new file mode 100644
index 0000000000..90b35a9f6b
--- /dev/null
+++ b/engines/xeen/dialogs_char_info.h
@@ -0,0 +1,70 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_CHAR_INFO_H
+#define XEEN_DIALOGS_CHAR_INFO_H
+
+#include "xeen/dialogs.h"
+#include "xeen/party.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+class CharacterInfo : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+ DrawStruct _drawList[24];
+ int _cursorCell;
+
+ CharacterInfo(XeenEngine *vm) : ButtonContainer(), _vm(vm), _cursorCell(0) {}
+
+ void execute(int charIndex);
+
+ /**
+ * Load the draw structure list with frame numbers and positions
+ */
+ void loadDrawStructs();
+
+ /**
+ * Set up the button list for the dialog
+ */
+ void addButtons();
+
+ /**
+ * Return a string containing the details of the character
+ */
+ Common::String loadCharacterDetails(const Character &c);
+
+ /**
+ * Cursor display handling
+ */
+ void showCursor(bool flag);
+
+ bool expandStat(int attrib, const Character &c);
+public:
+ static void show(XeenEngine *vm, int charIndex);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_CHAR_INFO_H */
diff --git a/engines/xeen/dialogs_control_panel.cpp b/engines/xeen/dialogs_control_panel.cpp
new file mode 100644
index 0000000000..7e8f89cc39
--- /dev/null
+++ b/engines/xeen/dialogs_control_panel.cpp
@@ -0,0 +1,42 @@
+/* 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.
+ *
+ */
+
+#include "xeen/dialogs_control_panel.h"
+#include "xeen/party.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+int ControlPanel::show(XeenEngine *vm) {
+ ControlPanel *dlg = new ControlPanel(vm);
+ int result = dlg->execute();
+ delete dlg;
+
+ return result;
+}
+
+int ControlPanel::execute() {
+ error("TODO: ControlPanel");
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_control_panel.h b/engines/xeen/dialogs_control_panel.h
new file mode 100644
index 0000000000..16c3781789
--- /dev/null
+++ b/engines/xeen/dialogs_control_panel.h
@@ -0,0 +1,43 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_CONTROL_PANEL_H
+#define XEEN_DIALOGS_CONTROL_PANEL_H
+
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class ControlPanel : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ ControlPanel(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute();
+public:
+ static int show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_CONTROL_PANEL_H */
diff --git a/engines/xeen/dialogs_dismiss.cpp b/engines/xeen/dialogs_dismiss.cpp
new file mode 100644
index 0000000000..9323b46429
--- /dev/null
+++ b/engines/xeen/dialogs_dismiss.cpp
@@ -0,0 +1,95 @@
+/* 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.
+ *
+ */
+
+#include "xeen/dialogs_dismiss.h"
+#include "xeen/party.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void Dismiss::show(XeenEngine *vm) {
+ Dismiss *dlg = new Dismiss(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void Dismiss::execute() {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ loadButtons();
+
+ Window &w = screen._windows[31];
+ w.open();
+ _iconSprites.draw(w, 0, Common::Point(225, 120));
+ w.update();
+
+ bool breakFlag = false;
+ while (!_vm->shouldQuit() && !breakFlag) {
+ do {
+ events.updateGameCounter();
+ intf.draw3d(false);
+ w.frame();
+ w.writeString("\r");
+ _iconSprites.draw(w, 0, Common::Point(225, 120));
+ screen._windows[3].update();
+ w.update();
+
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ } while (!_vm->shouldQuit() && !_buttonValue && events.timeElapsed() == 0);
+ } while (!_vm->shouldQuit() && !_buttonValue);
+
+ if (_buttonValue >= Common::KEYCODE_F1 && _buttonValue <= Common::KEYCODE_F6) {
+ _buttonValue -= Common::KEYCODE_F1;
+
+ if (_buttonValue < (int)party._activeParty.size()) {
+ if (party._activeParty.size() == 1) {
+ w.close();
+ ErrorScroll::show(_vm, CANT_DISMISS_LAST_CHAR, WT_NONFREEZED_WAIT);
+ w.open();
+ } else {
+ // Remove the character from the party
+ party._activeParty.remove_at(_buttonValue);
+ breakFlag = true;
+ }
+ break;
+ }
+ } else if (_buttonValue == Common::KEYCODE_ESCAPE) {
+ breakFlag = true;
+ }
+ }
+}
+
+void Dismiss::loadButtons() {
+ _iconSprites.load("esc.icn");
+ addButton(Common::Rect(225, 120, 249, 140), Common::KEYCODE_ESCAPE, &_iconSprites);
+ addButton(Common::Rect(16, 16, 48, 48), Common::KEYCODE_1);
+ addButton(Common::Rect(117, 16, 149, 48), Common::KEYCODE_2);
+ addButton(Common::Rect(16, 59, 48, 91), Common::KEYCODE_3);
+ addButton(Common::Rect(117, 59, 149, 91), Common::KEYCODE_4);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_dismiss.h b/engines/xeen/dialogs_dismiss.h
new file mode 100644
index 0000000000..ec40e87f7c
--- /dev/null
+++ b/engines/xeen/dialogs_dismiss.h
@@ -0,0 +1,47 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_DISMISS_H
+#define XEEN_DIALOGS_DISMISS_H
+
+#include "xeen/dialogs.h"
+#include "xeen/party.h"
+
+namespace Xeen {
+
+class Dismiss : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ Dismiss(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+
+ void loadButtons();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_DISMISS_H */
diff --git a/engines/xeen/dialogs_error.cpp b/engines/xeen/dialogs_error.cpp
new file mode 100644
index 0000000000..7204bad8fe
--- /dev/null
+++ b/engines/xeen/dialogs_error.cpp
@@ -0,0 +1,118 @@
+/* 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.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/events.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void ErrorDialog::show(XeenEngine *vm, const Common::String &msg, ErrorWaitType waitType) {
+ ErrorDialog *dlg = new ErrorDialog(vm);
+ dlg->execute(msg, waitType);
+ delete dlg;
+}
+
+void ErrorDialog::execute(const Common::String &msg, ErrorWaitType waitType) {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ Window &w = screen._windows[6];
+
+ w.open();
+ w.writeString(msg);
+ w.update();
+
+ switch (waitType) {
+ case WT_FREEZE_WAIT:
+ while (!_vm->shouldQuit() && !events.isKeyMousePressed())
+ events.pollEventsAndWait();
+
+ events.clearEvents();
+ break;
+ case WT_3:
+ if (w._enabled || _vm->_mode == MODE_17) {
+ warning("TODO: sub_26D8F");
+ break;
+ }
+ // Deliberate fall-through
+ case WT_NONFREEZED_WAIT:
+ do {
+ events.updateGameCounter();
+ _vm->_interface->draw3d(true);
+
+ events.wait(1, true);
+ if (checkEvents(_vm))
+ break;
+ } while (!_vm->shouldQuit() && !_buttonValue);
+ break;
+ case WT_2:
+ warning("TODO: sub_26D8F");
+ break;
+ default:
+ break;
+ }
+
+ w.close();
+}
+
+/*------------------------------------------------------------------------*/
+
+void ErrorScroll::show(XeenEngine *vm, const Common::String &msg, ErrorWaitType waitType) {
+ Common::String s = Common::String::format("\x3""c\v010\t000%s", msg.c_str());
+ ErrorDialog::show(vm, s, waitType);
+}
+
+/*------------------------------------------------------------------------*/
+
+void CantCast::show(XeenEngine *vm, int spellId, int componentNum) {
+ CantCast *dlg = new CantCast(vm);
+ dlg->execute(spellId, componentNum);
+ delete dlg;
+}
+
+void CantCast::execute(int spellId, int componentNum) {
+ EventsManager &events = *_vm->_events;
+ SoundManager &sound = *_vm->_sound;
+ Spells &spells = *_vm->_spells;
+ Window &w = _vm->_screen->_windows[6];
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_FF;
+
+ sound.playFX(21);
+ w.open();
+ w.writeString(Common::String::format(NOT_ENOUGH_TO_CAST,
+ SPELL_CAST_COMPONENTS[componentNum - 1],
+ spells._spellNames[spellId].c_str()
+ ));
+ w.update();
+
+ do {
+ events.pollEventsAndWait();
+ } while (!_vm->shouldQuit() && !events.isKeyMousePressed());
+ events.clearEvents();
+
+ w.close();
+ _vm->_mode = oldMode;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_error.h b/engines/xeen/dialogs_error.h
new file mode 100644
index 0000000000..46efdb1683
--- /dev/null
+++ b/engines/xeen/dialogs_error.h
@@ -0,0 +1,65 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_ERROR_H
+#define XEEN_DIALOGS_ERROR_H
+
+#include "xeen/dialogs.h"
+#include "xeen/character.h"
+
+namespace Xeen {
+
+enum ErrorWaitType { WT_FREEZE_WAIT = 0, WT_NONFREEZED_WAIT = 1,
+ WT_2 = 2, WT_3 = 3 };
+
+class ErrorDialog : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ ErrorDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute(const Common::String &msg, ErrorWaitType waitType);
+public:
+ static void show(XeenEngine *vm, const Common::String &msg,
+ ErrorWaitType waitType = WT_FREEZE_WAIT);
+};
+
+class ErrorScroll {
+public:
+ static void show(XeenEngine *vm, const Common::String &msg,
+ ErrorWaitType waitType = WT_FREEZE_WAIT);
+};
+
+class CantCast: public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ CantCast(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute(int spellId, int componentNum);
+public:
+ static void show(XeenEngine *vm, int spellId, int componentNum);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_ERROR_H */
diff --git a/engines/xeen/dialogs_exchange.cpp b/engines/xeen/dialogs_exchange.cpp
new file mode 100644
index 0000000000..37da4e0963
--- /dev/null
+++ b/engines/xeen/dialogs_exchange.cpp
@@ -0,0 +1,80 @@
+/* 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.
+ *
+ */
+
+#include "xeen/dialogs_exchange.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void ExchangeDialog::show(XeenEngine *vm, Character *&c, int &charIndex) {
+ ExchangeDialog *dlg = new ExchangeDialog(vm);
+ dlg->execute(c, charIndex);
+ delete dlg;
+}
+
+void ExchangeDialog::execute(Character *&c, int &charIndex) {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ loadButtons();
+
+ Window &w = screen._windows[31];
+ w.open();
+ w.writeString(EXCHANGE_WITH_WHOM);
+ _iconSprites.draw(w, 0, Common::Point(225, 120));
+ w.update();
+
+ while (!_vm->shouldQuit()) {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+
+ if (_buttonValue >= Common::KEYCODE_F1 && _buttonValue <= Common::KEYCODE_F6) {
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ SWAP(party._activeParty[charIndex], party._activeParty[_buttonValue]);
+
+ charIndex = _buttonValue;
+ c = &party._activeParty[charIndex];
+ break;
+ }
+ } else if (_buttonValue == Common::KEYCODE_ESCAPE) {
+ break;
+ }
+ }
+
+ w.close();
+ intf.drawParty(true);
+ intf.highlightChar(charIndex);
+}
+
+void ExchangeDialog::loadButtons() {
+ _iconSprites.load("esc.icn");
+ addButton(Common::Rect(225, 120, 249, 245), Common::KEYCODE_ESCAPE, &_iconSprites);
+ addButton(Common::Rect(16, 16, 48, 48), Common::KEYCODE_1);
+ addButton(Common::Rect(117, 16, 149, 48), Common::KEYCODE_2);
+ addButton(Common::Rect(16, 59, 48, 91), Common::KEYCODE_3);
+ addButton(Common::Rect(117, 59, 149, 91), Common::KEYCODE_4);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_exchange.h b/engines/xeen/dialogs_exchange.h
new file mode 100644
index 0000000000..e8c4a2dfb1
--- /dev/null
+++ b/engines/xeen/dialogs_exchange.h
@@ -0,0 +1,47 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_EXCHANGE_H
+#define XEEN_DIALOGS_EXCHANGE_H
+
+#include "xeen/dialogs.h"
+#include "xeen/party.h"
+
+namespace Xeen {
+
+class ExchangeDialog : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ ExchangeDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute(Character *&c, int &charIndex);
+
+ void loadButtons();
+public:
+ static void show(XeenEngine *vm, Character *&c, int &charIndex);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_EXCHANGE_H */
diff --git a/engines/xeen/dialogs_fight_options.cpp b/engines/xeen/dialogs_fight_options.cpp
new file mode 100644
index 0000000000..c84e754dc2
--- /dev/null
+++ b/engines/xeen/dialogs_fight_options.cpp
@@ -0,0 +1,39 @@
+/* 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.
+ *
+ */
+
+#include "xeen/dialogs_fight_options.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void FightOptions::show(XeenEngine *vm) {
+ FightOptions *dlg = new FightOptions(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void FightOptions::execute() {
+ error("TODO: FightOptions");
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_fight_options.h b/engines/xeen/dialogs_fight_options.h
new file mode 100644
index 0000000000..7b058bc6e9
--- /dev/null
+++ b/engines/xeen/dialogs_fight_options.h
@@ -0,0 +1,43 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_FIGHT_OPTIONS_H
+#define XEEN_DIALOGS_FIGHT_OPTIONS_H
+
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class FightOptions : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ FightOptions(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_FIGHT_OPTIONS_H */
diff --git a/engines/xeen/dialogs_info.cpp b/engines/xeen/dialogs_info.cpp
new file mode 100644
index 0000000000..56755630da
--- /dev/null
+++ b/engines/xeen/dialogs_info.cpp
@@ -0,0 +1,128 @@
+/* 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.
+ *
+ */
+
+#include "xeen/dialogs_info.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void InfoDialog::show(XeenEngine *vm) {
+ InfoDialog *dlg = new InfoDialog(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void InfoDialog::execute() {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+
+ protectionText();
+ Common::String statusText = "";
+ for (uint idx = 0; idx < _lines.size(); ++idx)
+ statusText += _lines[idx];
+
+ Common::String gameName;
+ if (_vm->getGameID() == GType_Swords)
+ gameName = SWORDS_GAME_TEXT;
+ else if (_vm->getGameID() == GType_Clouds)
+ gameName = CLOUDS_GAME_TEXT;
+ else if (_vm->getGameID() == GType_DarkSide)
+ gameName = DARKSIDE_GAME_TEXT;
+ else
+ gameName = WORLD_GAME_TEXT;
+
+ // Form the display message
+ int hour = party._minutes / 60;
+ Common::String details = Common::String::format(GAME_INFORMATION,
+ gameName.c_str(), WEEK_DAY_STRINGS[party._day % 10],
+ (hour > 12) ? hour - 12 : (!hour ? 12 : hour),
+ party._minutes % 60, (hour > 11) ? 'p' : 'a',
+ party._day, party._year, statusText.c_str());
+
+ Window &w = screen._windows[28];
+ w.setBounds(Common::Rect(88, 20, 248, 112));
+ w.open();
+
+ do {
+ events.updateGameCounter();
+ intf.draw3d(false);
+ w.frame();
+ w.writeString(details);
+ w.update();
+
+ events.wait(1, true);
+ } while (!_vm->shouldQuit() && !events.isKeyMousePressed());
+
+ events.clearEvents();
+ w.close();
+}
+
+void InfoDialog::protectionText() {
+ Party &party = *_vm->_party;
+// Common::StringArray _lines;
+ const char *const AA_L024 = "\x3l\n\x9""024";
+ const char *const AA_R124 = "\x3r\x9""124";
+
+ if (party._lightCount) {
+ _lines.push_back(Common::String::format(LIGHT_COUNT_TEXT, party._lightCount));
+ }
+
+ if (party._fireResistence) {
+ _lines.push_back(Common::String::format(FIRE_RESISTENCE_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124, party._fireResistence));
+ }
+
+ if (party._electricityResistence) {
+ _lines.push_back(Common::String::format(ELECRICITY_RESISTENCE_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124, party._electricityResistence));
+ }
+
+ if (party._coldResistence) {
+ _lines.push_back(Common::String::format(COLD_RESISTENCE_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124, party._coldResistence));
+ }
+
+ if (party._poisonResistence) {
+ _lines.push_back(Common::String::format(POISON_RESISTENCE_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124, party._poisonResistence));
+ }
+
+ if (party._clairvoyanceActive) {
+ _lines.push_back(Common::String::format(CLAIRVOYANCE_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124));
+ }
+
+ if (party._levitateActive) {
+ _lines.push_back(Common::String::format(LEVITATE_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124));
+ }
+
+ if (party._walkOnWaterActive) {
+ _lines.push_back(Common::String::format(WALK_ON_WATER_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124));
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_info.h b/engines/xeen/dialogs_info.h
new file mode 100644
index 0000000000..66b915788b
--- /dev/null
+++ b/engines/xeen/dialogs_info.h
@@ -0,0 +1,47 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_INFO_H
+#define XEEN_DIALOGS_INFO_H
+
+#include "common/str-array.h"
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class InfoDialog : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ Common::StringArray _lines;
+
+ InfoDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+
+ void protectionText();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_INFO_H */
diff --git a/engines/xeen/dialogs_input.cpp b/engines/xeen/dialogs_input.cpp
new file mode 100644
index 0000000000..40b8847d14
--- /dev/null
+++ b/engines/xeen/dialogs_input.cpp
@@ -0,0 +1,275 @@
+/* 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.
+ *
+ */
+
+#include "xeen/dialogs_input.h"
+#include "xeen/scripts.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+int Input::show(XeenEngine *vm, Window *window, Common::String &line,
+ uint maxLen, int maxWidth, bool isNumeric) {
+ Input *dlg = new Input(vm, window);
+ int result = dlg->getString(line, maxLen, maxWidth, isNumeric);
+ delete dlg;
+
+ return result;
+}
+
+int Input::getString(Common::String &line, uint maxLen, int maxWidth, bool isNumeric) {
+ _vm->_noDirectionSense = true;
+ Common::String msg = Common::String::format("\x3""l\t000\x4%03d\x3""c", maxWidth);
+ _window->writeString(msg);
+ _window->update();
+
+ while (!_vm->shouldQuit()) {
+ Common::KeyCode keyCode = doCursor(msg);
+
+ bool refresh = false;
+ if ((keyCode == Common::KEYCODE_BACKSPACE || keyCode == Common::KEYCODE_DELETE)
+ && line.size() > 0) {
+ line.deleteLastChar();
+ refresh = true;
+ } else if (line.size() < maxLen && (line.size() > 0 || keyCode != Common::KEYCODE_SPACE)
+ && ((isNumeric && keyCode >= Common::KEYCODE_0 && keyCode < Common::KEYCODE_9) ||
+ (!isNumeric && keyCode >= Common::KEYCODE_SPACE && keyCode < Common::KEYCODE_DELETE))) {
+ line += (char)keyCode;
+ refresh = true;
+ } else if (keyCode == Common::KEYCODE_RETURN || keyCode == Common::KEYCODE_KP_ENTER) {
+ break;
+ } else if (keyCode == Common::KEYCODE_ESCAPE) {
+ line = "";
+ break;
+ }
+
+ if (refresh) {
+ msg = Common::String::format("\x3""l\t000\x4%03d\x3""c%s", maxWidth, line.c_str());
+ _window->writeString(msg);
+ _window->update();
+ }
+ }
+
+ _vm->_noDirectionSense = false;
+ return line.size();
+}
+
+Common::KeyCode Input::doCursor(const Common::String &msg) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+
+ bool oldUpDoorText = intf._upDoorText;
+ byte oldTillMove = intf._tillMove;
+ intf._upDoorText = false;
+ intf._tillMove = 0;
+
+ bool flag = !_vm->_startupWindowActive && !screen._windows[25]._enabled
+ && _vm->_mode != MODE_FF && _vm->_mode != MODE_17;
+
+ Common::KeyCode ch = Common::KEYCODE_INVALID;
+ while (!_vm->shouldQuit()) {
+ events.updateGameCounter();
+
+ if (flag)
+ intf.draw3d(false);
+ _window->writeString(msg);
+ _window->update();
+
+ if (flag)
+ screen._windows[3].update();
+
+ events.wait(1, true);
+ if (events.isKeyPending()) {
+ Common::KeyState keyState;
+ events.getKey(keyState);
+ ch = keyState.keycode;
+ break;
+ }
+ }
+
+ _window->writeString("");
+ _window->update();
+
+ intf._tillMove = oldTillMove;
+ intf._upDoorText = oldUpDoorText;
+
+ return ch;
+}
+
+/*------------------------------------------------------------------------*/
+
+StringInput::StringInput(XeenEngine *vm): Input(vm, &vm->_screen->_windows[6]) {
+}
+
+int StringInput::show(XeenEngine *vm, bool type, const Common::String &msg1,
+ const Common::String &msg2, int opcode) {
+ StringInput *dlg = new StringInput(vm);
+ int result = dlg->execute(type, msg1, msg2, opcode);
+ delete dlg;
+
+ return result;
+}
+
+int StringInput::execute(bool type, const Common::String &expected,
+ const Common::String &title, int opcode) {
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ Scripts &scripts = *_vm->_scripts;
+ Window &w = screen._windows[6];
+ SoundManager &sound = *_vm->_sound;
+ int result = 0;
+
+ w.open();
+ w.writeString(Common::String::format("\r\x03""c%s\v024\t000", title.c_str()));
+ w.update();
+
+ Common::String line;
+ if (getString(line, 30, 200, false)) {
+ if (type) {
+ if (line == intf._interfaceText) {
+ result = true;
+ } else if (line == expected) {
+ result = (opcode == 55) ? -1 : 1;
+ }
+ } else {
+ // Load in the mirror list
+ File f(Common::String::format("%smirr.txt",
+ _vm->_files->_isDarkCc ? "dark" : "xeen"));
+ MirrorEntry me;
+ scripts._mirror.clear();
+ while (me.synchronize(f))
+ scripts._mirror.push_back(me);
+
+ for (uint idx = 0; idx < scripts._mirror.size(); ++idx) {
+ if (line == scripts._mirror[idx]._name) {
+ result = idx;
+ sound.playFX(_vm->_files->_isDarkCc ? 35 : 61);
+ break;
+ }
+ }
+ }
+ }
+
+ w.close();
+ return result;
+}
+
+/*------------------------------------------------------------------------*/
+
+NumericInput::NumericInput(XeenEngine *vm, int window) : Input(vm, &vm->_screen->_windows[window]) {
+}
+
+int NumericInput::show(XeenEngine *vm, int window, int maxLength, int maxWidth) {
+ NumericInput *dlg = new NumericInput(vm, window);
+ int result = dlg->execute(maxLength, maxWidth);
+ delete dlg;
+
+ return result;
+}
+
+int NumericInput::execute(int maxLength, int maxWidth) {
+ Common::String line;
+
+ if (getString(line, maxLength, maxWidth, true))
+ return atoi(line.c_str());
+ else
+ return 0;
+}
+
+/*------------------------------------------------------------------------*/
+
+int Choose123::show(XeenEngine *vm, int numOptions) {
+ assert(numOptions <= 3);
+ Choose123 *dlg = new Choose123(vm);
+ int result = dlg->execute(numOptions);
+ delete dlg;
+
+ return result;
+}
+
+int Choose123::execute(int numOptions) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ Town &town = *_vm->_town;
+
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_DIALOG_123;
+
+ loadButtons(numOptions);
+ _iconSprites.draw(screen, 7, Common::Point(232, 74));
+ drawButtons(&screen);
+ screen._windows[34].update();
+
+ int result = -1;
+ while (result == -1) {
+ do {
+ events.updateGameCounter();
+ int delay;
+ if (town.isActive()) {
+ town.drawTownAnim(true);
+ delay = 3;
+ } else {
+ intf.draw3d(true);
+ delay = 1;
+ }
+
+ events.wait(delay, true);
+ if (_vm->shouldQuit())
+ return 0;
+ } while (!_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_ESCAPE:
+ result = 0;
+ break;
+ case Common::KEYCODE_1:
+ case Common::KEYCODE_2:
+ case Common::KEYCODE_3: {
+ int v = _buttonValue - Common::KEYCODE_1 + 1;
+ if (v <= numOptions)
+ result = v;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ _vm->_mode = oldMode;
+ intf.mainIconsPrint();
+
+ return result;
+}
+
+void Choose123::loadButtons(int numOptions) {
+ _iconSprites.load("choose.icn");
+
+ if (numOptions >= 1)
+ addButton(Common::Rect(235, 75, 259, 95), Common::KEYCODE_1, &_iconSprites);
+ if (numOptions >= 2)
+ addButton(Common::Rect(260, 75, 284, 95), Common::KEYCODE_2, &_iconSprites);
+ if (numOptions >= 3)
+ addButton(Common::Rect(286, 75, 311, 95), Common::KEYCODE_3, &_iconSprites);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_input.h b/engines/xeen/dialogs_input.h
new file mode 100644
index 0000000000..e824d17f57
--- /dev/null
+++ b/engines/xeen/dialogs_input.h
@@ -0,0 +1,89 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_STRING_INPUT_H
+#define XEEN_DIALOGS_STRING_INPUT_H
+
+#include "common/keyboard.h"
+#include "xeen/dialogs.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+class Input : public ButtonContainer {
+private:
+ /**
+ * Draws the cursor and waits until the user presses a key
+ */
+ Common::KeyCode doCursor(const Common::String &msg);
+protected:
+ XeenEngine *_vm;
+ Window *_window;
+
+ /**
+ * Allows the user to enter a string
+ */
+ int getString(Common::String &line, uint maxLen, int maxWidth, bool isNumeric);
+
+ Input(XeenEngine *vm, Window *window) : _vm(vm), _window(window) {}
+public:
+ static int show(XeenEngine *vm, Window *window, Common::String &line,
+ uint maxLen, int maxWidth, bool isNumeric = false);
+};
+
+class StringInput : public Input {
+protected:
+ StringInput(XeenEngine *vm);
+
+ int execute(bool type, const Common::String &expected,
+ const Common::String &title, int opcode);
+public:
+ static int show(XeenEngine *vm, bool type, const Common::String &msg1,
+ const Common::String &msg2, int opcode);
+};
+
+class NumericInput : public Input {
+private:
+ NumericInput(XeenEngine *vm, int window);
+
+ int execute(int maxLength, int maxWidth);
+public:
+ static int show(XeenEngine *vm, int window, int maxLength, int maxWidth);
+};
+
+class Choose123 : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ Choose123(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute(int numOptions);
+
+ void loadButtons(int numOptions);
+public:
+ static int show(XeenEngine *vm, int numOptions);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_STRING_INPUT_H */
diff --git a/engines/xeen/dialogs_items.cpp b/engines/xeen/dialogs_items.cpp
new file mode 100644
index 0000000000..4ad9e240bd
--- /dev/null
+++ b/engines/xeen/dialogs_items.cpp
@@ -0,0 +1,1065 @@
+/* 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.
+ *
+ */
+
+#include "xeen/dialogs_items.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/dialogs_quests.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+Character *ItemsDialog::show(XeenEngine *vm, Character *c, ItemsMode mode) {
+ ItemsDialog *dlg = new ItemsDialog(vm);
+ Character *result = dlg->execute(c, mode);
+ delete dlg;
+
+ return result;
+}
+
+Character *ItemsDialog::execute(Character *c, ItemsMode mode) {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+
+ Character *startingChar = c;
+ ItemCategory category = mode == ITEMMODE_RECHARGE || mode == ITEMMODE_COMBAT ?
+ CATEGORY_MISC : CATEGORY_WEAPON;
+ int varA = mode == ITEMMODE_COMBAT ? 1 : 0;
+ if (varA != 0)
+ mode = ITEMMODE_CHAR_INFO;
+ bool updateStock = mode == ITEMMODE_BLACKSMITH;
+ int itemIndex = -1;
+ Common::StringArray lines;
+ int arr[40];
+ int actionIndex = -1;
+
+ events.setCursor(0);
+ loadButtons(mode, c);
+
+ screen._windows[29].open();
+ screen._windows[30].open();
+
+ enum { REDRAW_NONE, REDRAW_TEXT, REDRAW_FULL } redrawFlag = REDRAW_FULL;
+ while (!_vm->shouldQuit()) {
+ if (redrawFlag == REDRAW_FULL) {
+ if ((mode != ITEMMODE_CHAR_INFO || category != CATEGORY_MISC) && mode != ITEMMODE_ENCHANT
+ && mode != ITEMMODE_RECHARGE && mode != ITEMMODE_TO_GOLD) {
+ _buttons[8]._bounds.moveTo(148, _buttons[8]._bounds.top);
+ _buttons[9]._draw = false;
+ } else if (mode == ITEMMODE_RECHARGE) {
+ _buttons[4]._value = Common::KEYCODE_r;
+ } else if (mode == ITEMMODE_ENCHANT) {
+ _buttons[4]._value = Common::KEYCODE_e;
+ } else if (mode == ITEMMODE_TO_GOLD) {
+ _buttons[4]._value = Common::KEYCODE_g;
+ } else {
+ _buttons[8]._bounds.moveTo(0, _buttons[8]._bounds.top);
+ _buttons[9]._draw = true;
+ _buttons[9]._value = Common::KEYCODE_u;
+ }
+
+ // Write text for the dialog
+ Common::String msg;
+ if (mode != ITEMMODE_CHAR_INFO && mode != ITEMMODE_8 && mode != ITEMMODE_ENCHANT
+ && mode != ITEMMODE_RECHARGE && mode != ITEMMODE_TO_GOLD) {
+ msg = Common::String::format(ITEMS_DIALOG_TEXT1,
+ BTN_SELL, BTN_IDENTIFY, BTN_FIX);
+ } else if (mode != ITEMMODE_ENCHANT && mode != ITEMMODE_RECHARGE && mode != ITEMMODE_TO_GOLD) {
+ msg = Common::String::format(ITEMS_DIALOG_TEXT1,
+ category == 3 ? BTN_USE : BTN_EQUIP,
+ BTN_REMOVE, BTN_DISCARD, BTN_QUEST);
+ } else if (mode == ITEMMODE_ENCHANT) {
+ msg = Common::String::format(ITEMS_DIALOG_TEXT2, BTN_ENCHANT);
+ } else if (mode == ITEMMODE_RECHARGE) {
+ msg = Common::String::format(ITEMS_DIALOG_TEXT2, BTN_RECHARGE);
+ } else {
+ msg = Common::String::format(ITEMS_DIALOG_TEXT2, BTN_GOLD);
+ }
+
+ screen._windows[29].writeString(msg);
+ drawButtons(&screen);
+
+ Common::fill(&arr[0], &arr[40], 0);
+ itemIndex = -1;
+ }
+
+ if (redrawFlag == REDRAW_TEXT || redrawFlag == REDRAW_FULL) {
+ lines.clear();
+
+ if (mode == ITEMMODE_CHAR_INFO || category != 3) {
+ _iconSprites.draw(screen, 8, Common::Point(148, 109));
+ }
+ if (mode != ITEMMODE_ENCHANT && mode != ITEMMODE_RECHARGE && mode != ITEMMODE_TO_GOLD) {
+ _iconSprites.draw(screen, 10, Common::Point(182, 109));
+ _iconSprites.draw(screen, 12, Common::Point(216, 109));
+ _iconSprites.draw(screen, 14, Common::Point(250, 109));
+ }
+
+ switch (mode) {
+ case ITEMMODE_CHAR_INFO:
+ _iconSprites.draw(screen, 9, Common::Point(148, 109));
+ break;
+ case ITEMMODE_BLACKSMITH:
+ _iconSprites.draw(screen, 11, Common::Point(182, 109));
+ break;
+ case ITEMMODE_REPAIR:
+ _iconSprites.draw(screen, 15, Common::Point(250, 109));
+ break;
+ case ITEMMODE_IDENTIFY:
+ _iconSprites.draw(screen, 13, Common::Point(216, 109));
+ break;
+ default:
+ break;
+ }
+
+ for (int idx = 0; idx < 9; ++idx) {
+ _itemsDrawList[idx]._y = 10 + idx * 9;
+
+ switch (category) {
+ case CATEGORY_WEAPON:
+ case CATEGORY_ARMOR:
+ case CATEGORY_ACCESSORY: {
+ XeenItem &i = (category == CATEGORY_WEAPON) ? c->_weapons[idx] :
+ ((category == CATEGORY_ARMOR) ? c->_armor[idx] : c->_accessories[idx]);
+
+ if (i._id) {
+ if (mode == ITEMMODE_CHAR_INFO || mode == ITEMMODE_8
+ || mode == ITEMMODE_ENCHANT || mode == ITEMMODE_RECHARGE) {
+ lines.push_back(Common::String::format(ITEMS_DIALOG_LINE1,
+ arr[idx], idx + 1,
+ c->_items[category].getFullDescription(idx, arr[idx]).c_str()));
+ } else {
+ lines.push_back(Common::String::format(ITEMS_DIALOG_LINE2,
+ arr[idx], idx + 1,
+ c->_items[category].getFullDescription(idx, arr[idx]).c_str(),
+ calcItemCost(c, idx, mode,
+ mode == ITEMMODE_TO_GOLD ? 1 : startingChar->_skills[MERCHANT],
+ category)
+ ));
+ }
+
+ DrawStruct &ds = _itemsDrawList[idx];
+ ds._sprites = &_equipSprites;
+ if (c->_weapons.passRestrictions(i._id, true))
+ ds._frame = i._frame;
+ else
+ ds._frame = 14;
+ } else if (_itemsDrawList[idx]._sprites == nullptr) {
+ lines.push_back(NO_ITEMS_AVAILABLE);
+ }
+ break;
+ }
+
+ case CATEGORY_MISC: {
+ XeenItem &i = c->_misc[idx];
+ _itemsDrawList[idx]._sprites = nullptr;
+
+ if (i._material == 0) {
+ // No item
+ if (idx == 0) {
+ lines.push_back(NO_ITEMS_AVAILABLE);
+ }
+ } else {
+ ItemsMode tempMode = mode;
+ int skill = startingChar->_skills[MERCHANT];
+
+ if (mode == ITEMMODE_CHAR_INFO || mode == ITEMMODE_8
+ || mode == ITEMMODE_ENCHANT || mode == ITEMMODE_RECHARGE) {
+ tempMode = ITEMMODE_ENCHANT;
+ } else if (mode == ITEMMODE_TO_GOLD) {
+ skill = 1;
+ }
+
+ lines.push_back(Common::String::format(ITEMS_DIALOG_LINE2,
+ arr[idx], idx + 1,
+ c->_items[category].getFullDescription(idx, arr[idx]).c_str(),
+ calcItemCost(c, idx, tempMode, skill, category)
+ ));
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ while (lines.size() < INV_ITEMS_TOTAL)
+ lines.push_back("");
+
+ // Draw out overall text and the list of items
+ switch (mode) {
+ case ITEMMODE_CHAR_INFO:
+ case ITEMMODE_8:
+ screen._windows[30].writeString(Common::String::format(X_FOR_THE_Y,
+ category == CATEGORY_MISC ? "\x3l" : "\x3c",
+ CATEGORY_NAMES[category], c->_name.c_str(), CLASS_NAMES[c->_class],
+ category == CATEGORY_MISC ? FMT_CHARGES : " ",
+ lines[0].c_str(), lines[1].c_str(), lines[2].c_str(), lines[3].c_str(),
+ lines[4].c_str(), lines[5].c_str(), lines[6].c_str(), lines[7].c_str(),
+ lines[8].c_str()
+ ));
+
+ case ITEMMODE_BLACKSMITH: {
+ // Original uses var in this block that's never set?!
+ const int v1 = 0;
+ screen._windows[30].writeString(Common::String::format(AVAILABLE_GOLD_COST,
+ CATEGORY_NAMES[category],
+ v1 ? "" : "s", party._gold,
+ lines[0].c_str(), lines[1].c_str(), lines[2].c_str(), lines[3].c_str(),
+ lines[4].c_str(), lines[5].c_str(), lines[6].c_str(), lines[7].c_str(),
+ lines[8].c_str()
+ ));
+ break;
+ }
+
+ case ITEMMODE_2:
+ case ITEMMODE_RECHARGE:
+ case ITEMMODE_ENCHANT:
+ case ITEMMODE_REPAIR:
+ case ITEMMODE_IDENTIFY:
+ case ITEMMODE_TO_GOLD:
+ screen._windows[30].writeString(Common::String::format(X_FOR_Y,
+ CATEGORY_NAMES[category], startingChar->_name.c_str(),
+ (mode == ITEMMODE_RECHARGE || mode == ITEMMODE_ENCHANT) ? CHARGES : COST,
+ lines[0].c_str(), lines[1].c_str(), lines[2].c_str(), lines[3].c_str(),
+ lines[4].c_str(), lines[5].c_str(), lines[6].c_str(), lines[7].c_str(),
+ lines[8].c_str()
+ ));
+ break;
+
+ case ITEMMODE_3:
+ case ITEMMODE_5:
+ screen._windows[30].writeString(Common::String::format(X_FOR_Y_GOLD,
+ CATEGORY_NAMES[category], c->_name.c_str(), party._gold, CHARGES,
+ lines[0].c_str(), lines[1].c_str(), lines[2].c_str(), lines[3].c_str(),
+ lines[4].c_str(), lines[5].c_str(), lines[6].c_str(), lines[7].c_str(),
+ lines[8].c_str()
+ ));
+ break;
+
+ default:
+ break;
+ }
+
+ // Draw the glyphs for the items
+ screen._windows[0].drawList(_itemsDrawList, INV_ITEMS_TOTAL);
+ screen._windows[0].update();
+ }
+
+ redrawFlag = REDRAW_NONE;
+
+ if (itemIndex != -1) {
+ switch (mode) {
+ case ITEMMODE_BLACKSMITH:
+ actionIndex = 0;
+ break;
+ case ITEMMODE_2:
+ actionIndex = 1;
+ break;
+ case ITEMMODE_REPAIR:
+ actionIndex = 3;
+ break;
+ case ITEMMODE_IDENTIFY:
+ actionIndex = 2;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // If it's time to do an item action, take care of it
+ if (actionIndex >= 0) {
+ int result = doItemOptions(*c, actionIndex, itemIndex, category, mode);
+ if (result == 1) {
+ // Finish dialog with no selected character
+ c = nullptr;
+ break;
+ } else if (result == 2) {
+ // Close dialogs and finish dialog with original starting character
+ screen._windows[30].close();
+ screen._windows[29].close();
+ c = startingChar;
+ break;
+ }
+
+ // Otherwise, result and continue showing dialog
+ actionIndex = -1;
+ }
+
+ // Wait for a selection
+ _buttonValue = 0;
+ while (!_vm->shouldQuit() && !_buttonValue) {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ }
+ if (_vm->shouldQuit())
+ return nullptr;
+
+ // Handle escaping out of dialog
+ if (_buttonValue == Common::KEYCODE_ESCAPE) {
+ if (mode == ITEMMODE_8)
+ continue;
+ break;
+ }
+
+ // Handle other selections
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ if (!varA && mode != ITEMMODE_3 && mode != ITEMMODE_ENCHANT
+ && mode != ITEMMODE_RECHARGE && mode != ITEMMODE_TO_GOLD
+ && party._mazeId != 0) {
+ _buttonValue -= Common::KEYCODE_F1;
+
+ if (_buttonValue < (int)(_vm->_mode == MODE_COMBAT ?
+ combat._combatParty.size() : party._activeParty.size())) {
+ // Character number is valid
+ redrawFlag = REDRAW_TEXT;
+ Character *newChar = _vm->_mode == MODE_COMBAT ?
+ combat._combatParty[_buttonValue] : &party._activeParty[_buttonValue];
+
+ if (mode == ITEMMODE_BLACKSMITH) {
+ _oldCharacter = newChar;
+ startingChar = newChar;
+ c = &_itemsCharacter;
+ } else if (mode != ITEMMODE_2 && mode != ITEMMODE_REPAIR
+ && mode != ITEMMODE_IDENTIFY && itemIndex != -1) {
+ InventoryItems &destItems = newChar->_items[category];
+ XeenItem &destItem = destItems[INV_ITEMS_TOTAL - 1];
+ InventoryItems &srcItems = c->_items[category];
+ XeenItem &srcItem = srcItems[itemIndex];
+
+ if (srcItem._bonusFlags & ITEMFLAG_CURSED)
+ ErrorScroll::show(_vm, CANNOT_REMOVE_CURSED_ITEM);
+ else if (destItems[INV_ITEMS_TOTAL - 1]._id)
+ ErrorScroll::show(_vm, Common::String::format(
+ CATEGORY_BACKPACK_IS_FULL[category], c->_name.c_str()));
+ else {
+ destItem = srcItem;
+ srcItem.clear();
+ destItem._frame = 0;
+
+ srcItems.sort();
+ destItems.sort();
+ }
+
+ continue;
+ }
+
+ c = newChar;
+ startingChar = newChar;
+ intf.highlightChar(_buttonValue);
+ }
+ }
+ break;
+
+ case Common::KEYCODE_1:
+ case Common::KEYCODE_2:
+ case Common::KEYCODE_3:
+ case Common::KEYCODE_4:
+ case Common::KEYCODE_5:
+ case Common::KEYCODE_6:
+ case Common::KEYCODE_7:
+ case Common::KEYCODE_8:
+ case Common::KEYCODE_9:
+ // Select an item
+ if (mode == ITEMMODE_3)
+ break;
+
+ _buttonValue -= Common::KEYCODE_1;
+ if (_buttonValue != itemIndex) {
+ // Check whether the new selection has an associated item
+ if (!c->_items[category][_buttonValue].empty()) {
+ itemIndex = _buttonValue;
+ Common::fill(&arr[0], &arr[40], 0);
+ arr[itemIndex] = 15;
+ }
+
+ redrawFlag = REDRAW_TEXT;
+ }
+ break;
+
+ case Common::KEYCODE_a:
+ // Armor category
+ category = CATEGORY_ARMOR;
+ redrawFlag = REDRAW_FULL;
+ break;
+
+ case Common::KEYCODE_b:
+ // Buy
+ if (mode != ITEMMODE_CHAR_INFO && mode != ITEMMODE_RECHARGE &&
+ mode != ITEMMODE_ENCHANT && mode != ITEMMODE_TO_GOLD) {
+ mode = ITEMMODE_BLACKSMITH;
+ c = &_itemsCharacter;
+ redrawFlag = REDRAW_FULL;
+ }
+ break;
+
+ case Common::KEYCODE_c:
+ // Accessories category
+ category = CATEGORY_ACCESSORY;
+ redrawFlag = REDRAW_FULL;
+ break;
+
+ case Common::KEYCODE_d:
+ if (mode == ITEMMODE_CHAR_INFO)
+ actionIndex = 3;
+ break;
+
+ case Common::KEYCODE_e:
+ if (mode == ITEMMODE_CHAR_INFO || mode == ITEMMODE_ENCHANT ||
+ mode == ITEMMODE_TO_GOLD) {
+ if (category != CATEGORY_MISC) {
+ actionIndex = mode == ITEMMODE_ENCHANT ? 4 : 0;
+ }
+ }
+ break;
+
+ case Common::KEYCODE_f:
+ if (mode != ITEMMODE_CHAR_INFO && mode != ITEMMODE_RECHARGE &&
+ mode != ITEMMODE_ENCHANT && mode != ITEMMODE_TO_GOLD) {
+ mode = ITEMMODE_REPAIR;
+ c = startingChar;
+ redrawFlag = REDRAW_TEXT;
+ }
+ break;
+
+ case Common::KEYCODE_g:
+ if (mode == ITEMMODE_TO_GOLD)
+ actionIndex = 6;
+ break;
+
+ case Common::KEYCODE_i:
+ if (mode != ITEMMODE_CHAR_INFO && mode != ITEMMODE_RECHARGE &&
+ mode != ITEMMODE_ENCHANT && mode != ITEMMODE_TO_GOLD) {
+ mode = ITEMMODE_IDENTIFY;
+ c = startingChar;
+ redrawFlag = REDRAW_TEXT;
+ }
+ break;
+
+ case Common::KEYCODE_n:
+ // Misc category
+ category = CATEGORY_MISC;
+ redrawFlag = REDRAW_TEXT;
+ break;
+
+ case Common::KEYCODE_q:
+ if (mode == ITEMMODE_CHAR_INFO) {
+ Quests::show(_vm);
+ redrawFlag = REDRAW_TEXT;
+ }
+ break;
+
+ case Common::KEYCODE_r:
+ if (mode == ITEMMODE_CHAR_INFO)
+ actionIndex = 1;
+ else if (mode == ITEMMODE_RECHARGE)
+ actionIndex = 5;
+ break;
+
+ case Common::KEYCODE_s:
+ if (mode != ITEMMODE_CHAR_INFO && mode != ITEMMODE_RECHARGE &&
+ mode != ITEMMODE_ENCHANT && mode != ITEMMODE_TO_GOLD) {
+ mode = ITEMMODE_2;
+ c = startingChar;
+ redrawFlag = REDRAW_TEXT;
+ }
+ break;
+
+ case Common::KEYCODE_u:
+ if (mode == ITEMMODE_CHAR_INFO && category == CATEGORY_MISC)
+ actionIndex = 2;
+ break;
+
+ case Common::KEYCODE_w:
+ // Weapons category
+ category = CATEGORY_WEAPON;
+ redrawFlag = REDRAW_TEXT;
+ break;
+ }
+ }
+
+ intf.drawParty(true);
+ if (updateStock)
+ charData2BlackData();
+
+ return c;
+}
+
+void ItemsDialog::loadButtons(ItemsMode mode, Character *&c) {
+ _iconSprites.load(Common::String::format("%s.icn",
+ (mode == ITEMMODE_CHAR_INFO) ? "items" : "buy"));
+ _equipSprites.load("equip.icn");
+
+ if (mode == ITEMMODE_ENCHANT || mode == ITEMMODE_RECHARGE || mode == ITEMMODE_TO_GOLD) {
+ // Enchant button list
+ addButton(Common::Rect(12, 109, 36, 129), Common::KEYCODE_w, &_iconSprites);
+ addButton(Common::Rect(46, 109, 70, 129), Common::KEYCODE_a, &_iconSprites);
+ addButton(Common::Rect(80, 109, 104, 129), Common::KEYCODE_c, &_iconSprites);
+ addButton(Common::Rect(114, 109, 138, 129), Common::KEYCODE_n, &_iconSprites);
+ addButton(Common::Rect(148, 109, 172, 129), Common::KEYCODE_e, &_iconSprites);
+ addButton(Common::Rect(284, 109, 308, 129), Common::KEYCODE_ESCAPE, &_iconSprites);
+ addButton(Common::Rect(148, 109, 172, 129), Common::KEYCODE_u, &_iconSprites);
+ addButton(Common::Rect(8, 20, 263, 28), Common::KEYCODE_1);
+ addButton(Common::Rect(8, 29, 263, 37), Common::KEYCODE_2);
+ addButton(Common::Rect(8, 38, 263, 46), Common::KEYCODE_3);
+ addButton(Common::Rect(8, 47, 263, 55), Common::KEYCODE_4);
+ addButton(Common::Rect(8, 56, 263, 64), Common::KEYCODE_5);
+ addButton(Common::Rect(8, 65, 263, 73), Common::KEYCODE_6);
+ addButton(Common::Rect(8, 74, 263, 82), Common::KEYCODE_7);
+ addButton(Common::Rect(8, 83, 263, 91), Common::KEYCODE_8);
+ addButton(Common::Rect(8, 92, 263, 100), Common::KEYCODE_9);
+ } else {
+ addButton(Common::Rect(12, 109, 36, 129), Common::KEYCODE_w, &_iconSprites);
+ addButton(Common::Rect(46, 109, 70, 129), Common::KEYCODE_a, &_iconSprites);
+ addButton(Common::Rect(80, 109, 104, 129), Common::KEYCODE_c, &_iconSprites);
+ addButton(Common::Rect(114, 109, 138, 129), Common::KEYCODE_n, &_iconSprites);
+ addButton(Common::Rect(148, 109, 172, 129), Common::KEYCODE_e, &_iconSprites);
+ addButton(Common::Rect(182, 109, 206, 129), Common::KEYCODE_r, &_iconSprites);
+ addButton(Common::Rect(216, 109, 240, 129), Common::KEYCODE_d, &_iconSprites);
+ addButton(Common::Rect(250, 109, 274, 129), Common::KEYCODE_q, &_iconSprites);
+ addButton(Common::Rect(284, 109, 308, 129), Common::KEYCODE_ESCAPE, &_iconSprites);
+ addButton(Common::Rect(8, 20, 263, 28), Common::KEYCODE_1);
+ addButton(Common::Rect(8, 29, 263, 37), Common::KEYCODE_2);
+ addButton(Common::Rect(8, 38, 263, 46), Common::KEYCODE_3);
+ addButton(Common::Rect(8, 47, 263, 55), Common::KEYCODE_4);
+ addButton(Common::Rect(8, 56, 263, 64), Common::KEYCODE_5);
+ addButton(Common::Rect(8, 65, 263, 73), Common::KEYCODE_6);
+ addButton(Common::Rect(8, 74, 263, 82), Common::KEYCODE_7);
+ addButton(Common::Rect(8, 83, 263, 91), Common::KEYCODE_8);
+ addButton(Common::Rect(8, 92, 263, 100), Common::KEYCODE_9);
+ addPartyButtons(_vm);
+ }
+
+ if (mode == ITEMMODE_BLACKSMITH) {
+ _oldCharacter = c;
+ c = &_itemsCharacter;
+ blackData2CharData();
+
+ _buttons[4]._value = Common::KEYCODE_b;
+ _buttons[5]._value = Common::KEYCODE_s;
+ _buttons[6]._value = Common::KEYCODE_i;
+ _buttons[7]._value = Common::KEYCODE_f;
+
+ setEquipmentIcons();
+ } else {
+ _buttons[4]._value = Common::KEYCODE_e;
+ _buttons[5]._value = Common::KEYCODE_r;
+ _buttons[6]._value = Common::KEYCODE_d;
+ _buttons[7]._value = Common::KEYCODE_q;
+ }
+}
+
+void ItemsDialog::blackData2CharData() {
+ Party &party = *_vm->_party;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ int slotIndex = 0;
+ while (party._mazeId != (int)BLACKSMITH_MAP_IDS[isDarkCc][slotIndex] && slotIndex < 4)
+ ++slotIndex;
+ if (slotIndex == 4)
+ slotIndex = 0;
+
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ _itemsCharacter._weapons[idx] = party._blacksmithWeapons[isDarkCc][idx];
+ _itemsCharacter._armor[idx] = party._blacksmithArmor[isDarkCc][idx];
+ _itemsCharacter._accessories[idx] = party._blacksmithAccessories[isDarkCc][idx];
+ _itemsCharacter._misc[idx] = party._blacksmithMisc[isDarkCc][idx];
+ }
+}
+
+void ItemsDialog::charData2BlackData() {
+ Party &party = *_vm->_party;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ int slotIndex = 0;
+ while (party._mazeId != (int)BLACKSMITH_MAP_IDS[isDarkCc][slotIndex] && slotIndex < 4)
+ ++slotIndex;
+ if (slotIndex == 4)
+ slotIndex = 0;
+
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ party._blacksmithWeapons[isDarkCc][idx] = _itemsCharacter._weapons[idx];
+ party._blacksmithArmor[isDarkCc][idx] = _itemsCharacter._armor[idx];
+ party._blacksmithAccessories[isDarkCc][idx] = _itemsCharacter._accessories[idx];
+ party._blacksmithMisc[isDarkCc][idx] = _itemsCharacter._misc[idx];
+ }
+}
+
+void ItemsDialog::setEquipmentIcons() {
+ for (int typeIndex = 0; typeIndex < 4; ++typeIndex) {
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ switch (typeIndex) {
+ case 0: {
+ XeenItem &i = _itemsCharacter._weapons[idx];
+ if (i._id <= 17)
+ i._frame = 1;
+ else if (i._id <= 29 || i._id > 33)
+ i._frame = 13;
+ else
+ i._frame = 4;
+ break;
+ }
+
+ case 1: {
+ XeenItem &i = _itemsCharacter._armor[idx];
+ if (i._id <= 7)
+ i._frame = 3;
+ else if (i._id == 9)
+ i._frame = 5;
+ else if (i._id == 10)
+ i._frame = 9;
+ else if (i._id <= 12)
+ i._frame = 10;
+ else
+ i._frame = 6;
+ break;
+ }
+
+ case 2: {
+ XeenItem &i = _itemsCharacter._accessories[idx];
+ if (i._id == 1)
+ i._id = 8;
+ else if (i._id == 2)
+ i._frame = 12;
+ else if (i._id <= 7)
+ i._frame = 7;
+ else
+ i._frame = 11;
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+}
+
+int ItemsDialog::calcItemCost(Character *c, int itemIndex, ItemsMode mode,
+ int skillLevel, ItemCategory category) {
+ int amount1 = 0, amount2 = 0, amount3 = 0, amount4 = 0;
+ int result = 0;
+ int level = skillLevel & 0x7f;
+
+ switch (mode) {
+ case ITEMMODE_BLACKSMITH:
+ level = 0;
+ break;
+ case ITEMMODE_2:
+ case ITEMMODE_TO_GOLD:
+ level = level == 0 ? 1 : 0;
+ break;
+ case ITEMMODE_IDENTIFY:
+ level = 2;
+ break;
+ case ITEMMODE_REPAIR:
+ level = 3;
+ break;
+ default:
+ break;
+ }
+
+ switch (category) {
+ case CATEGORY_WEAPON:
+ case CATEGORY_ARMOR:
+ case CATEGORY_ACCESSORY: {
+ // 0=Weapons, 1=Armor, 2=Accessories
+ XeenItem &i = (mode == 0) ? c->_weapons[itemIndex] :
+ (mode == 1 ? c->_armor[itemIndex] : c->_accessories[itemIndex]);
+ amount1 = (mode == 0) ? WEAPON_BASE_COSTS[i._id] :
+ (mode == 1 ? ARMOR_BASE_COSTS[i._id] : ACCESSORY_BASE_COSTS[i._id]);
+
+ if (i._material > 36 && i._material < 59) {
+ switch (i._material) {
+ case 37:
+ amount1 /= 10;
+ break;
+ case 38:
+ amount1 /= 4;
+ break;
+ case 39:
+ amount1 /= 2;
+ break;
+ case 40:
+ amount1 /= 4;
+ break;
+ default:
+ amount1 *= METAL_BASE_MULTIPLIERS[i._material - 37];
+ break;
+ }
+ }
+
+ if (i._material < 37)
+ amount2 = ELEMENTAL_DAMAGE[i._material] * 100;
+ else if (i._material > 58)
+ amount3 = METAL_BASE_MULTIPLIERS[i._material] * 100;
+
+ switch (mode) {
+ case ITEMMODE_BLACKSMITH:
+ case ITEMMODE_2:
+ case ITEMMODE_REPAIR:
+ case ITEMMODE_IDENTIFY:
+ case ITEMMODE_TO_GOLD:
+ result = (amount1 + amount2 + amount3 + amount4) / ITEM_SKILL_DIVISORS[level];
+ if (!result)
+ result = 1;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+
+ case 3: {
+ // Misc
+ XeenItem &i = c->_misc[itemIndex];
+ amount1 = MISC_MATERIAL_COSTS[i._material];
+ amount4 = MISC_BASE_COSTS[i._id];
+
+ switch (mode) {
+ case ITEMMODE_BLACKSMITH:
+ case ITEMMODE_2:
+ case ITEMMODE_REPAIR:
+ case ITEMMODE_IDENTIFY:
+ case ITEMMODE_TO_GOLD:
+ result = (amount1 + amount2 + amount3 + amount4) / ITEM_SKILL_DIVISORS[level];
+ if (!result)
+ result = 1;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return (mode == ITEMMODE_CHAR_INFO) ? 0 : result;
+}
+
+int ItemsDialog::doItemOptions(Character &c, int actionIndex, int itemIndex, ItemCategory category,
+ ItemsMode mode) {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Spells &spells = *_vm->_spells;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ XeenItem *itemCategories[4] = { &c._weapons[0], &c._armor[0], &c._accessories[0], &c._misc[0] };
+ XeenItem *items = itemCategories[category];
+ if (!items[0]._id)
+ // Inventory is empty
+ return category == CATEGORY_MISC ? 0 : 2;
+
+ Window &w = screen._windows[11];
+ SpriteResource escSprites;
+ if (itemIndex < 0 || itemIndex > 8) {
+ saveButtons();
+
+ escSprites.load("esc.icn");
+ addButton(Common::Rect(235, 111, 259, 131), Common::KEYCODE_ESCAPE, &escSprites);
+ addButton(Common::Rect(8, 20, 263, 28), Common::KEYCODE_1);
+ addButton(Common::Rect(8, 29, 263, 37), Common::KEYCODE_2);
+ addButton(Common::Rect(8, 38, 263, 46), Common::KEYCODE_3);
+ addButton(Common::Rect(8, 47, 263, 55), Common::KEYCODE_4);
+ addButton(Common::Rect(8, 56, 263, 64), Common::KEYCODE_5);
+ addButton(Common::Rect(8, 65, 263, 73), Common::KEYCODE_6);
+ addButton(Common::Rect(8, 74, 263, 82), Common::KEYCODE_7);
+ addButton(Common::Rect(8, 83, 263, 91), Common::KEYCODE_8);
+ addButton(Common::Rect(8, 92, 263, 100), Common::KEYCODE_9);
+
+ w.open();
+ w.writeString(Common::String::format(WHICH_ITEM, ITEM_ACTIONS[actionIndex]));
+ _iconSprites.draw(screen, 0, Common::Point(235, 111));
+ w.update();
+
+ while (!_vm->shouldQuit()) {
+ while (!_buttonValue) {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ if (_vm->shouldQuit())
+ return false;
+ }
+
+ if (_buttonValue == Common::KEYCODE_ESCAPE) {
+ itemIndex = -1;
+ break;
+ } else if (_buttonValue >= Common::KEYCODE_1 && _buttonValue <= Common::KEYCODE_9) {
+ // Check whether there's an item at the selected index
+ int selectedIndex = _buttonValue - Common::KEYCODE_1;
+ if (!items[selectedIndex]._id)
+ continue;
+
+ itemIndex = selectedIndex;
+ break;
+ }
+ }
+
+ w.close();
+ restoreButtons();
+ }
+
+ if (itemIndex != -1) {
+ XeenItem &item = c._items[category][itemIndex];
+
+ switch (mode) {
+ case ITEMMODE_CHAR_INFO:
+ case ITEMMODE_8:
+ switch (actionIndex) {
+ case 0:
+ c._items[category].equipItem(itemIndex);
+ break;
+ case 1:
+ c._items[category].removeItem(itemIndex);
+ break;
+ case 2:
+ if (!party._mazeId) {
+ ErrorScroll::show(_vm, WHATS_YOUR_HURRY);
+ } else {
+ XeenItem &i = c._misc[itemIndex];
+
+ Condition condition = c.worstCondition();
+ switch (condition) {
+ case ASLEEP:
+ case PARALYZED:
+ case UNCONSCIOUS:
+ case DEAD:
+ case STONED:
+ case ERADICATED:
+ ErrorScroll::show(_vm, Common::String::format(IN_NO_CONDITION, c._name.c_str()));
+ break;
+ default:
+ if (combat._itemFlag) {
+ ErrorScroll::show(_vm, USE_ITEM_IN_COMBAT);
+ } else if (i._id && (i._bonusFlags & ITEMFLAG_BONUS_MASK)
+ && !(i._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED))) {
+ int charges = (i._bonusFlags & ITEMFLAG_BONUS_MASK) - 1;
+ i._bonusFlags = charges;
+ _oldCharacter = &c;
+
+ screen._windows[30].close();
+ screen._windows[29].close();
+ screen._windows[24].close();
+ spells.castItemSpell(i._id);
+
+ if (!charges) {
+ // Ran out of charges, so make item disappear
+ c._items[category][itemIndex].clear();
+ c._items[category].sort();
+ }
+ } else {
+ ErrorScroll::show(_vm, Common::String::format(NO_SPECIAL_ABILITIES,
+ c._items[category].getFullDescription(itemIndex).c_str()
+ ));
+ }
+ }
+ }
+ break;
+ case 3:
+ c._items[category].discardItem(itemIndex);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case ITEMMODE_BLACKSMITH: {
+ InventoryItems &invItems = _oldCharacter->_items[category];
+ if (invItems[INV_ITEMS_TOTAL - 1]._id) {
+ // If the last slot is in use, it means the list is full
+ ErrorScroll::show(_vm, Common::String::format(BACKPACK_IS_FULL,
+ _oldCharacter->_name.c_str()));
+ } else {
+ int cost = calcItemCost(_oldCharacter, itemIndex, mode, 0, category);
+ Common::String desc = c._items[category].getFullDescription(itemIndex);
+ if (Confirm::show(_vm, Common::String::format(BUY_X_FOR_Y_GOLD,
+ desc.c_str(), cost))) {
+ if (party.subtract(0, cost, 0, WT_FREEZE_WAIT)) {
+ if (isDarkCc) {
+ sound.playSample(0, 0);
+ File f("choice2.voc");
+ sound.playSample(&f, 0);
+ }
+
+ // Add entry to the end of the list
+ _oldCharacter->_items[category][8] = c._items[category][itemIndex];
+ _oldCharacter->_items[category][8]._frame = 0;
+ c._items[category].clear();
+ c._items[category].sort();
+ _oldCharacter->_items[category].sort();
+ }
+ }
+ }
+ return 0;
+ }
+
+ case ITEMMODE_2: {
+ bool noNeed;
+ switch (category) {
+ case CATEGORY_WEAPON:
+ noNeed = (item._bonusFlags & ITEMFLAG_CURSED) || item._id == 34;
+ break;
+ default:
+ noNeed = item._bonusFlags & ITEMFLAG_CURSED;
+ break;
+ }
+
+ if (noNeed) {
+ ErrorScroll::show(_vm, Common::String::format(NO_NEED_OF_THIS,
+ c._items[category].getFullDescription(itemIndex).c_str()));
+ } else {
+ int cost = calcItemCost(&c, itemIndex, mode, c._skills[MERCHANT], category);
+ Common::String desc = c._items[category].getFullDescription(itemIndex);
+ Common::String msg = Common::String::format(SELL_X_FOR_Y_GOLD,
+ desc.c_str(), cost);
+
+ if (Confirm::show(_vm, msg)) {
+ // Remove the sold item and add gold to the party's total
+ item.clear();
+ c._items[category].sort();
+
+ party._gold += cost;
+ }
+ }
+ return 0;
+ }
+
+ case ITEMMODE_RECHARGE:
+ if (category != CATEGORY_MISC || c._misc[itemIndex]._material > 9
+ || c._misc[itemIndex]._id == 53 || c._misc[itemIndex]._id == 0) {
+ sound.playFX(21);
+ ErrorScroll::show(_vm, Common::String::format(NOT_RECHARGABLE, SPELL_FAILED));
+ } else {
+ int charges = MIN(63, _vm->getRandomNumber(1, 6) +
+ (c._misc[itemIndex]._bonusFlags & ITEMFLAG_BONUS_MASK));
+ sound.playFX(20);
+
+ c._misc[itemIndex]._bonusFlags = (c._misc[itemIndex]._bonusFlags
+ & ~ITEMFLAG_BONUS_MASK) | charges;
+ }
+ return 2;
+
+ case ITEMMODE_ENCHANT: {
+ int amount = _vm->getRandomNumber(1, _oldCharacter->getCurrentLevel() / 5 + 1);
+ amount = MIN(amount, 5);
+ _oldCharacter->_items[category].enchantItem(itemIndex, amount);
+ break;
+ }
+
+ case ITEMMODE_REPAIR:
+ if (!(item._bonusFlags & ITEMFLAG_BROKEN)) {
+ ErrorScroll::show(_vm, ITEM_NOT_BROKEN);
+ } else {
+ int cost = calcItemCost(&c, itemIndex, mode, actionIndex, category);
+ Common::String msg = Common::String::format(FIX_IDENTIFY_GOLD,
+ FIX_IDENTIFY[0],
+ c._items[category].getFullDescription(itemIndex).c_str(),
+ cost);
+
+ if (Confirm::show(_vm, msg) && party.subtract(0, cost, 0)) {
+ item._bonusFlags &= ~ITEMFLAG_BROKEN;
+ }
+ }
+ break;
+
+ case ITEMMODE_IDENTIFY: {
+ int cost = calcItemCost(&c, itemIndex, mode, actionIndex, category);
+ Common::String msg = Common::String::format(FIX_IDENTIFY_GOLD,
+ FIX_IDENTIFY[1],
+ c._items[category].getFullDescription(itemIndex).c_str(),
+ cost);
+
+ if (Confirm::show(_vm, msg) && party.subtract(0, cost, 0)) {
+ Common::String details = c._items[category].getIdentifiedDetails(itemIndex);
+ Common::String desc = c._items[category].getFullDescription(itemIndex);
+ Common::String str = Common::String::format(IDENTIFY_ITEM_MSG,
+ desc.c_str(), details.c_str());
+
+ Window &win = screen._windows[14];
+ win.open();
+ win.writeString(str);
+ win.update();
+
+ saveButtons();
+ clearButtons();
+
+ while (!_vm->shouldQuit() && !events.isKeyMousePressed())
+ events.pollEventsAndWait();
+ events.clearEvents();
+
+ restoreButtons();
+ win.close();
+ }
+ break;
+ }
+
+ case ITEMMODE_TO_GOLD:
+ // Convert item in inventory to gold
+ itemToGold(c, itemIndex, category, mode);
+ return 2;
+
+ default:
+ break;
+ }
+ }
+
+ intf._charsShooting = false;
+ combat.moveMonsters();
+ combat._whosTurn = -1;
+ return true;
+}
+
+void ItemsDialog::itemToGold(Character &c, int itemIndex, ItemCategory category,
+ ItemsMode mode) {
+ XeenItem &item = c._items[category][itemIndex];
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ if (category == CATEGORY_WEAPON && item._id == 34) {
+ sound.playFX(21);
+ ErrorScroll::show(_vm, Common::String::format("\v012\t000\x03c%s",
+ SPELL_FAILED));
+ } else if (item._id != 0) {
+ // There is a valid item present
+ // Calculate cost of item and add it to the party's total
+ int cost = calcItemCost(&c, itemIndex, mode, 1, category);
+ party._gold += cost;
+
+ // Remove the item from the inventory
+ item.clear();
+ c._items[category].sort();
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_items.h b/engines/xeen/dialogs_items.h
new file mode 100644
index 0000000000..6069ca823b
--- /dev/null
+++ b/engines/xeen/dialogs_items.h
@@ -0,0 +1,91 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_ITEMS_H
+#define XEEN_DIALOGS_ITEMS_H
+
+#include "xeen/dialogs.h"
+#include "xeen/party.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+enum ItemsMode {
+ ITEMMODE_CHAR_INFO = 0, ITEMMODE_BLACKSMITH = 1, ITEMMODE_2 = 2, ITEMMODE_3 = 3,
+ ITEMMODE_RECHARGE = 4, ITEMMODE_5 = 5, ITEMMODE_ENCHANT = 6, ITEMMODE_COMBAT = 7, ITEMMODE_8 = 8,
+ ITEMMODE_REPAIR = 9, ITEMMODE_IDENTIFY = 10, ITEMMODE_TO_GOLD = 11
+};
+
+class ItemsDialog : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+ SpriteResource _equipSprites;
+ Character _itemsCharacter;
+ Character *_oldCharacter;
+ DrawStruct _itemsDrawList[INV_ITEMS_TOTAL];
+
+ ItemsDialog(XeenEngine *vm) : ButtonContainer(),
+ _vm(vm), _oldCharacter(nullptr) {}
+
+ Character *execute(Character *c, ItemsMode mode);
+
+ /**
+ * Load the buttons for the dialog
+ */
+ void loadButtons(ItemsMode mode, Character *&c);
+
+ /**
+ * Loads the temporary _itemsCharacter character with the item set
+ * the given blacksmith has available, so the user can "view" the
+ * set as if it were a standard character's inventory
+ */
+ void blackData2CharData();
+
+ /**
+ * Saves the inventory from the temporary _itemsCharacter character back into the
+ * blacksmith storage, so changes in blacksmith inventory remain persistent
+ */
+ void charData2BlackData();
+
+ /**
+ * Sets the equipment icon to use for each item for display
+ */
+ void setEquipmentIcons();
+
+ /**
+ * Calculate the cost of an item
+ */
+ int calcItemCost(Character *c, int itemIndex, ItemsMode mode, int skillLevel,
+ ItemCategory category);
+
+ int doItemOptions(Character &c, int actionIndex, int itemIndex,
+ ItemCategory category, ItemsMode mode);
+
+ void itemToGold(Character &c, int itemIndex, ItemCategory category, ItemsMode mode);
+public:
+ static Character *show(XeenEngine *vm, Character *c, ItemsMode mode);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_ITEMS_H */
diff --git a/engines/xeen/dialogs_options.cpp b/engines/xeen/dialogs_options.cpp
new file mode 100644
index 0000000000..757634a2ad
--- /dev/null
+++ b/engines/xeen/dialogs_options.cpp
@@ -0,0 +1,232 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "xeen/dialogs_options.h"
+#include "xeen/resources.h"
+
+namespace Xeen {
+
+void OptionsMenu::show(XeenEngine *vm) {
+ OptionsMenu *menu;
+
+ switch (vm->getGameID()) {
+ case GType_Clouds:
+ menu = new CloudsOptionsMenu(vm);
+ break;
+ case GType_DarkSide:
+ menu = new DarkSideOptionsMenu(vm);
+ break;
+ case GType_WorldOfXeen:
+ menu = new WorldOptionsMenu(vm);
+ break;
+ default:
+ error("Unsupported game");
+ break;
+ }
+
+ menu->execute();
+ delete menu;
+}
+
+void OptionsMenu::execute() {
+ SpriteResource special("special.icn");
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+
+ File newBright("newbrigh.m");
+ _vm->_sound->playSong(newBright);
+
+ screen._windows[GAME_WINDOW].setBounds(Common::Rect(72, 25, 248, 175));
+
+ Common::String title1, title2;
+ startup(title1, title2);
+ SpriteResource title1Sprites(title1), title2Sprites(title2);
+
+ bool firstTime = true, doFade = true;
+ while (!_vm->shouldQuit()) {
+ setBackground(doFade);
+ events.setCursor(0);
+
+ if (firstTime) {
+ firstTime = false;
+ warning("TODO: Read existing save file");
+ }
+
+ showTitles1(title1Sprites);
+ showTitles2();
+
+ clearButtons();
+ setupButtons(&title2Sprites);
+ openWindow();
+
+ while (!_vm->shouldQuit()) {
+ // Show the dialog with a continually animating background
+ while (!_vm->shouldQuit() && !_buttonValue)
+ showContents(title1Sprites, true);
+ if (_vm->shouldQuit())
+ return;
+
+ // Handle keypress
+ int key = toupper(_buttonValue);
+ _buttonValue = 0;
+
+ if (key == 'C' || key == 'V') {
+ // Show credits
+ CreditsScreen::show(_vm);
+ break;
+ } else if (key == 27) {
+ // Hide the options menu
+ break;
+ }
+ }
+ }
+}
+
+void OptionsMenu::showTitles1(SpriteResource &sprites) {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+
+ int frameNum = 0;
+ while (!_vm->shouldQuit() && !events.isKeyMousePressed()) {
+ events.updateGameCounter();
+
+ frameNum = (frameNum + 1) % (_vm->getGameID() == GType_WorldOfXeen ? 5 : 10);
+ screen.restoreBackground();
+ sprites.draw(screen, frameNum);
+
+ events.wait(4, true);
+ }
+}
+
+void OptionsMenu::showTitles2() {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ SoundManager &sound = *_vm->_sound;
+
+ File voc("elect.voc");
+ SpriteResource titleSprites("title2b.raw");
+ SpriteResource kludgeSprites("kludge.int");
+ SpriteResource title2Sprites[8] = {
+ SpriteResource("title2b.int"), SpriteResource("title2c.int"),
+ SpriteResource("title2d.int"), SpriteResource("title2e.int"),
+ SpriteResource("title2f.int"), SpriteResource("title2g.int"),
+ SpriteResource("title2h.int"), SpriteResource("title2i.int"),
+ };
+
+ kludgeSprites.draw(screen, 0);
+ screen.saveBackground();
+ sound.playSample(&voc, 0);
+
+ for (int i = 0; i < 30 && !_vm->shouldQuit(); ++i) {
+ events.updateGameCounter();
+ screen.restoreBackground();
+ title2Sprites[i / 4].draw(screen, i % 4);
+ screen._windows[0].update();
+
+ if (i == 19)
+ sound.playSample(nullptr, 0);
+
+ while (!_vm->shouldQuit() && events.timeElapsed() < 2)
+ events.pollEventsAndWait();
+ }
+
+ screen.restoreBackground();
+ screen._windows[0].update();
+}
+
+void OptionsMenu::setupButtons(SpriteResource *buttons) {
+ addButton(Common::Rect(124, 87, 124 + 53, 87 + 10), 'S');
+ addButton(Common::Rect(126, 98, 126 + 47, 98 + 10), 'L');
+ addButton(Common::Rect(91, 110, 91 + 118, 110 + 10), 'C');
+ addButton(Common::Rect(85, 121, 85 + 131, 121 + 10), 'O');
+}
+
+void WorldOptionsMenu::setupButtons(SpriteResource *buttons) {
+ addButton(Common::Rect(93, 53, 93 + 134, 53 + 20), 'S', buttons);
+ addButton(Common::Rect(93, 78, 93 + 134, 78 + 20), 'L', buttons);
+ addButton(Common::Rect(93, 103, 93 + 134, 103 + 20), 'C', buttons);
+ addButton(Common::Rect(93, 128, 93 + 134, 128 + 20), 'O', buttons);
+}
+
+/*------------------------------------------------------------------------*/
+
+void CloudsOptionsMenu::startup(Common::String &title1, Common::String &title2) {
+ title1 = "title1.int";
+ title2 = "title1a.int";
+}
+
+/*------------------------------------------------------------------------*/
+
+void DarkSideOptionsMenu::startup(Common::String &title1, Common::String &title2) {
+ title1 = "title2.int";
+ title2 = "title2a.int";
+}
+
+void WorldOptionsMenu::startup(Common::String &title1, Common::String &title2) {
+ title1 = "world.int";
+ title2 = "start.icn";
+
+ Screen &screen = *_vm->_screen;
+ screen.fadeOut(4);
+ screen.loadPalette("dark.pal");
+ _vm->_events->clearEvents();
+}
+
+void WorldOptionsMenu::setBackground(bool doFade) {
+ Screen &screen = *_vm->_screen;
+ screen.loadBackground("world.raw");
+ screen.saveBackground();
+
+ if (doFade)
+ screen.fadeIn(4);
+}
+
+void WorldOptionsMenu::openWindow() {
+ _vm->_screen->_windows[GAME_WINDOW].open();
+}
+
+void WorldOptionsMenu::showContents(SpriteResource &title1, bool waitFlag) {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ events.updateGameCounter();
+
+ // Draw the background frame in a continous cycle
+ _bgFrame = (_bgFrame + 1) % 5;
+ title1.draw(screen._windows[0], _bgFrame);
+
+ // Draw the basic frame for the optitons menu and title text
+ screen._windows[GAME_WINDOW].frame();
+ screen._windows[GAME_WINDOW].writeString(OPTIONS_TITLE);
+
+ drawButtons(&screen._windows[0]);
+
+ if (waitFlag) {
+ screen._windows[0].update();
+
+ while (!_vm->shouldQuit() && !_buttonValue && events.timeElapsed() < 3) {
+ checkEvents(_vm);
+ }
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_options.h b/engines/xeen/dialogs_options.h
new file mode 100644
index 0000000000..bb4aea9a36
--- /dev/null
+++ b/engines/xeen/dialogs_options.h
@@ -0,0 +1,95 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_OPTIONS_H
+#define XEEN_DIALOGS_OPTIONS_H
+
+#include "xeen/xeen.h"
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class OptionsMenu : public SettingsBaseDialog {
+private:
+ void execute();
+protected:
+ OptionsMenu(XeenEngine *vm) : SettingsBaseDialog(vm) {}
+protected:
+ virtual void startup(Common::String &title1, Common::String &title2) = 0;
+
+ virtual void setBackground(bool doFade) {}
+
+ virtual void showTitles1(SpriteResource &sprites);
+
+ virtual void showTitles2();
+
+ virtual void setupButtons(SpriteResource *buttons);
+
+ virtual void openWindow() {}
+public:
+ virtual ~OptionsMenu() {}
+
+ static void show(XeenEngine *vm);
+};
+
+class CloudsOptionsMenu : public OptionsMenu {
+protected:
+ virtual void startup(Common::String &title1, Common::String &title2);
+public:
+ CloudsOptionsMenu(XeenEngine *vm) : OptionsMenu(vm) {}
+
+ virtual ~CloudsOptionsMenu() {}
+};
+
+class DarkSideOptionsMenu : public OptionsMenu {
+protected:
+ virtual void startup(Common::String &title1, Common::String &title2);
+public:
+ DarkSideOptionsMenu(XeenEngine *vm) : OptionsMenu(vm) {}
+
+ virtual ~DarkSideOptionsMenu() {}
+};
+
+class WorldOptionsMenu : public DarkSideOptionsMenu {
+private:
+ int _bgFrame;
+protected:
+ virtual void startup(Common::String &title1, Common::String &title2);
+
+ virtual void setBackground(bool doFade);
+
+ virtual void showTitles2() {}
+
+ virtual void setupButtons(SpriteResource *buttons);
+
+ virtual void openWindow();
+
+ virtual void showContents(SpriteResource &title1, bool mode);
+public:
+ WorldOptionsMenu(XeenEngine *vm) : DarkSideOptionsMenu(vm), _bgFrame(0) {}
+
+ virtual ~WorldOptionsMenu() {}
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_H */
diff --git a/engines/xeen/dialogs_party.cpp b/engines/xeen/dialogs_party.cpp
new file mode 100644
index 0000000000..a03e2d763d
--- /dev/null
+++ b/engines/xeen/dialogs_party.cpp
@@ -0,0 +1,1045 @@
+/* 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.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "xeen/dialogs_char_info.h"
+#include "xeen/dialogs_party.h"
+#include "xeen/dialogs_input.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/character.h"
+#include "xeen/events.h"
+#include "xeen/party.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+PartyDialog::PartyDialog(XeenEngine *vm) : ButtonContainer(),
+ PartyDrawer(vm), _vm(vm) {
+ initDrawStructs();
+}
+
+void PartyDialog::show(XeenEngine *vm) {
+ PartyDialog *dlg = new PartyDialog(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void PartyDialog::execute() {
+ EventsManager &events = *_vm->_events;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ bool modeFlag = false;
+ int startingChar = 0;
+
+ loadButtons();
+ setupBackground();
+
+ while (!_vm->shouldQuit()) {
+ _vm->_mode = MODE_1;
+
+ // Build up a list of available characters in the Roster that are on the
+ // same side of Xeen as the player is currently on
+ _charList.clear();
+ for (int i = 0; i < XEEN_TOTAL_CHARACTERS; ++i) {
+ Character &player = party._roster[i];
+ if (player._name.empty() || player._xeenSide != (map._loadDarkSide ? 1 : 0))
+ continue;
+
+ _charList.push_back(i);
+ }
+
+ Window &w = screen._windows[11];
+ w.open();
+ setupFaces(startingChar, false);
+ w.writeString(Common::String::format(PARTY_DIALOG_TEXT, _partyDetails.c_str()));
+ w.drawList(&_faceDrawStructs[0], 4);
+
+ _uiSprites.draw(w, 0, Common::Point(16, 100));
+ _uiSprites.draw(w, 2, Common::Point(52, 100));
+ _uiSprites.draw(w, 4, Common::Point(87, 100));
+ _uiSprites.draw(w, 6, Common::Point(122, 100));
+ _uiSprites.draw(w, 8, Common::Point(157, 100));
+ _uiSprites.draw(w, 10, Common::Point(192, 100));
+ screen.loadPalette("mm4.pal");
+
+ if (modeFlag) {
+ screen._windows[0].update();
+ events.setCursor(0);
+ screen.fadeIn(4);
+ } else {
+ if (_vm->getGameID() == GType_DarkSide) {
+ screen.fadeOut(4);
+ screen._windows[0].update();
+ }
+
+ doScroll(_vm, false, false);
+ events.setCursor(0);
+
+ if (_vm->getGameID() == GType_DarkSide) {
+ screen.fadeIn(4);
+ }
+ }
+
+ bool breakFlag = false;
+ while (!_vm->shouldQuit() && !breakFlag) {
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ } while (!_vm->shouldQuit() && !_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_ESCAPE:
+ case Common::KEYCODE_SPACE:
+ case Common::KEYCODE_e:
+ case Common::KEYCODE_x:
+ if (party._activeParty.size() == 0) {
+ ErrorScroll::show(_vm, NO_ONE_TO_ADVENTURE_WITH);
+ } else {
+ if (_vm->_mode != MODE_0) {
+ for (int idx = 4; idx >= 0; --idx) {
+ events.updateGameCounter();
+ screen.frameWindow(idx);
+ w.update();
+
+ while (events.timeElapsed() < 1)
+ events.pollEventsAndWait();
+ }
+ }
+
+ w.close();
+ party._mazeId = party._priorMazeId;
+
+ party.copyPartyToRoster();
+ _vm->_saves->writeCharFile();
+ return;
+ }
+ break;
+
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ // Show character info
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size())
+ CharacterInfo::show(_vm, _buttonValue);
+ break;
+
+ case Common::KEYCODE_1:
+ case Common::KEYCODE_2:
+ case Common::KEYCODE_3:
+ case Common::KEYCODE_4:
+ _buttonValue -= Common::KEYCODE_1 - 7;
+ if ((_buttonValue - 7 + startingChar) < (int)_charList.size()) {
+ // Check if the selected character is already in the party
+ uint idx = 0;
+ for (; idx < party._activeParty.size(); ++idx) {
+ if (_charList[_buttonValue - 7 + startingChar] ==
+ party._activeParty[idx]._rosterId)
+ break;
+ }
+
+ // Only add the character if they're not already in the party
+ if (idx == party._activeParty.size()) {
+ if (party._activeParty.size() == MAX_ACTIVE_PARTY) {
+ sound.playFX(21);
+ ErrorScroll::show(_vm, YOUR_PARTY_IS_FULL);
+ } else {
+ // Add the character to the active party
+ party._activeParty.push_back(party._roster[
+ _charList[_buttonValue - 7 + startingChar]]);
+ startingCharChanged(startingChar);
+ }
+ }
+ }
+ break;
+
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ // Up arrow
+ if (startingChar > 0) {
+ startingChar -= 4;
+ startingCharChanged(startingChar);
+ }
+ break;
+
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ // Down arrow
+ if (startingChar < ((int)_charList.size() - 4)) {
+ startingChar += 4;
+ startingCharChanged(startingChar);
+ }
+ break;
+
+ case Common::KEYCODE_c:
+ // Create
+ if (_charList.size() == XEEN_TOTAL_CHARACTERS) {
+ ErrorScroll::show(_vm, YOUR_ROSTER_IS_FULL);
+ } else {
+ screen.fadeOut(4);
+ w.close();
+
+ createChar();
+
+ party.copyPartyToRoster();
+ _vm->_saves->writeCharFile();
+ screen.fadeOut(4);
+ modeFlag = true;
+ breakFlag = true;
+ }
+ break;
+
+ case Common::KEYCODE_d:
+ // Delete character
+ if (_charList.size() > 0) {
+ int charButtonValue = selectCharacter(true, startingChar);
+ if (charButtonValue != 0) {
+ int charIndex = charButtonValue - Common::KEYCODE_1 + startingChar;
+ Character &c = party._roster[_charList[charIndex]];
+ if (c.hasSpecialItem()) {
+ ErrorScroll::show(_vm, HAS_SLAYER_SWORD);
+ } else {
+ Common::String msg = Common::String::format(SURE_TO_DELETE_CHAR,
+ c._name.c_str(), CLASS_NAMES[c._class]);
+ if (Confirm::show(_vm, msg)) {
+ // If the character is in the party, remove it
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (party._activeParty[idx]._rosterId == c._rosterId) {
+ party._activeParty.remove_at(idx);
+ break;
+ }
+ }
+
+ // Empty the character in the roster
+ c.clear();
+
+ // Rebuild the character list
+ _charList.clear();
+ for (int idx = 0; idx < XEEN_TOTAL_CHARACTERS; ++idx) {
+ Character &ch = party._roster[idx];
+ if (!ch._name.empty() && ch._savedMazeId == party._priorMazeId) {
+ _charList.push_back(idx);
+ }
+ }
+
+ startingCharChanged(startingChar);
+ }
+ }
+ }
+ }
+ break;
+
+ case Common::KEYCODE_r:
+ // Remove character
+ if (party._activeParty.size() > 0) {
+ int charButtonValue = selectCharacter(false, startingChar);
+ if (charButtonValue != 0) {
+ party.copyPartyToRoster();
+ party._activeParty.remove_at(charButtonValue - Common::KEYCODE_F1);
+ }
+ startingCharChanged(startingChar);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
+
+void PartyDialog::loadButtons() {
+ _uiSprites.load("inn.icn");
+ addButton(Common::Rect(16, 100, 40, 120), Common::KEYCODE_UP, &_uiSprites);
+ addButton(Common::Rect(52, 100, 76, 120), Common::KEYCODE_DOWN, &_uiSprites);
+ addButton(Common::Rect(87, 100, 111, 120), Common::KEYCODE_d, &_uiSprites);
+ addButton(Common::Rect(122, 100, 146, 120), Common::KEYCODE_r, &_uiSprites);
+ addButton(Common::Rect(157, 100, 181, 120), Common::KEYCODE_c, &_uiSprites);
+ addButton(Common::Rect(192, 100, 216, 120), Common::KEYCODE_x, &_uiSprites);
+ addButton(Common::Rect(0, 0, 0, 0), Common::KEYCODE_ESCAPE);
+}
+
+void PartyDialog::initDrawStructs() {
+ _faceDrawStructs[0] = DrawStruct(0, 0, 0);
+ _faceDrawStructs[1] = DrawStruct(0, 101, 0);
+ _faceDrawStructs[2] = DrawStruct(0, 0, 43);
+ _faceDrawStructs[3] = DrawStruct(0, 101, 43);
+}
+
+void PartyDialog::setupBackground() {
+ _vm->_screen->loadBackground("back.raw");
+ _vm->_interface->assembleBorder();
+}
+
+void PartyDialog::setupFaces(int firstDisplayChar, bool updateFlag) {
+ Party &party = *_vm->_party;
+ Common::String charNames[4];
+ Common::String charRaces[4];
+ Common::String charSex[4];
+ Common::String charClasses[4];
+ int posIndex;
+ int charId;
+
+ // Reset the button areas for the display character images
+ while (_buttons.size() > 7)
+ _buttons.remove_at(7);
+ addButton(Common::Rect(16, 16, 48, 48), Common::KEYCODE_1);
+ addButton(Common::Rect(117, 16, 149, 48), Common::KEYCODE_2);
+ addButton(Common::Rect(59, 59, 91, 91), Common::KEYCODE_3);
+ addButton(Common::Rect(117, 59, 151, 91), Common::KEYCODE_4);
+
+
+ for (posIndex = 0; posIndex < 4; ++posIndex) {
+ charId = (firstDisplayChar + posIndex) >= (int)_charList.size() ? -1 :
+ _charList[firstDisplayChar + posIndex];
+ bool isInParty = party.isInParty(charId);
+
+ if (charId == -1) {
+ while ((int)_buttons.size() >(7 + posIndex))
+ _buttons.remove_at(_buttons.size() - 1);
+ break;
+ }
+
+ Common::Rect &b = _buttons[7 + posIndex]._bounds;
+ b.moveTo((posIndex & 1) ? 117 : 16, b.top);
+ Character &ps = party._roster[_charList[firstDisplayChar + posIndex]];
+ charNames[posIndex] = isInParty ? IN_PARTY : ps._name;
+ charRaces[posIndex] = RACE_NAMES[ps._race];
+ charSex[posIndex] = SEX_NAMES[ps._sex];
+ charClasses[posIndex] = CLASS_NAMES[ps._class];
+ }
+
+ drawParty(updateFlag);
+
+ // Set up the sprite set to use for each face
+ for (posIndex = 0; posIndex < 4; ++posIndex) {
+ if ((firstDisplayChar + posIndex) >= (int)_charList.size())
+ _faceDrawStructs[posIndex]._sprites = nullptr;
+ else
+ _faceDrawStructs[posIndex]._sprites = party._roster[
+ _charList[firstDisplayChar + posIndex]]._faceSprites;
+ }
+
+ _partyDetails = Common::String::format(PARTY_DETAILS,
+ charNames[0].c_str(), charRaces[0].c_str(), charSex[0].c_str(), charClasses[0].c_str(),
+ charNames[1].c_str(), charRaces[1].c_str(), charSex[1].c_str(), charClasses[1].c_str(),
+ charNames[2].c_str(), charRaces[2].c_str(), charSex[2].c_str(), charClasses[2].c_str(),
+ charNames[3].c_str(), charRaces[3].c_str(), charSex[3].c_str(), charClasses[3].c_str()
+ );
+}
+
+void PartyDialog::startingCharChanged(int firstDisplayChar) {
+ Window &w = _vm->_screen->_windows[11];
+
+ setupFaces(firstDisplayChar, true);
+ w.writeString(Common::String::format(PARTY_DIALOG_TEXT, _partyDetails.c_str()));
+ w.drawList(_faceDrawStructs, 4);
+
+ _uiSprites.draw(w, 0, Common::Point(16, 100));
+ _uiSprites.draw(w, 2, Common::Point(52, 100));
+ _uiSprites.draw(w, 4, Common::Point(87, 100));
+ _uiSprites.draw(w, 6, Common::Point(122, 100));
+ _uiSprites.draw(w, 8, Common::Point(157, 100));
+ _uiSprites.draw(w, 10, Common::Point(192, 100));
+
+ w.update();
+}
+
+void PartyDialog::createChar() {
+ EventsManager &events = *_vm->_events;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Window &w = screen._windows[0];
+ SpriteResource dice, icons;
+ Common::Array<int> freeCharList;
+ int classId;
+ int selectedClass = 0;
+ bool hasFadedIn = false;
+ bool restartFlag = true;
+ uint attribs[TOTAL_ATTRIBUTES];
+ bool allowedClasses[TOTAL_CLASSES];
+ Race race;
+ Sex sex;
+ Common::String msg;
+ int charIndex;
+
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_4;
+ dice.load("dice.vga");
+ icons.load("create.raw");
+
+ _dicePos[0] = Common::Point(20, 17);
+ _dicePos[1] = Common::Point(112, 35);
+ _dicePos[2] = Common::Point(61, 50);
+ _diceFrame[0] = 0;
+ _diceFrame[1] = 2;
+ _diceFrame[2] = 4;
+ _diceInc[0] = Common::Point(10, -10);
+ _diceInc[1] = Common::Point(-10, -10);
+ _diceInc[2] = Common::Point(-10, 10);
+
+ // Add buttons
+ saveButtons();
+ addButton(Common::Rect(132, 98, 156, 118), Common::KEYCODE_r, &icons);
+ addButton(Common::Rect(132, 128, 156, 148), Common::KEYCODE_c, &icons);
+ addButton(Common::Rect(132, 158, 156, 178), Common::KEYCODE_ESCAPE, &icons);
+ addButton(Common::Rect(86, 98, 110, 118), Common::KEYCODE_UP, &icons);
+ addButton(Common::Rect(86, 120, 110, 140), Common::KEYCODE_DOWN, &icons);
+ addButton(Common::Rect(168, 19, 192, 39), Common::KEYCODE_n, nullptr);
+ addButton(Common::Rect(168, 43, 192, 63), Common::KEYCODE_i, nullptr);
+ addButton(Common::Rect(168, 67, 192, 87), Common::KEYCODE_p, nullptr);
+ addButton(Common::Rect(168, 91, 192, 111), Common::KEYCODE_e, nullptr);
+ addButton(Common::Rect(168, 115, 192, 135), Common::KEYCODE_s, nullptr);
+ addButton(Common::Rect(168, 139, 192, 159), Common::KEYCODE_a, nullptr);
+ addButton(Common::Rect(168, 163, 192, 183), Common::KEYCODE_l, nullptr);
+ addButton(Common::Rect(227, 19, 139, 29), 1000, nullptr);
+ addButton(Common::Rect(227, 30, 139, 40), 1001, nullptr);
+ addButton(Common::Rect(227, 41, 139, 51), 1002, nullptr);
+ addButton(Common::Rect(227, 52, 139, 62), 1003, nullptr);
+ addButton(Common::Rect(227, 63, 139, 73), 1004, nullptr);
+ addButton(Common::Rect(227, 74, 139, 84), 1005, nullptr);
+ addButton(Common::Rect(227, 85, 139, 95), 1006, nullptr);
+ addButton(Common::Rect(227, 96, 139, 106), 1007, nullptr);
+ addButton(Common::Rect(227, 107, 139, 117), 1008, nullptr);
+ addButton(Common::Rect(227, 118, 139, 128), 1009, nullptr);
+
+ // Load the background
+ screen.loadBackground("create.raw");
+ events.setCursor(0);
+
+ while (!_vm->shouldQuit()) {
+ classId = -1;
+
+ if (restartFlag) {
+ // Build up list of roster slot indexes that are free
+ freeCharList.clear();
+ for (uint idx = 0; idx < XEEN_TOTAL_CHARACTERS; ++idx) {
+ if (party._roster[idx]._name.empty())
+ freeCharList.push_back(idx);
+ }
+ charIndex = 0;
+ //bool flag9 = true;
+
+ if (freeCharList.size() == XEEN_TOTAL_CHARACTERS)
+ break;
+
+ // Get and race and sex for the given character
+ race = (Race)((freeCharList[charIndex] / 4) % 5);
+ sex = (Sex)(freeCharList[charIndex] & 1);
+
+ // Randomly determine attributes, and which classes they allow
+ throwDice(attribs, allowedClasses);
+
+ // Set up display of the rolled character details
+ selectedClass = newCharDetails(attribs, allowedClasses,
+ race, sex, classId, selectedClass, msg);
+
+ // Draw the screen
+ icons.draw(w, 10, Common::Point(168, 19));
+ icons.draw(w, 12, Common::Point(168, 43));
+ icons.draw(w, 14, Common::Point(168, 67));
+ icons.draw(w, 16, Common::Point(168, 91));
+ icons.draw(w, 18, Common::Point(168, 115));
+ icons.draw(w, 20, Common::Point(168, 139));
+ icons.draw(w, 22, Common::Point(168, 163));
+ for (int idx = 0; idx < 9; ++idx)
+ icons.draw(w, 24 + idx * 2, Common::Point(227, 19 + 11 * idx));
+
+ for (int idx = 0; idx < 7; ++idx)
+ icons.draw(w, 50 + idx, Common::Point(195, 31 + 24 * idx));
+
+ icons.draw(w, 57, Common::Point(62, 148));
+ icons.draw(w, 58, Common::Point(62, 158));
+ icons.draw(w, 59, Common::Point(62, 168));
+ icons.draw(w, 61, Common::Point(220, 19));
+ icons.draw(w, 64, Common::Point(220, 155));
+ icons.draw(w, 65, Common::Point(220, 170));
+
+ party._roster[freeCharList[charIndex]]._faceSprites->draw(
+ w, 0, Common::Point(27, 102));
+
+ icons.draw(w, 0, Common::Point(132, 98));
+ icons.draw(w, 2, Common::Point(132, 128));
+ icons.draw(w, 4, Common::Point(132, 158));
+ icons.draw(w, 6, Common::Point(86, 98));
+ icons.draw(w, 8, Common::Point(86, 120));
+
+ w.writeString(msg);
+ w.update();
+
+ // Draw the arrow for the selected class, if applicable
+ if (selectedClass != -1)
+ printSelectionArrow(icons, selectedClass);
+
+ // Draw the dice
+ drawDice(dice);
+ if (!hasFadedIn) {
+ screen.fadeIn(4);
+ hasFadedIn = true;
+ }
+
+ restartFlag = false;
+ }
+
+ // Animate the dice until a user action occurs
+ _buttonValue = 0;
+ while (!_vm->shouldQuit() && !_buttonValue)
+ drawDice(dice);
+
+ // Handling for different actions
+ switch (_buttonValue) {
+ case Common::KEYCODE_UP:
+ if (charIndex == 0)
+ continue;
+
+ race = (Race)((freeCharList[charIndex] / 4) % 5);
+ sex = (Sex)(freeCharList[charIndex] & 1);
+ break;
+
+ case Common::KEYCODE_DOWN:
+ if (++charIndex == (int)freeCharList.size()) {
+ --charIndex;
+ continue;
+ } else {
+ race = (Race)((freeCharList[charIndex] / 4) % 5);
+ sex = (Sex)(freeCharList[charIndex] & 1);
+ }
+ break;
+
+ case Common::KEYCODE_PAGEUP:
+ for (int tempClass = selectedClass - 1; tempClass >= 0; --tempClass) {
+ if (allowedClasses[tempClass]) {
+ selectedClass = tempClass;
+ break;
+ }
+ }
+
+ printSelectionArrow(icons, selectedClass);
+ continue;
+
+ case Common::KEYCODE_PAGEDOWN:
+ break;
+
+ case Common::KEYCODE_m:
+ case Common::KEYCODE_i:
+ case Common::KEYCODE_p:
+ case Common::KEYCODE_e:
+ case Common::KEYCODE_s:
+ case Common::KEYCODE_a:
+ case Common::KEYCODE_l: {
+ Attribute srcAttrib, destAttrib;
+ if (_buttonValue == Common::KEYCODE_m)
+ srcAttrib = MIGHT;
+ else if (_buttonValue == Common::KEYCODE_i)
+ srcAttrib = INTELLECT;
+ else if (_buttonValue == Common::KEYCODE_p)
+ srcAttrib = PERSONALITY;
+ else if (_buttonValue == Common::KEYCODE_e)
+ srcAttrib = ENDURANCE;
+ else if (_buttonValue == Common::KEYCODE_s)
+ srcAttrib = SPEED;
+ else if (_buttonValue == Common::KEYCODE_a)
+ srcAttrib = ACCURACY;
+ else
+ srcAttrib = LUCK;
+
+ _vm->_mode = MODE_86;
+ icons.draw(w, srcAttrib * 2 + 11, Common::Point(
+ _buttons[srcAttrib + 5]._bounds.left, _buttons[srcAttrib + 5]._bounds.top));
+ w.update();
+
+ int destAttribVal = exchangeAttribute(srcAttrib + 1);
+ if (destAttribVal) {
+ destAttrib = (Attribute)(destAttribVal - 1);
+ icons.draw(w, destAttrib * 2 + 11, Common::Point(
+ _buttons[destAttrib + 10]._bounds.left,
+ _buttons[destAttrib + 10]._bounds.top));
+ w.update();
+
+ SWAP(attribs[srcAttrib], attribs[destAttrib]);
+ checkClass(attribs, allowedClasses);
+ classId = -1;
+ selectedClass = newCharDetails(attribs, allowedClasses,
+ race, sex, classId, selectedClass, msg);
+ } else {
+ icons.draw(w, srcAttrib * 2 + 10, Common::Point(
+ _buttons[srcAttrib + 5]._bounds.left,
+ _buttons[srcAttrib + 5]._bounds.top));
+ w.update();
+ _vm->_mode = MODE_SLEEPING;
+ continue;
+ }
+ break;
+ }
+
+ case 1000:
+ case 1001:
+ case 1002:
+ case 1003:
+ case 1004:
+ case 1005:
+ case 1006:
+ case 1007:
+ case 1008:
+ case 1009:
+ if (allowedClasses[_buttonValue - 1000]) {
+ selectedClass = classId = _buttonValue - 1000;
+ }
+ break;
+
+ case Common::KEYCODE_c: {
+ _vm->_mode = MODE_FF;
+ bool result = saveCharacter(party._roster[freeCharList[charIndex]],
+ classId, race, sex, attribs);
+ _vm->_mode = MODE_4;
+
+ if (result)
+ restartFlag = true;
+ continue;
+ }
+
+ case Common::KEYCODE_RETURN:
+ classId = selectedClass;
+ break;
+
+ case Common::KEYCODE_SPACE:
+ case Common::KEYCODE_r:
+ // Re-roll the attributes
+ throwDice(attribs, allowedClasses);
+ classId = -1;
+ break;
+
+ default:
+ // For all other keypresses, skip the code below the switch
+ // statement, and go to wait for the next key
+ continue;
+ }
+
+ if (_buttonValue != Common::KEYCODE_PAGEDOWN) {
+ selectedClass = newCharDetails(attribs, allowedClasses,
+ race, sex, classId, selectedClass, msg);
+
+ for (int idx = 0; idx < 7; ++idx)
+ icons.draw(w, 10 + idx * 2, Common::Point(168, 19 + idx * 24));
+ for (int idx = 0; idx < 10; ++idx)
+ icons.draw(w, 24 + idx * 2, Common::Point(227, 19 + idx * 11));
+ for (int idx = 0; idx < 8; ++idx)
+ icons.draw(w, 50 + idx, Common::Point(195, 31 + idx * 24));
+
+ icons.draw(w, 57, Common::Point(62, 148));
+ icons.draw(w, 58, Common::Point(62, 158));
+ icons.draw(w, 59, Common::Point(62, 168));
+ icons.draw(w, 61, Common::Point(220, 19));
+ icons.draw(w, 64, Common::Point(220, 155));
+ icons.draw(w, 65, Common::Point(220, 170));
+
+ party._roster[freeCharList[charIndex]]._faceSprites->draw(w, 0,
+ Common::Point(27, 102));
+
+ icons.draw(w, 0, Common::Point(132, 98));
+ icons.draw(w, 2, Common::Point(132, 128));
+ icons.draw(w, 4, Common::Point(132, 158));
+ icons.draw(w, 6, Common::Point(86, 98));
+ icons.draw(w, 8, Common::Point(86, 120));
+
+ w.writeString(msg);
+ w.update();
+
+ if (selectedClass != -1) {
+ printSelectionArrow(icons, selectedClass);
+ continue;
+ }
+ }
+
+ // Move to next available class, or if the code block above resulted in
+ // selectedClass being -1, move to select the first available class
+ for (int tempClass = selectedClass + 1; tempClass <= CLASS_RANGER; ++tempClass) {
+ if (allowedClasses[tempClass]) {
+ selectedClass = tempClass;
+ break;
+ }
+ }
+
+ printSelectionArrow(icons, selectedClass);
+ } while (!_vm->shouldQuit() && _buttonValue != Common::KEYCODE_ESCAPE);
+
+ _vm->_mode = oldMode;
+}
+
+int PartyDialog::selectCharacter(bool isDelete, int firstDisplayChar) {
+ EventsManager &events = *_vm->_events;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Window &w = screen._windows[28];
+
+ SpriteResource iconSprites;
+ iconSprites.load("esc.icn");
+
+ w.setBounds(Common::Rect(50, isDelete ? 112 : 76, 266, isDelete ? 148 : 112));
+ w.open();
+ w.writeString(Common::String::format(REMOVE_OR_DELETE_WHICH,
+ REMOVE_DELETE[isDelete ? 1 : 0]));
+ iconSprites.draw(w, 0, Common::Point(225, isDelete ? 120 : 84));
+ w.update();
+
+ saveButtons();
+ addButton(Common::Rect(225, isDelete ? 120 : 84, 249, isDelete ? 140 : 104),
+ Common::KEYCODE_ESCAPE, &iconSprites);
+ addButton(Common::Rect(16, 16, 48, 48), Common::KEYCODE_1);
+ addButton(Common::Rect(117, 16, 149, 48), Common::KEYCODE_2);
+ addButton(Common::Rect(16, 59, 48, 91), Common::KEYCODE_3);
+ addButton(Common::Rect(117, 59, 149, 91), Common::KEYCODE_4);
+ addPartyButtons(_vm);
+
+ int result = -1, v;
+ while (!_vm->shouldQuit() && result == -1) {
+ _buttonValue = 0;
+ while (!_vm->shouldQuit() && !_buttonValue) {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ }
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_ESCAPE:
+ result = 0;
+ break;
+
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ if (!isDelete) {
+ v = _buttonValue - Common::KEYCODE_F1;
+ if (v < (int)party._activeParty.size())
+ result = _buttonValue;
+ }
+ break;
+
+ case Common::KEYCODE_1:
+ case Common::KEYCODE_2:
+ case Common::KEYCODE_3:
+ case Common::KEYCODE_4:
+ if (isDelete) {
+ v = _buttonValue - Common::KEYCODE_1;
+ if ((firstDisplayChar + v) < (int)_charList.size())
+ result = _buttonValue;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ w.close();
+ restoreButtons();
+ return result == -1 ? 0 : result;
+}
+
+void PartyDialog::throwDice(uint attribs[TOTAL_ATTRIBUTES], bool allowedClasses[TOTAL_CLASSES]) {
+ bool repeat = true;
+ do {
+ // Default all the attributes to zero
+ Common::fill(&attribs[0], &attribs[TOTAL_ATTRIBUTES], 0);
+
+ // Assign random amounts to each attribute
+ for (int idx1 = 0; idx1 < 3; ++idx1) {
+ for (int idx2 = 0; idx2 < TOTAL_ATTRIBUTES; ++idx2) {
+ attribs[idx1] += _vm->getRandomNumber(10, 79) / 10;
+ }
+ }
+
+ // Check which classes are allowed based on the rolled attributes
+ checkClass(attribs, allowedClasses);
+
+ // Only exit if the attributes allow for at least one class
+ for (int idx = 0; idx < TOTAL_CLASSES; ++idx) {
+ if (allowedClasses[idx])
+ repeat = false;
+ }
+ } while (repeat);
+}
+
+void PartyDialog::checkClass(const uint attribs[TOTAL_ATTRIBUTES], bool allowedClasses[TOTAL_CLASSES]) {
+ allowedClasses[CLASS_KNIGHT] = attribs[MIGHT] >= 15;
+ allowedClasses[CLASS_PALADIN] = attribs[MIGHT] >= 13
+ && attribs[PERSONALITY] >= 13 && attribs[ENDURANCE] >= 13;
+ allowedClasses[CLASS_ARCHER] = attribs[INTELLECT] >= 13 && attribs[ACCURACY] >= 13;
+ allowedClasses[CLASS_CLERIC] = attribs[PERSONALITY] >= 13;
+ allowedClasses[CLASS_SORCERER] = attribs[INTELLECT] >= 13;
+ allowedClasses[CLASS_ROBBER] = attribs[LUCK] >= 13;
+ allowedClasses[CLASS_NINJA] = attribs[SPEED] >= 13 && attribs[ACCURACY] >= 13;
+ allowedClasses[CLASS_BARBARIAN] = attribs[ENDURANCE] >= 15;
+ allowedClasses[CLASS_DRUID] = attribs[INTELLECT] >= 15 && attribs[PERSONALITY] >= 15;
+ allowedClasses[CLASS_RANGER] = attribs[INTELLECT] >= 12 && attribs[PERSONALITY] >= 12
+ && attribs[ENDURANCE] >= 12 && attribs[SPEED] >= 12;
+}
+
+int PartyDialog::newCharDetails(const uint attribs[TOTAL_ATTRIBUTES],
+ bool allowedClasses[TOTAL_CLASSES], Race race, Sex sex, int classId,
+ int selectedClass, Common::String &msg) {
+ int foundClass = -1;
+ Common::String skillStr, classStr, raceSkillStr;
+
+ // If a selected class is provided, set the default skill for that class
+ if (classId != -1 && NEW_CHAR_SKILLS[classId] != -1) {
+ const char *skillP = SKILL_NAMES[NEW_CHAR_SKILLS[classId]];
+ skillStr = Common::String(skillP, skillP + NEW_CHAR_SKILLS_LEN[classId]);
+ }
+
+ // If a class is provided, set the class name
+ if (classId != -1) {
+ classStr = Common::String::format("\t062\v168%s", CLASS_NAMES[classId]);
+ }
+
+ // Set up default skill for the race, if any
+ if (NEW_CHAR_RACE_SKILLS[race] != -1) {
+ raceSkillStr = SKILL_NAMES[NEW_CHAR_RACE_SKILLS[race]];
+ }
+
+ // Set up color to use for each skill string to be displayed, based
+ // on whether each class is allowed or not for the given attributes
+ int classColors[TOTAL_CLASSES];
+ Common::fill(&classColors[0], &classColors[TOTAL_CLASSES], 0);
+ for (int classNum = CLASS_KNIGHT; classNum <= CLASS_RANGER; ++classNum) {
+ if (allowedClasses[classNum]) {
+ if (classId == -1 && (foundClass == -1 || foundClass < classNum))
+ foundClass = classNum;
+ classColors[classNum] = 4;
+ }
+ }
+
+ // Return stats details and character class
+ msg = Common::String::format(NEW_CHAR_STATS, RACE_NAMES[race], SEX_NAMES[sex],
+ attribs[MIGHT], attribs[INTELLECT], attribs[PERSONALITY],
+ attribs[ENDURANCE], attribs[SPEED], attribs[ACCURACY], attribs[LUCK],
+ classColors[CLASS_KNIGHT], classColors[CLASS_PALADIN],
+ classColors[CLASS_ARCHER], classColors[CLASS_CLERIC],
+ classColors[CLASS_SORCERER], classColors[CLASS_ROBBER],
+ classColors[CLASS_NINJA], classColors[CLASS_BARBARIAN],
+ classColors[CLASS_DRUID], classColors[CLASS_RANGER],
+ skillStr.c_str(), raceSkillStr.c_str(), classStr.c_str()
+ );
+ return classId == -1 ? foundClass : selectedClass;
+}
+
+void PartyDialog::printSelectionArrow(SpriteResource &icons, int selectedClass) {
+ Window &w = _vm->_screen->_windows[0];
+ icons.draw(w, 61, Common::Point(220, 19));
+ icons.draw(w, 63, Common::Point(220, selectedClass * 11 + 21));
+ w.update();
+}
+
+void PartyDialog::drawDice(SpriteResource &dice) {
+ EventsManager &events = *_vm->_events;
+ Window &w = _vm->_screen->_windows[32];
+ dice.draw(w, 7, Common::Point(12, 11));
+
+ for (int diceNum = 0; diceNum < 3; ++diceNum) {
+ _diceFrame[diceNum] = (_diceFrame[diceNum] + 1) % 7;
+ _dicePos[diceNum] += _diceInc[diceNum];
+
+ if (_dicePos[diceNum].x < 13) {
+ _dicePos[diceNum].x = 13;
+ _diceInc[diceNum].x *= -1;
+ } else if (_dicePos[diceNum].x >= 163) {
+ _dicePos[diceNum].x = 163;
+ _diceInc[diceNum].x *= -1;
+ }
+
+ if (_dicePos[diceNum].y < 12) {
+ _dicePos[diceNum].y = 12;
+ _diceInc[diceNum].y *= -1;
+ } else if (_dicePos[diceNum].y >= 93) {
+ _dicePos[diceNum].y = 93;
+ _diceInc[diceNum].y *= -1;
+ }
+
+ dice.draw(w, _diceFrame[diceNum], _dicePos[diceNum]);
+ }
+
+ w.update();
+
+ // Wait for keypress
+ events.wait(1, true);
+ checkEvents(_vm);
+}
+
+int PartyDialog::exchangeAttribute(int srcAttr) {
+ EventsManager &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ SpriteResource icons;
+ icons.load("create2.icn");
+
+ saveButtons();
+ addButton(Common::Rect(118, 58, 142, 78), Common::KEYCODE_ESCAPE, &icons);
+ addButton(Common::Rect(168, 19, 192, 39), Common::KEYCODE_m);
+ addButton(Common::Rect(168, 43, 192, 63), Common::KEYCODE_i);
+ addButton(Common::Rect(168, 67, 192, 87), Common::KEYCODE_p);
+ addButton(Common::Rect(168, 91, 192, 111), Common::KEYCODE_e);
+ addButton(Common::Rect(168, 115, 192, 135), Common::KEYCODE_s);
+ addButton(Common::Rect(168, 139, 192, 159), Common::KEYCODE_a);
+ addButton(Common::Rect(168, 163, 192, 183), Common::KEYCODE_l);
+
+ Window &w = screen._windows[26];
+ w.open();
+ w.writeString(Common::String::format(EXCHANGE_ATTR_WITH, STAT_NAMES[srcAttr - 1]));
+ icons.draw(w, 0, Common::Point(118, 58));
+ w.update();
+
+ int result = 0;
+ bool breakFlag = false;
+ while (!_vm->shouldQuit() && !breakFlag) {
+ // Wait for an action
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ } while (!_vm->shouldQuit() && !_buttonValue);
+
+ Attribute destAttr;
+ switch (_buttonValue) {
+ case Common::KEYCODE_m:
+ destAttr = MIGHT;
+ break;
+ case Common::KEYCODE_i:
+ destAttr = INTELLECT;
+ break;
+ case Common::KEYCODE_p:
+ destAttr = PERSONALITY;
+ break;
+ case Common::KEYCODE_e:
+ destAttr = ENDURANCE;
+ break;
+ case Common::KEYCODE_s:
+ destAttr = SPEED;
+ break;
+ case Common::KEYCODE_a:
+ destAttr = ACCURACY;
+ break;
+ case Common::KEYCODE_l:
+ destAttr = LUCK;
+ break;
+ case Common::KEYCODE_ESCAPE:
+ result = 0;
+ breakFlag = true;
+ continue;
+ default:
+ continue;
+ }
+
+ if ((srcAttr - 1) != destAttr) {
+ result = destAttr + 1;
+ break;
+ }
+ }
+
+ w.close();
+ _buttonValue = 0;
+ restoreButtons();
+
+ return result;
+}
+
+bool PartyDialog::saveCharacter(Character &c, int classId,
+ Race race, Sex sex, uint attribs[TOTAL_ATTRIBUTES]) {
+ if (classId == -1) {
+ ErrorScroll::show(_vm, SELECT_CLASS_BEFORE_SAVING);
+ return false;
+ }
+
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Window &w = screen._windows[6];
+ Common::String name;
+ int result;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ saveButtons();
+ w.writeString(NAME_FOR_NEW_CHARACTER);
+
+ result = Input::show(_vm, &w, name, 10, 200);
+ w.close();
+ restoreButtons();
+ if (!result)
+ return false;
+
+ // Save new character details
+ c.clear();
+ c._name = name;
+ c._savedMazeId = party._priorMazeId;
+ c._xeenSide = map._loadDarkSide;
+ c._sex = sex;
+ c._race = race;
+ c._class = (CharacterClass)classId;
+ c._level._permanent = isDarkCc ? 5 : 1;
+
+ c._might._permanent = attribs[MIGHT];
+ c._intellect._permanent = attribs[INTELLECT];
+ c._personality._permanent = attribs[PERSONALITY];
+ c._endurance._permanent = attribs[ENDURANCE];
+ c._speed._permanent = attribs[SPEED];
+ c._accuracy._permanent = attribs[ACCURACY];
+ c._luck._permanent = attribs[LUCK];
+
+ c._magicResistence._permanent = RACE_MAGIC_RESISTENCES[race];
+ c._fireResistence._permanent = RACE_FIRE_RESISTENCES[race];
+ c._electricityResistence._permanent = RACE_ELECTRIC_RESISTENCES[race];
+ c._coldResistence._permanent = RACE_COLD_RESISTENCES[race];
+ c._energyResistence._permanent = RACE_ENERGY_RESISTENCES[race];
+ c._poisonResistence._permanent = RACE_POISON_RESISTENCES[race];
+
+ c._birthYear = party._year - 18;
+ c._birthDay = party._day;
+ c._hasSpells = false;
+ c._currentSpell = -1;
+
+ // Set up any default spells for the character's class
+ for (int idx = 0; idx < 4; ++idx) {
+ if (NEW_CHARACTER_SPELLS[c._class][idx] != -1) {
+ c._hasSpells = true;
+ c._currentSpell = NEW_CHARACTER_SPELLS[c._class][idx];
+ c._spells[c._currentSpell] = 1;
+ }
+ }
+
+ int classSkill = NEW_CHAR_SKILLS[c._class];
+ if (classSkill != -1)
+ c._skills[classSkill] = 1;
+
+ int raceSkill = NEW_CHAR_RACE_SKILLS[c._race];
+ if (raceSkill != -1)
+ c._skills[raceSkill] = 1;
+
+ c._currentHp = c.getMaxHP();
+ c._currentSp = c.getMaxSP();
+ return true;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_party.h b/engines/xeen/dialogs_party.h
new file mode 100644
index 0000000000..17ec4fd300
--- /dev/null
+++ b/engines/xeen/dialogs_party.h
@@ -0,0 +1,111 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_PARTY_H
+#define XEEN_DIALOGS_PARTY_H
+
+#include "common/array.h"
+#include "xeen/dialogs.h"
+#include "xeen/interface.h"
+#include "xeen/screen.h"
+#include "xeen/sprites.h"
+
+namespace Xeen {
+
+class PartyDialog : public ButtonContainer, public PartyDrawer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _uiSprites;
+ DrawStruct _faceDrawStructs[4];
+ Common::String _partyDetails;
+ Common::Array<int> _charList;
+ int _diceFrame[3];
+ Common::Point _dicePos[3];
+ Common::Point _diceInc[3];
+
+ PartyDialog(XeenEngine *vm);
+
+ void execute();
+
+ void loadButtons();
+
+ void initDrawStructs();
+
+ void setupBackground();
+
+ /**
+ * Sets up the faces from the avaialble roster for display in the party dialog
+ */
+ void setupFaces(int firstDisplayChar, bool updateFlag);
+
+ void startingCharChanged(int firstDisplayChar);
+
+ void createChar();
+
+ int selectCharacter(bool isDelete, int firstDisplayChar);
+
+ /**
+ * Roll up some random values for the attributes, and return both them as
+ * well as a list of classes that the attributes meet the requirements for
+ */
+ void throwDice(uint attribs[TOTAL_ATTRIBUTES], bool allowedClasses[TOTAL_CLASSES]);
+
+ /**
+ * Set a list of flags for which classes the passed attribute set meet the
+ * minimum requirements of
+ */
+ void checkClass(const uint attribs[TOTAL_ATTRIBUTES], bool allowedClasses[TOTAL_CLASSES]);
+
+ /**
+ * Return details of the generated character
+ */
+ int newCharDetails(const uint attribs[TOTAL_ATTRIBUTES],
+ bool allowedClasses[TOTAL_CLASSES], Race race, Sex sex, int classId,
+ int selectedClass, Common::String &msg);
+
+ /**
+ * Print the selection arrow to indicate the selected class
+ */
+ void printSelectionArrow(SpriteResource &icons, int selectedClass);
+
+ /**
+ * Print the dice animation
+ */
+ void drawDice(SpriteResource &dice);
+
+ /**
+ * Exchanging two attributes for the character being rolled
+ */
+ int exchangeAttribute(int srcAttr);
+
+ /**
+ * Saves the rolled character into the roster
+ */
+ bool saveCharacter(Character &c, int classId, Race race,
+ Sex sex, uint attribs[TOTAL_ATTRIBUTES]);
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_PARTY_H */
diff --git a/engines/xeen/dialogs_query.cpp b/engines/xeen/dialogs_query.cpp
new file mode 100644
index 0000000000..abaddafe69
--- /dev/null
+++ b/engines/xeen/dialogs_query.cpp
@@ -0,0 +1,160 @@
+/* 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.
+ *
+ */
+
+#include "xeen/dialogs_query.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+bool Confirm::show(XeenEngine *vm, const Common::String &msg, int mode) {
+ Confirm *dlg = new Confirm(vm);
+ bool result = dlg->execute(msg, mode);
+ delete dlg;
+
+ return result;
+}
+
+bool Confirm::execute(const Common::String &msg, int mode) {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ SpriteResource confirmSprites;
+ bool result = false;
+
+ confirmSprites.load("confirm.icn");
+ addButton(Common::Rect(129, 112, 153, 122), Common::KEYCODE_y, &confirmSprites);
+ addButton(Common::Rect(185, 112, 209, 122), Common::KEYCODE_n, &confirmSprites);
+
+ Window &w = screen._windows[mode ? 22 : 21];
+ w.open();
+
+ if (!mode) {
+ confirmSprites.draw(w, 0, Common::Point(129, 112));
+ confirmSprites.draw(w, 2, Common::Point(185, 112));
+ _buttons[0]._bounds.moveTo(129, 112);
+ _buttons[1]._bounds.moveTo(185, 112);
+ } else {
+ if (mode & 0x80) {
+ clearButtons();
+ } else {
+ confirmSprites.draw(w, 0, Common::Point(120, 133));
+ confirmSprites.draw(w, 2, Common::Point(176, 133));
+ _buttons[0]._bounds.moveTo(120, 133);
+ _buttons[1]._bounds.moveTo(176, 133);
+ }
+ }
+
+ w.writeString(msg);
+ w.update();
+
+ events.clearEvents();
+ while (!_vm->shouldQuit()) {
+ while (!_vm->shouldQuit() && !_buttonValue) {
+ events.pollEvents();
+ checkEvents(_vm);
+ }
+
+ if ((mode & 0x80) || _buttonValue == Common::KEYCODE_ESCAPE
+ || _buttonValue == Common::KEYCODE_n)
+ break;
+
+ if (_buttonValue == Common::KEYCODE_y) {
+ result = true;
+ break;
+ }
+ }
+
+ w.close();
+ return result;
+}
+
+/*------------------------------------------------------------------------*/
+
+bool YesNo::show(XeenEngine *vm, bool type, bool townFlag) {
+ YesNo *dlg = new YesNo(vm);
+ bool result = dlg->execute(type, townFlag);
+ delete dlg;
+
+ return result;
+}
+
+bool YesNo::execute(bool type, bool townFlag) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+ Town &town = *_vm->_town;
+ SpriteResource confirmSprites;
+ //int numFrames;
+ bool result = false;
+
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = oldMode == MODE_7 ? MODE_8 : MODE_7;
+
+ if (!type) {
+ confirmSprites.load("confirm.icn");
+ res._globalSprites.draw(screen, 7, Common::Point(232, 74));
+ confirmSprites.draw(screen, 0, Common::Point(235, 75));
+ confirmSprites.draw(screen, 2, Common::Point(260, 75));
+ screen._windows[34].update();
+
+ addButton(Common::Rect(235, 75, 259, 95), Common::KEYCODE_y, &confirmSprites);
+ addButton(Common::Rect(260, 75, 284, 95), Common::KEYCODE_n, &confirmSprites);
+
+ intf._face1State = map._headData[party._mazePosition.y][party._mazePosition.x]._left;
+ intf._face2State = map._headData[party._mazePosition.y][party._mazePosition.x]._right;
+ }
+
+ while (!_vm->shouldQuit()) {
+ events.updateGameCounter();
+
+ if (town.isActive()) {
+ town.drawTownAnim(townFlag);
+ //numFrames = 3;
+ } else {
+ intf.draw3d(true);
+ //numFrames = 1;
+ }
+
+ events.wait(3, true);
+ checkEvents(_vm);
+ if (!_buttonValue)
+ continue;
+
+ if (type || _buttonValue == Common::KEYCODE_y) {
+ result = true;
+ break;
+ } else if (_buttonValue == Common::KEYCODE_n || _buttonValue == Common::KEYCODE_ESCAPE)
+ break;
+ }
+
+ intf._face1State = intf._face2State = 2;
+ _vm->_mode = oldMode;
+
+ if (!type)
+ intf.mainIconsPrint();
+
+ return result;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_query.h b/engines/xeen/dialogs_query.h
new file mode 100644
index 0000000000..96ae488b97
--- /dev/null
+++ b/engines/xeen/dialogs_query.h
@@ -0,0 +1,54 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_QUERY_H
+#define XEEN_DIALOGS_QUERY_H
+
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class Confirm : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ Confirm(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ bool execute(const Common::String &msg, int mode);
+public:
+ static bool show(XeenEngine *vm, const Common::String &msg, int mode = 0);
+};
+
+class YesNo : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ YesNo(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ bool execute(bool type, bool townFlag);
+public:
+ static bool show(XeenEngine *vm, bool type, bool townFlag = false);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_QUERY_H */
diff --git a/engines/xeen/dialogs_quests.cpp b/engines/xeen/dialogs_quests.cpp
new file mode 100644
index 0000000000..284e15781b
--- /dev/null
+++ b/engines/xeen/dialogs_quests.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.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "xeen/dialogs_quests.h"
+#include "xeen/events.h"
+#include "xeen/party.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+#define MAX_DIALOG_LINES 128
+
+void Quests::show(XeenEngine *vm) {
+ Quests *dlg = new Quests(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void Quests::execute() {
+ EventsManager &events = *_vm->_events;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Mode oldMode = _vm->_mode;
+ int count = 0;
+ bool headerShown = false;
+ int topRow = 0;
+
+ addButtons();
+ loadQuestNotes();
+
+ enum { QUEST_ITEMS, CURRENT_QUESTS, AUTO_NOTES } mode = QUEST_ITEMS;
+ bool windowFlag;
+ if (screen._windows[29]._enabled) {
+ windowFlag = false;
+ } else {
+ screen._windows[29].open();
+ screen._windows[30].open();
+ windowFlag = true;
+ }
+
+ screen._windows[29].writeString(QUESTS_DIALOG_TEXT);
+ drawButtons(&screen);
+
+ while (!_vm->shouldQuit()) {
+ Common::String lines[MAX_DIALOG_LINES];
+
+ switch (mode) {
+ case QUEST_ITEMS:
+ for (int idx = 0; idx < TOTAL_QUEST_ITEMS; ++idx)
+ lines[idx] = "\b \b*";
+
+ count = 0;
+ headerShown = false;
+ for (int idx = 0; idx < TOTAL_QUEST_ITEMS; ++idx) {
+ if (party._questItems[idx]) {
+ if (!count && !headerShown && idx < 35) {
+ lines[count++] = CLOUDS_OF_XEEN_LINE;
+ }
+ if (idx >= 35 && !headerShown) {
+ lines[count++] = DARKSIDE_OF_XEEN_LINE;
+ headerShown = true;
+ }
+
+ switch (idx) {
+ case 17:
+ case 26:
+ case 79:
+ case 80:
+ case 81:
+ case 82:
+ case 83:
+ case 84:
+ lines[count++] = Common::String::format("%d %s%c",
+ party._questItems[idx], QUEST_ITEM_NAMES[idx],
+ party._questItems[idx] == 1 ? ' ' : 's');
+ break;
+ default:
+ lines[count++] = QUEST_ITEM_NAMES[idx];
+ break;
+ }
+ }
+ }
+
+ if (count == 0) {
+ screen._windows[30].writeString(NO_QUEST_ITEMS);
+ } else {
+ screen._windows[30].writeString(Common::String::format(QUEST_ITEMS_DATA,
+ lines[topRow].c_str(), lines[topRow + 1].c_str(),
+ lines[topRow + 2].c_str(), lines[topRow + 3].c_str(),
+ lines[topRow + 4].c_str(), lines[topRow + 5].c_str(),
+ lines[topRow + 6].c_str(), lines[topRow + 7].c_str(),
+ lines[topRow + 8].c_str()
+ ));
+ }
+ break;
+
+ case CURRENT_QUESTS:
+ for (int idx = 0; idx < TOTAL_QUEST_ITEMS; ++idx)
+ lines[idx] = "";
+
+ count = 0;
+ headerShown = false;
+ for (int idx = 0; idx < TOTAL_QUEST_FLAGS; ++idx) {
+ if (party._quests[idx]) {
+ if (!count && !headerShown && idx < 29) {
+ lines[count++] = CLOUDS_OF_XEEN_LINE;
+ }
+ if (idx > 28 && !headerShown) {
+ lines[count++] = DARKSIDE_OF_XEEN_LINE;
+ headerShown = true;
+ }
+
+ lines[count++] = _questNotes[idx];
+ }
+ }
+
+ if (count == 0)
+ lines[1] = NO_CURRENT_QUESTS;
+
+ screen._windows[30].writeString(Common::String::format(CURRENT_QUESTS_DATA,
+ lines[topRow].c_str(), lines[topRow + 1].c_str(), lines[topRow + 2].c_str()));
+ break;
+
+ case AUTO_NOTES:
+ for (int idx = 0; idx < MAX_DIALOG_LINES; ++idx)
+ lines[idx] = "";
+
+ count = 0;
+ headerShown = false;
+ for (int idx = 0; idx < MAX_DIALOG_LINES; ++idx) {
+ if (party._worldFlags[idx]) {
+ if (!count && !headerShown && idx < 72) {
+ lines[count++] = CLOUDS_OF_XEEN_LINE;
+ }
+ if (idx >= 72 && !headerShown) {
+ lines[count++] = DARKSIDE_OF_XEEN_LINE;
+ headerShown = true;
+ }
+
+ lines[count++] = _questNotes[idx + 56];
+ }
+ }
+
+ if (count == 0)
+ lines[1] = NO_AUTO_NOTES;
+
+ screen._windows[30].writeString(Common::String::format(AUTO_NOTES_DATA,
+ lines[topRow].c_str(), lines[topRow + 1].c_str(),
+ lines[topRow + 2].c_str(), lines[topRow + 3].c_str(),
+ lines[topRow + 4].c_str(), lines[topRow + 5].c_str(),
+ lines[topRow + 6].c_str(), lines[topRow + 7].c_str(),
+ lines[topRow + 8].c_str()
+ ));
+ break;
+ }
+
+ screen._windows[30].writeString("\v000\t000");
+ screen._windows[24].update();
+
+ // Key handling
+ _buttonValue = 0;
+ while (!_vm->shouldQuit() && !_buttonValue) {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ }
+
+ if (_buttonValue == Common::KEYCODE_ESCAPE)
+ break;
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_a:
+ mode = AUTO_NOTES;
+ topRow = 0;
+ break;
+ case Common::KEYCODE_i:
+ mode = QUEST_ITEMS;
+ topRow = 0;
+ break;
+ case Common::KEYCODE_q:
+ mode = CURRENT_QUESTS;
+ topRow = 0;
+ break;
+ case Common::KEYCODE_HOME:
+ topRow = 0;
+ break;
+ case Common::KEYCODE_END:
+ topRow = count - 1;
+ break;
+ case Common::KEYCODE_PAGEUP:
+ topRow = MAX(topRow - 3, 0);
+ break;
+ case Common::KEYCODE_PAGEDOWN:
+ topRow = CLIP(topRow + 3, 0, count - 1);
+ break;
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ topRow = MAX(topRow - 1, 0);
+ break;
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ topRow = CLIP(topRow + 1, 0, count - 1);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (windowFlag) {
+ screen._windows[30].close();
+ screen._windows[29].close();
+ }
+ _vm->_mode = oldMode;
+}
+
+void Quests::addButtons() {
+ _iconSprites.load("quest.icn");
+
+
+ addButton(Common::Rect(12, 109, 36, 129), Common::KEYCODE_i, &_iconSprites);
+ addButton(Common::Rect(80, 109, 104, 129), Common::KEYCODE_q, &_iconSprites);
+ addButton(Common::Rect(148, 109, 172, 129), Common::KEYCODE_a, &_iconSprites);
+ addButton(Common::Rect(216, 109, 240, 129), Common::KEYCODE_UP, &_iconSprites);
+ addButton(Common::Rect(250, 109, 274, 129), Common::KEYCODE_DOWN, &_iconSprites);
+ addButton(Common::Rect(284, 109, 308, 129), Common::KEYCODE_ESCAPE, &_iconSprites);
+}
+
+void Quests::loadQuestNotes() {
+ File f("qnotes.bin", *_vm->_files->_sideArchives[_vm->getGameID() == GType_Clouds ? 0 : 1]);
+ while (f.pos() < f.size())
+ _questNotes.push_back(f.readString());
+ f.close();
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_quests.h b/engines/xeen/dialogs_quests.h
new file mode 100644
index 0000000000..234accded6
--- /dev/null
+++ b/engines/xeen/dialogs_quests.h
@@ -0,0 +1,50 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_QUESTS_H
+#define XEEN_DIALOGS_QUESTS_H
+
+#include "common/str-array.h"
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class Quests : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+ Common::StringArray _questNotes;
+
+ Quests(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+
+ void addButtons();
+
+ void loadQuestNotes();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_QUESTS_H */
diff --git a/engines/xeen/dialogs_quick_ref.cpp b/engines/xeen/dialogs_quick_ref.cpp
new file mode 100644
index 0000000000..e9ffbd482c
--- /dev/null
+++ b/engines/xeen/dialogs_quick_ref.cpp
@@ -0,0 +1,88 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#include "xeen/dialogs_quick_ref.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void QuickReferenceDialog::show(XeenEngine *vm) {
+ QuickReferenceDialog *dlg = new QuickReferenceDialog(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void QuickReferenceDialog::execute() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Common::String lines[8];
+
+ events.setCursor(0);
+
+ for (uint idx = 0; idx < (combat._globalCombat == 2 ? combat._combatParty.size() :
+ party._activeParty.size()); ++idx) {
+ Character &c = combat._globalCombat == 2 ? *combat._combatParty[idx] :
+ party._activeParty[idx];
+ Condition condition = c.worstCondition();
+ lines[idx] = Common::String::format(QUICK_REF_LINE,
+ idx * 10 + 24, idx + 1, c._name.c_str(),
+ CLASS_NAMES[c._class][0], CLASS_NAMES[c._class][1], CLASS_NAMES[c._class][2],
+ c.statColor(c.getCurrentLevel(), c._level._permanent), c._level._permanent,
+ c.statColor(c._currentHp, c.getMaxHP()), c._currentHp,
+ c.statColor(c._currentSp, c.getMaxSP()), c._currentSp,
+ c.statColor(c.getArmorClass(), c.getArmorClass(true)), c.getArmorClass(),
+ CONDITION_COLORS[condition],
+ CONDITION_NAMES[condition][0], CONDITION_NAMES[condition][1],
+ CONDITION_NAMES[condition][2], CONDITION_NAMES[condition][3]
+ );
+ }
+
+ int food = (party._food / party._activeParty.size()) / 3;
+ Common::String msg = Common::String::format(QUICK_REFERENCE,
+ lines[0].c_str(), lines[1].c_str(), lines[2].c_str(),
+ lines[3].c_str(), lines[4].c_str(), lines[5].c_str(),
+ lines[6].c_str(), lines[7].c_str(),
+ party._gold, party._gems,
+ food, food == 1 ? "" : "s"
+ );
+
+ Window &w = screen._windows[24];
+ bool windowOpen = w._enabled;
+ if (!windowOpen)
+ w.open();
+ w.writeString(msg);
+ w.update();
+
+ // Wait for a key/mouse press
+ events.clearEvents();
+ while (!_vm->shouldQuit() && !events.isKeyMousePressed())
+ events.pollEventsAndWait();
+ events.clearEvents();
+
+ if (!windowOpen)
+ w.close();
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_quick_ref.h b/engines/xeen/dialogs_quick_ref.h
new file mode 100644
index 0000000000..0c1b8e3f91
--- /dev/null
+++ b/engines/xeen/dialogs_quick_ref.h
@@ -0,0 +1,43 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_QUICK_REF_H
+#define XEEN_DIALOGS_QUICK_REF_H
+
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class QuickReferenceDialog : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ QuickReferenceDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_QUICK_REF_H */
diff --git a/engines/xeen/dialogs_spells.cpp b/engines/xeen/dialogs_spells.cpp
new file mode 100644
index 0000000000..e7bbbf5cbf
--- /dev/null
+++ b/engines/xeen/dialogs_spells.cpp
@@ -0,0 +1,1030 @@
+/* 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.
+ *
+ */
+
+#include "xeen/dialogs_spells.h"
+#include "xeen/dialogs_input.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/resources.h"
+#include "xeen/spells.h"
+#include "xeen/sprites.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+Character *SpellsDialog::show(XeenEngine *vm, ButtonContainer *priorDialog,
+ Character *c, int isCasting) {
+ SpellsDialog *dlg = new SpellsDialog(vm);
+ Character *result = dlg->execute(priorDialog, c, isCasting);
+ delete dlg;
+
+ return result;
+}
+
+Character *SpellsDialog::execute(ButtonContainer *priorDialog, Character *c, int isCasting) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Spells &spells = *_vm->_spells;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ loadButtons();
+
+ int castingCopy = isCasting;
+ isCasting &= 0x7f;
+ int selection = -1;
+ int topIndex = 0;
+ int newSelection;
+ screen._windows[25].open();
+
+ do {
+ if (!isCasting) {
+ if (!c->guildMember()) {
+ sound.playSample(nullptr, 0);
+ intf._overallFrame = 5;
+ File f(isDarkCc ? "skull1.voc" : "guild11.voc");
+ sound.playSample(&f, 1);
+ break;
+ }
+
+ Common::String title = Common::String::format(BUY_SPELLS, c->_name.c_str());
+ Common::String msg = Common::String::format(GUILD_OPTIONS,
+ title.c_str(), XeenEngine::printMil(party._gold).c_str());
+ screen._windows[10].writeString(msg);
+
+ warning("TODO: Sprite draw using previously used button sprites");
+ }
+
+ _spells.clear();
+ const char *errorMsg = setSpellText(c, castingCopy);
+ screen._windows[25].writeString(Common::String::format(SPELLS_FOR,
+ errorMsg == nullptr ? SPELL_LINES_0_TO_9 : "",
+ c->_name.c_str()));
+
+ // Setup and write out spell list
+ const char *names[10];
+ int colors[10];
+ Common::String emptyStr = "";
+ Common::fill(&names[0], &names[10], emptyStr.c_str());
+ Common::fill(&colors[0], &colors[10], 9);
+
+ for (int idx = 0; idx < 10; ++idx) {
+ if ((topIndex + idx) < (int)_spells.size()) {
+ names[idx] = _spells[topIndex + idx]._name.c_str();
+ colors[idx] = _spells[topIndex + idx]._color;
+ }
+ }
+
+ if (selection >= topIndex && selection < (topIndex + 10))
+ colors[selection - topIndex] = 15;
+ if (_spells.size() == 0)
+ names[0] = errorMsg;
+
+ screen._windows[37].writeString(Common::String::format(SPELLS_DIALOG_SPELLS,
+ colors[0], names[0], colors[1], names[1], colors[2], names[2],
+ colors[3], names[3], colors[4], names[4], colors[5], names[5],
+ colors[6], names[6], colors[7], names[7], colors[8], names[8],
+ colors[9], names[9],
+ isCasting ? SPELL_PTS : GOLD,
+ isCasting ? c->_currentSp : party._gold
+ ));
+
+ _scrollSprites.draw(screen, 4, Common::Point(39, 26));
+ _scrollSprites.draw(screen, 0, Common::Point(187, 26));
+ _scrollSprites.draw(screen, 2, Common::Point(187, 111));
+ if (isCasting)
+ _scrollSprites.draw(screen._windows[25], 5, Common::Point(132, 123));
+
+ screen._windows[25].update();
+
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ } while (!_vm->shouldQuit() && !_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ if (_vm->_mode != MODE_COMBAT) {
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ c = &party._activeParty[_buttonValue];
+ spells._lastCaster = _buttonValue;
+ intf.highlightChar(_buttonValue);
+
+ if (_vm->_mode == MODE_17) {
+ screen._windows[10].writeString(Common::String::format(GUILD_OPTIONS,
+ XeenEngine::printMil(party._gold).c_str(), GUILD_TEXT, c->_name.c_str()));
+ } else {
+ int category;
+ switch (c->_class) {
+ case CLASS_ARCHER:
+ case CLASS_SORCERER:
+ category = 1;
+ break;
+ case CLASS_DRUID:
+ case CLASS_RANGER:
+ category = 2;
+ break;
+ default:
+ category = 0;
+ break;
+ }
+
+ int spellIndex = (c->_currentSpell == -1) ? 39 : c->_currentSpell;
+ int spellId = SPELLS_ALLOWED[category][spellIndex];
+ screen._windows[10].writeString(Common::String::format(CAST_SPELL_DETAILS,
+ c->_name.c_str(), spells._spellNames[spellId].c_str(),
+ spells.calcSpellPoints(spellId, c->getCurrentLevel()),
+ SPELL_GEM_COST[spellId], c->_currentSp));
+ }
+
+ if (priorDialog != nullptr)
+ priorDialog->drawButtons(&screen);
+ screen._windows[10].update();
+ }
+ }
+ break;
+
+ case Common::KEYCODE_RETURN:
+ case Common::KEYCODE_KP_ENTER:
+ case Common::KEYCODE_s:
+ if (selection != -1)
+ _buttonValue = Common::KEYCODE_ESCAPE;
+ break;
+
+ case Common::KEYCODE_ESCAPE:
+ selection = -1;
+ _buttonValue = Common::KEYCODE_ESCAPE;
+ break;
+
+ case Common::KEYCODE_0:
+ case Common::KEYCODE_1:
+ case Common::KEYCODE_2:
+ case Common::KEYCODE_3:
+ case Common::KEYCODE_4:
+ case Common::KEYCODE_5:
+ case Common::KEYCODE_6:
+ case Common::KEYCODE_7:
+ case Common::KEYCODE_8:
+ case Common::KEYCODE_9:
+ newSelection = topIndex + ((_buttonValue == Common::KEYCODE_0) ? 9 :
+ (_buttonValue - Common::KEYCODE_1));
+
+ if (newSelection < (int)_spells.size()) {
+ int expenseFactor = 0;
+ int category = 0;
+
+ switch (c->_class) {
+ case CLASS_PALADIN:
+ expenseFactor = 1;
+ category = 0;
+ break;
+ case CLASS_ARCHER:
+ expenseFactor = 1;
+ category = 1;
+ break;
+ case CLASS_CLERIC:
+ category = 0;
+ break;
+ case CLASS_SORCERER:
+ category = 1;
+ break;
+ case CLASS_DRUID:
+ category = 2;
+ break;
+ case CLASS_RANGER:
+ expenseFactor = 1;
+ category = 2;
+ break;
+ default:
+ break;
+ }
+
+ int spellIndex = _spells[newSelection]._spellIndex;
+ int spellId = SPELLS_ALLOWED[category][spellIndex];
+ int spellCost = spells.calcSpellCost(spellId, expenseFactor);
+
+ if (isCasting) {
+ selection = newSelection;
+ } else {
+ Common::String spellName = _spells[newSelection]._name;
+ Common::String msg = (castingCopy & 0x80) ?
+ Common::String::format(SPELLS_PRESS_A_KEY, spellName.c_str()) :
+ Common::String::format(SPELLS_PURCHASE, spellName.c_str(), spellCost);
+
+ if (Confirm::show(_vm, msg, castingCopy + 1)) {
+ if (party.subtract(0, spellCost, 0, WT_FREEZE_WAIT)) {
+ ++c->_spells[spellIndex];
+ sound.playSample(nullptr, 0);
+ intf._overallFrame = 0;
+ File f(isDarkCc ? "guild12.voc" : "parrot2.voc");
+ sound.playSample(&f, 1);
+ } else {
+ sound.playFX(21);
+ }
+ }
+ }
+ }
+ break;
+
+ case Common::KEYCODE_PAGEUP:
+ case Common::KEYCODE_KP9:
+ topIndex = MAX((int)topIndex - 10, 0);
+ break;
+
+ case Common::KEYCODE_PAGEDOWN:
+ case Common::KEYCODE_KP3:
+ topIndex = MIN(topIndex + 10, (((int)_spells.size() - 1) / 10) * 10);
+ break;
+
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ if (topIndex > 0)
+ --topIndex;
+ break;
+
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ if (topIndex < ((int)_spells.size() - 10))
+ ++topIndex;
+ break;
+ }
+ } while (!_vm->shouldQuit() && _buttonValue != Common::KEYCODE_ESCAPE);
+
+ screen._windows[25].close();
+
+ if (_vm->shouldQuit())
+ selection = -1;
+ if (isCasting && selection != -1)
+ c->_currentSpell = _spells[selection]._spellIndex;
+
+ return c;
+}
+
+void SpellsDialog::loadButtons() {
+ _iconSprites.load("main.icn");
+ _scrollSprites.load("scroll.icn");
+ addButton(Common::Rect(187, 26, 198, 36), Common::KEYCODE_UP, &_scrollSprites);
+ addButton(Common::Rect(187, 111, 198, 121), Common::KEYCODE_DOWN, &_scrollSprites);
+ addButton(Common::Rect(40, 28, 187, 36), Common::KEYCODE_1);
+ addButton(Common::Rect(40, 37, 187, 45), Common::KEYCODE_2);
+ addButton(Common::Rect(40, 46, 187, 54), Common::KEYCODE_3);
+ addButton(Common::Rect(40, 55, 187, 63), Common::KEYCODE_4);
+ addButton(Common::Rect(40, 64, 187, 72), Common::KEYCODE_5);
+ addButton(Common::Rect(40, 73, 187, 81), Common::KEYCODE_6);
+ addButton(Common::Rect(40, 82, 187, 90), Common::KEYCODE_7);
+ addButton(Common::Rect(40, 91, 187, 99), Common::KEYCODE_8);
+ addButton(Common::Rect(40, 100, 187, 108), Common::KEYCODE_9);
+ addButton(Common::Rect(40, 109, 187, 117), Common::KEYCODE_0);
+ addButton(Common::Rect(174, 123, 198, 133), Common::KEYCODE_ESCAPE);
+ addButton(Common::Rect(187, 35, 198, 73), Common::KEYCODE_PAGEUP);
+ addButton(Common::Rect(187, 74, 198, 112), Common::KEYCODE_PAGEDOWN);
+ addButton(Common::Rect(132, 123, 168, 133), Common::KEYCODE_s);
+ addPartyButtons(_vm);
+}
+
+const char *SpellsDialog::setSpellText(Character *c, int isCasting) {
+ Party &party = *_vm->_party;
+ Spells &spells = *_vm->_spells;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ int expenseFactor = 0;
+ int currLevel = c->getCurrentLevel();
+ int category;
+
+ if ((isCasting & 0x7f) == 0) {
+ switch (c->_class) {
+ case CLASS_PALADIN:
+ expenseFactor = 1;
+ category = 0;
+ break;
+ case CLASS_ARCHER:
+ expenseFactor = 1;
+ category = 1;
+ break;
+ case CLASS_CLERIC:
+ category = 0;
+ break;
+ case CLASS_SORCERER:
+ category = 1;
+ break;
+ case CLASS_DRUID:
+ category = 2;
+ break;
+ case CLASS_RANGER:
+ expenseFactor = 1;
+ category = 2;
+ break;
+ default:
+ category = -1;
+ break;
+ }
+
+ if (category != -1) {
+ if (party._mazeId == 49 || party._mazeId == 37) {
+ for (uint spellId = 0; spellId < 76; ++spellId) {
+ int idx = 0;
+ while (idx < MAX_SPELLS_PER_CLASS && SPELLS_ALLOWED[category][idx] == spellId)
+ ++idx;
+
+ // Handling if the spell is appropriate for the character's class
+ if (idx < MAX_SPELLS_PER_CLASS) {
+ if (!c->_spells[idx] || (isCasting & 0x80)) {
+ int cost = spells.calcSpellCost(SPELLS_ALLOWED[category][idx], expenseFactor);
+ _spells.push_back(SpellEntry(Common::String::format("\x3l%s\x3r\x9""000%u",
+ spells._spellNames[SPELLS_ALLOWED[category][idx]].c_str(), cost),
+ idx, spellId));
+ }
+ }
+ }
+ } else if (isDarkCc) {
+ int groupIndex = (party._mazeId - 29) / 2;
+ for (int spellId = DARK_SPELL_RANGES[groupIndex][0];
+ spellId < DARK_SPELL_RANGES[groupIndex][1]; ++spellId) {
+ int idx = 0;
+ while (idx < 40 && SPELLS_ALLOWED[category][idx] ==
+ DARK_SPELL_OFFSETS[category][spellId]);
+
+ if (idx < 40) {
+ if (!c->_spells[idx] || (isCasting & 0x80)) {
+ int cost = spells.calcSpellCost(SPELLS_ALLOWED[category][idx], expenseFactor);
+ _spells.push_back(SpellEntry(Common::String::format("\x3l%s\x3r\x9""000%u",
+ spells._spellNames[SPELLS_ALLOWED[category][idx]].c_str(), cost),
+ idx, spellId));
+ }
+ }
+ }
+ } else {
+ for (int spellId = 0; spellId < 20; ++spellId) {
+ int idx = 0;
+ while (CLOUDS_SPELL_OFFSETS[party._mazeId - 29][spellId] !=
+ (int)SPELLS_ALLOWED[category][idx] && idx < 40) ;
+
+ if (idx < 40) {
+ if (!c->_spells[idx] || (isCasting & 0x80)) {
+ int cost = spells.calcSpellCost(SPELLS_ALLOWED[category][idx], expenseFactor);
+ _spells.push_back(SpellEntry(Common::String::format("\x3l%s\x3r\x9""000%u",
+ spells._spellNames[SPELLS_ALLOWED[category][idx]].c_str(), cost),
+ idx, spellId));
+ }
+ }
+ }
+ }
+ }
+
+ if (c->getMaxSP() == 0)
+ return NOT_A_SPELL_CASTER;
+
+ } else if ((isCasting & 0x7f) == 1) {
+ switch (c->_class) {
+ case CLASS_ARCHER:
+ case CLASS_SORCERER:
+ category = 1;
+ break;
+ case CLASS_DRUID:
+ case CLASS_RANGER:
+ category = 2;
+ break;
+ case CLASS_PALADIN:
+ case CLASS_CLERIC:
+ default:
+ category = 0;
+ break;
+ }
+
+ if (c->getMaxSP() == 0) {
+ return NOT_A_SPELL_CASTER;
+ } else {
+ for (int spellIndex = 0; spellIndex < (MAX_SPELLS_PER_CLASS - 1); ++spellIndex) {
+ if (c->_spells[spellIndex]) {
+ int spellId = SPELLS_ALLOWED[category][spellIndex];
+ int gemCost = SPELL_GEM_COST[spellId];
+ int spCost = spells.calcSpellPoints(spellId, currLevel);
+
+ Common::String msg = Common::String::format("\x3l%s\x3r\x9""000%u/%u",
+ spells._spellNames[spellId].c_str(), spCost, gemCost);
+ _spells.push_back(SpellEntry(msg, spellIndex, spellId));
+ }
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+/*------------------------------------------------------------------------*/
+
+int CastSpell::show(XeenEngine *vm) {
+ Combat &combat = *vm->_combat;
+ Interface &intf = *vm->_interface;
+ Party &party = *vm->_party;
+ Spells &spells = *vm->_spells;
+ int charNum;
+
+ // Get which character is doing the casting
+ if (vm->_mode == MODE_COMBAT) {
+ charNum = combat._whosTurn;
+ } else if (spells._lastCaster >= 0 && spells._lastCaster < (int)party._activeParty.size()) {
+ charNum = spells._lastCaster;
+ } else {
+ for (charNum = (int)party._activeParty.size() - 1; charNum >= 0; --charNum) {
+ if (party._activeParty[charNum]._hasSpells) {
+ spells._lastCaster = charNum;
+ break;
+ }
+ }
+ }
+
+ Character *c = &party._activeParty[charNum];
+ intf.highlightChar(charNum);
+
+ CastSpell *dlg = new CastSpell(vm);
+ int spellId = dlg->execute(c);
+ delete dlg;
+
+ return spellId;
+}
+
+int CastSpell::show(XeenEngine *vm, Character *&c) {
+ CastSpell *dlg = new CastSpell(vm);
+ int spellId = dlg->execute(c);
+ delete dlg;
+
+ return spellId;
+}
+
+int CastSpell::execute(Character *&c) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Spells &spells = *_vm->_spells;
+ Window &w = screen._windows[10];
+
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_3;
+
+ w.open();
+ loadButtons();
+
+ int spellId = -1;
+ bool redrawFlag = true;
+ do {
+ if (redrawFlag) {
+ int category = c->getClassCategory();
+ int spellIndex = c->_currentSpell != -1 ? c->_currentSpell : 39;
+ spellId = SPELLS_ALLOWED[category][spellIndex];
+ int gemCost = SPELL_GEM_COST[spellId];
+ int spCost = spells.calcSpellPoints(spellId, c->getCurrentLevel());
+
+ w.writeString(Common::String::format(CAST_SPELL_DETAILS,
+ c->_name.c_str(), spells._spellNames[spellId].c_str(),
+ spCost, gemCost, c->_currentSp));
+ drawButtons(&screen);
+ w.update();
+
+ redrawFlag = false;
+ }
+
+ events.updateGameCounter();
+ intf.draw3d(true);
+
+ // Wait for event or time expiry
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ } while (!_vm->shouldQuit() && events.timeElapsed() < 1 && !_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ // Only allow changing character if the party is not in combat
+ if (oldMode != MODE_COMBAT) {
+ _vm->_mode = oldMode;
+ _buttonValue -= Common::KEYCODE_F1;
+
+ if (_buttonValue < (int)party._activeParty.size()) {
+ c = &party._activeParty[_buttonValue];
+ intf.highlightChar(_buttonValue);
+ redrawFlag = true;
+ break;
+ }
+ }
+ break;
+
+ case Common::KEYCODE_ESCAPE:
+ spellId = -1;
+ break;
+
+ case Common::KEYCODE_c:
+ // Cast spell - return the selected spell Id to be cast
+ if (c->_currentSpell != -1 && !c->noActions())
+ _buttonValue = Common::KEYCODE_ESCAPE;
+ break;
+
+ case Common::KEYCODE_n:
+ // Select new spell
+ _vm->_mode = oldMode;
+ c = SpellsDialog::show(_vm, this, c, 1);
+ redrawFlag = true;
+ break;
+
+ default:
+ break;
+ }
+ } while (!_vm->shouldQuit() && _buttonValue != Common::KEYCODE_ESCAPE);
+
+ w.close();
+ intf.unhighlightChar();
+
+ if (_vm->shouldQuit())
+ spellId = -1;
+
+ _vm->_mode = oldMode;
+ return spellId;
+}
+
+void CastSpell::loadButtons() {
+ _iconSprites.load("cast.icn");
+ addButton(Common::Rect(234, 108, 259, 128), Common::KEYCODE_c, &_iconSprites);
+ addButton(Common::Rect(261, 108, 285, 128), Common::KEYCODE_n, &_iconSprites);
+ addButton(Common::Rect(288, 108, 312, 128), Common::KEYCODE_ESCAPE, &_iconSprites);
+ addPartyButtons(_vm);
+}
+
+/*------------------------------------------------------------------------*/
+
+Character *SpellOnWho::show(XeenEngine *vm, int spellId) {
+ SpellOnWho *dlg = new SpellOnWho(vm);
+ int result = dlg->execute(spellId);
+ delete dlg;
+
+ if (result == -1)
+ return nullptr;
+
+ Combat &combat = *vm->_combat;
+ Party &party = *vm->_party;
+ return combat._combatMode == 2 ? combat._combatParty[result] :
+ &party._activeParty[result];
+}
+
+int SpellOnWho::execute(int spellId) {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Spells &spells = *_vm->_spells;
+ Window &w = screen._windows[16];
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_3;
+ int result = 999;
+
+ w.open();
+ w.writeString(ON_WHO);
+ w.update();
+ addPartyButtons(_vm);
+
+ while (result == 999) {
+ do {
+ events.updateGameCounter();
+ intf.draw3d(true);
+
+ do {
+ events.pollEventsAndWait();
+ if (_vm->shouldQuit())
+ return -1;
+
+ checkEvents(_vm);
+ } while (!_buttonValue && events.timeElapsed() < 1);
+ } while (!_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_ESCAPE:
+ result = -1;
+ spells.addSpellCost(*combat._oldCharacter, spellId);
+ break;
+
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)(combat._combatMode == 2 ? combat._combatParty.size() :
+ party._activeParty.size())) {
+ result = _buttonValue;
+ }
+ break;
+ }
+ }
+
+ w.close();
+ _vm->_mode = oldMode;
+ return result;
+}
+
+/*------------------------------------------------------------------------*/
+
+int SelectElement::show(XeenEngine *vm, int spellId) {
+ SelectElement *dlg = new SelectElement(vm);
+ int result = dlg->execute(spellId);
+ delete dlg;
+
+ return result;
+}
+
+int SelectElement::execute(int spellId) {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ Spells &spells = *_vm->_spells;
+ Window &w = screen._windows[15];
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_3;
+ int result = 999;
+
+ loadButtons();
+
+ w.open();
+ w.writeString(WHICH_ELEMENT1);
+ drawButtons(&screen);
+ w.update();
+
+ while (result == 999) {
+ do {
+ events.updateGameCounter();
+ intf.draw3d(true);
+ w.frame();
+ w.writeString(WHICH_ELEMENT2);
+ drawButtons(&screen);
+ w.update();
+
+ do {
+ events.pollEventsAndWait();
+ if (_vm->shouldQuit())
+ return -1;
+
+ checkEvents(_vm);
+ } while (!_buttonValue && events.timeElapsed() < 1);
+ } while (!_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_ESCAPE:
+ result = -1;
+ spells.addSpellCost(*combat._oldCharacter, spellId);
+ break;
+
+ case Common::KEYCODE_a:
+ result = DT_POISON;
+ break;
+ case Common::KEYCODE_c:
+ result = DT_COLD;
+ break;
+ case Common::KEYCODE_e:
+ result = DT_ELECTRICAL;
+ break;
+ case Common::KEYCODE_f:
+ result = DT_FIRE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ w.close();
+ _vm->_mode = oldMode;
+ return result;
+}
+
+void SelectElement::loadButtons() {
+ _iconSprites.load("element.icn");
+ addButton(Common::Rect(60, 92, 84, 112), Common::KEYCODE_f, &_iconSprites);
+ addButton(Common::Rect(90, 92, 114, 112), Common::KEYCODE_e, &_iconSprites);
+ addButton(Common::Rect(120, 92, 144, 112), Common::KEYCODE_c, &_iconSprites);
+ addButton(Common::Rect(150, 92, 174, 112), Common::KEYCODE_a, &_iconSprites);
+}
+
+/*------------------------------------------------------------------------*/
+
+void NotWhileEngaged::show(XeenEngine *vm, int spellId) {
+ NotWhileEngaged *dlg = new NotWhileEngaged(vm);
+ dlg->execute(spellId);
+ delete dlg;
+}
+
+void NotWhileEngaged::execute(int spellId) {
+ EventsManager &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Spells &spells = *_vm->_spells;
+ Window &w = screen._windows[6];
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_3;
+
+ w.open();
+ w.writeString(Common::String::format(CANT_CAST_WHILE_ENGAGED,
+ spells._spellNames[spellId].c_str()));
+ w.update();
+
+ while (!_vm->shouldQuit() && !events.isKeyMousePressed())
+ events.pollEventsAndWait();
+ events.clearEvents();
+
+ w.close();
+ _vm->_mode = oldMode;
+}
+
+/*------------------------------------------------------------------------*/
+
+bool LloydsBeacon::show(XeenEngine *vm) {
+ LloydsBeacon *dlg = new LloydsBeacon(vm);
+ bool result = dlg->execute();
+ delete dlg;
+
+ return result;
+}
+
+bool LloydsBeacon::execute() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Window &w = screen._windows[10];
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ Character &c = *combat._oldCharacter;
+
+ loadButtons();
+
+ if (!c._lloydMap) {
+ // No destination previously set, so have a default ready
+ if (isDarkCc) {
+ c._lloydSide = 1;
+ c._lloydPosition = Common::Point(25, 21);
+ c._lloydMap = 29;
+ } else {
+ c._lloydSide = 0;
+ c._lloydPosition = Common::Point(18, 4);
+ c._lloydMap = 28;
+ }
+ }
+
+ // Open up the text file for the destination map and read in it's name
+ File textFile(Common::String::format("%s%c%03d.txt",
+ c._lloydSide == 0 ? "xeen" : "dark",
+ c._lloydMap >= 100 ? 'x' : '0',
+ c._lloydMap));
+ Common::String mapName = textFile.readString();
+ textFile.close();
+
+ // Display the dialog
+ w.open();
+ w.writeString(Common::String::format(LLOYDS_BEACON,
+ mapName.c_str(), c._lloydPosition.x, c._lloydPosition.y));
+ drawButtons(&screen);
+ w.update();
+
+ bool result = true;
+ do {
+ do {
+ events.updateGameCounter();
+ intf.draw3d(true);
+
+ do {
+ events.pollEventsAndWait();
+ if (_vm->shouldQuit())
+ return true;
+
+ checkEvents(_vm);
+ } while (!_buttonValue && events.timeElapsed() < 1);
+ } while (!_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_r:
+ if (!isDarkCc && c._lloydMap >= 75 && c._lloydMap <= 78 && !party._cloudsEnd) {
+ result = false;
+ } else {
+ sound.playFX(51);
+ map._loadDarkSide = isDarkCc;
+ if (c._lloydMap != party._mazeId || c._lloydSide != (isDarkCc ? 1 : 0)) {
+ map.load(c._lloydMap);
+ }
+
+ party._mazePosition = c._lloydPosition;
+ }
+
+ _buttonValue = Common::KEYCODE_ESCAPE;
+ break;
+
+ case Common::KEYCODE_s:
+ case Common::KEYCODE_t:
+ sound.playFX(20);
+ c._lloydMap = party._mazeId;
+ c._lloydPosition = party._mazePosition;
+ c._lloydSide = isDarkCc ? 1 : 0;
+
+ _buttonValue = Common::KEYCODE_ESCAPE;
+ break;
+ }
+ } while (_buttonValue != Common::KEYCODE_ESCAPE);
+
+ w.close();
+ return result;
+}
+
+void LloydsBeacon::loadButtons() {
+ _iconSprites.load("lloyds.icn");
+
+ addButton(Common::Rect(281, 108, 305, 128), Common::KEYCODE_r, &_iconSprites);
+ addButton(Common::Rect(242, 108, 266, 128), Common::KEYCODE_t, &_iconSprites);
+}
+
+/*------------------------------------------------------------------------*/
+
+int Teleport::show(XeenEngine *vm) {
+ Teleport *dlg = new Teleport(vm);
+ int result = dlg->execute();
+ delete dlg;
+
+ return result;
+}
+
+int Teleport::execute() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Window &w = screen._windows[6];
+ Common::String num;
+
+ w.open();
+ w.writeString(Common::String::format(HOW_MANY_SQUARES,
+ DIRECTION_TEXT[party._mazeDirection]));
+ w.update();
+ int lineSize = Input::show(_vm, &w, num, 1, 200, true);
+ w.close();
+
+ if (!lineSize)
+ return -1;
+ int numSquares = atoi(num.c_str());
+ Common::Point pt = party._mazePosition;
+ int v;
+
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ pt.y += numSquares;
+ break;
+ case DIR_EAST:
+ pt.x += numSquares;
+ break;
+ case DIR_SOUTH:
+ pt.y -= numSquares;
+ break;
+ case DIR_WEST:
+ pt.x -= numSquares;
+ break;
+ default:
+ break;
+ }
+
+ v = map.mazeLookup(pt, map._isOutdoors ? 0xF : 0xFFFF, 0);
+
+ if ((v != (map._isOutdoors ? 0 : INVALID_CELL)) &&
+ (!map._isOutdoors || v == SURFTYPE_DWATER)) {
+ party._mazePosition = pt;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+int TownPortal::show(XeenEngine *vm) {
+ TownPortal *dlg = new TownPortal(vm);
+ int townNumber = dlg->execute();
+ delete dlg;
+
+ return townNumber;
+}
+
+int TownPortal::execute() {
+ Map &map = *_vm->_map;
+ Screen &screen = *_vm->_screen;
+ Window &w = screen._windows[20];
+ Common::String townNames[5];
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_FF;
+
+ // Build up a lsit of the names of the towns on the current side of Xeen
+ for (int idx = 0; idx < 5; ++idx) {
+ File f(Common::String::format("%s%04d.txt",
+ map._sideTownPortal ? "dark" : "xeen",
+ TOWN_MAP_NUMBERS[map._sideTownPortal][idx]));
+ townNames[idx] = f.readString();
+ f.close();
+ }
+
+ w.open();
+ w.writeString(Common::String::format(TOWN_PORTAL,
+ townNames[0].c_str(), townNames[1].c_str(), townNames[2].c_str(),
+ townNames[3].c_str(), townNames[4].c_str()
+ ));
+ w.update();
+
+ // Get the town number
+ int townNumber;
+ Common::String num;
+ do {
+ int result = Input::show(_vm, &w, num, 1, 160, true);
+ townNumber = !result ? 0 : atoi(num.c_str());
+ } while (townNumber > 5);
+
+ w.close();
+ _vm->_mode = oldMode;
+
+ return townNumber;
+}
+
+/*------------------------------------------------------------------------*/
+
+void IdentifyMonster::show(XeenEngine *vm) {
+ IdentifyMonster *dlg = new IdentifyMonster(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void IdentifyMonster::execute() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Window &w = screen._windows[17];
+ Common::String monsterDesc[3];
+
+ for (int monIndex = 0; monIndex < 3; ++monIndex) {
+ if (combat._attackMonsters[monIndex] == -1)
+ continue;
+
+ MazeMonster &monster = map._mobData._monsters[combat._attackMonsters[monIndex]];
+ MonsterStruct &monsterData = *monster._monsterData;
+
+ monsterDesc[monIndex] = Common::String::format(MONSTER_DETAILS,
+ monsterData._name.c_str(),
+ _vm->printK2(monster._hp).c_str(),
+ monsterData._accuracy, monsterData._numberOfAttacks,
+ MONSTER_SPECIAL_ATTACKS[monsterData._specialAttack]
+ );
+ }
+
+ sound.playFX(20);
+ w.open();
+ w.writeString(Common::String::format(IDENTIFY_MONSTERS,
+ monsterDesc[0].c_str(), monsterDesc[1].c_str(), monsterDesc[2].c_str()));
+ w.update();
+
+ do {
+ events.updateGameCounter();
+ intf.draw3d(false);
+ w.frame();
+ screen._windows[3].update();
+
+ events.wait(1);
+ } while (!events.isKeyMousePressed());
+
+ w.close();
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_spells.h b/engines/xeen/dialogs_spells.h
new file mode 100644
index 0000000000..35b2708f7a
--- /dev/null
+++ b/engines/xeen/dialogs_spells.h
@@ -0,0 +1,162 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_SPELLS_H
+#define XEEN_DIALOGS_SPELLS_H
+
+#include "common/array.h"
+#include "xeen/dialogs.h"
+#include "xeen/party.h"
+
+namespace Xeen {
+
+struct SpellEntry {
+ Common::String _name;
+ int _spellIndex;
+ int _spellId;
+ int _color;
+
+ SpellEntry(const Common::String &name, int spellIndex, int spellId) :
+ _name(name), _spellIndex(spellIndex), _spellId(spellId), _color(9) {}
+};
+
+class SpellsDialog : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+ SpriteResource _scrollSprites;
+ Common::Array<SpellEntry> _spells;
+
+ SpellsDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ Character *execute(ButtonContainer *priorDialog, Character *c, int isCasting);
+
+ void loadButtons();
+
+ const char *setSpellText(Character *c, int isCasting);
+public:
+ static Character *show(XeenEngine *vm, ButtonContainer *priorDialog,
+ Character *c, int isCasting);
+};
+
+class CastSpell : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ CastSpell(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute(Character *&c);
+
+ void loadButtons();
+public:
+ static int show(XeenEngine *vm);
+ static int show(XeenEngine *vm, Character *&c);
+};
+
+class SpellOnWho : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ SpellOnWho(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute(int spellId);
+public:
+ static Character *show(XeenEngine *vm, int spellId);
+};
+
+class SelectElement : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ SelectElement(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute(int spellId);
+
+ void loadButtons();
+public:
+ static int show(XeenEngine *vm, int spellId);
+};
+
+class NotWhileEngaged : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ NotWhileEngaged(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute(int spellId);
+public:
+ static void show(XeenEngine *vm, int spellId);
+};
+
+class LloydsBeacon : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ LloydsBeacon(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ bool execute();
+
+ void loadButtons();
+public:
+ static bool show(XeenEngine *vm);
+};
+
+class Teleport : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ Teleport(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute();
+public:
+ static int show(XeenEngine *vm);
+};
+
+class TownPortal : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ TownPortal(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute();
+public:
+ static int show(XeenEngine *vm);
+};
+
+class IdentifyMonster : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ IdentifyMonster(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_SPELLS_H */
diff --git a/engines/xeen/dialogs_whowill.cpp b/engines/xeen/dialogs_whowill.cpp
new file mode 100644
index 0000000000..fd72154cba
--- /dev/null
+++ b/engines/xeen/dialogs_whowill.cpp
@@ -0,0 +1,105 @@
+/* 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.
+ *
+ */
+
+#include "xeen/dialogs_whowill.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+int WhoWill::show(XeenEngine *vm, int message, int action, bool type) {
+ WhoWill *dlg = new WhoWill(vm);
+ int result = dlg->execute(message, action, type);
+ delete dlg;
+
+ return result;
+}
+
+int WhoWill::execute(int message, int action, bool type) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Scripts &scripts = *_vm->_scripts;
+ Town &town = *_vm->_town;
+ int numFrames;
+
+ if (party._activeParty.size() <= 1)
+ // Unless there's at least two characters, just return the first one
+ return 1;
+
+ screen._windows[38].close();
+ screen._windows[12].close();
+
+ Common::String actionStr = type ? map._events._text[action] : WHO_WILL_ACTIONS[action];
+ Common::String msg = Common::String::format(WHO_WILL, actionStr.c_str(),
+ WHO_ACTIONS[message], party._activeParty.size());
+
+ screen._windows[36].open();
+ screen._windows[36].writeString(msg);
+ screen._windows[36].update();
+
+ intf._face1State = map._headData[party._mazePosition.y][party._mazePosition.x]._left;
+ intf._face2State = map._headData[party._mazePosition.y][party._mazePosition.x]._right;
+
+ while (!_vm->shouldQuit()) {
+ events.updateGameCounter();
+
+ if (screen._windows[11]._enabled) {
+ town.drawTownAnim(0);
+ screen._windows[36].frame();
+ numFrames = 3;
+ } else {
+ intf.draw3d(false);
+ screen._windows[36].frame();
+ screen._windows[3].update();
+ numFrames = 1;
+ }
+
+ events.wait(numFrames, true);
+ checkEvents(_vm);
+ if (!_buttonValue)
+ continue;
+
+ if (_buttonValue == 27) {
+ _buttonValue = 0;
+ break;
+ } else if (_buttonValue >= Common::KEYCODE_F1 && _buttonValue <= Common::KEYCODE_F6) {
+ _buttonValue -= Common::KEYCODE_F1 - 1;
+ if (_buttonValue > (int)party._activeParty.size())
+ continue;
+
+ if (party._activeParty[_buttonValue - 1].noActions())
+ continue;
+
+ scripts._whoWill = _buttonValue;
+ break;
+ }
+ }
+
+ intf._face1State = intf._face2State = 2;
+ screen._windows[36].close();
+ return _buttonValue;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_whowill.h b/engines/xeen/dialogs_whowill.h
new file mode 100644
index 0000000000..8080c36ddb
--- /dev/null
+++ b/engines/xeen/dialogs_whowill.h
@@ -0,0 +1,43 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_WHOWHILL_H
+#define XEEN_DIALOGS_WHOWHILL_H
+
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class WhoWill : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ WhoWill(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute(int message, int action, bool type);
+public:
+ static int show(XeenEngine *vm, int message, int action, bool type);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_WHOWHILL_H */
diff --git a/engines/xeen/events.cpp b/engines/xeen/events.cpp
new file mode 100644
index 0000000000..e50d689d9f
--- /dev/null
+++ b/engines/xeen/events.cpp
@@ -0,0 +1,179 @@
+/* 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.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "graphics/cursorman.h"
+#include "common/events.h"
+#include "common/endian.h"
+#include "engines/util.h"
+#include "xeen/xeen.h"
+#include "xeen/events.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+EventsManager::EventsManager(XeenEngine *vm) : _vm(vm),
+ _frameCounter(0), _priorFrameCounterTime(0), _gameCounter(0),
+ _leftButton(false), _rightButton(false), _sprites("mouse.icn"),
+ _keyCode(Common::KEYCODE_INVALID) {
+ Common::fill(&_gameCounters[0], &_gameCounters[6], 0);
+}
+
+EventsManager::~EventsManager() {
+}
+
+void EventsManager::setCursor(int cursorId) {
+ XSurface cursor;
+ _sprites.draw(cursor, cursorId);
+
+ CursorMan.replaceCursor(cursor.getPixels(), cursor.w, cursor.h, 0, 0, 0);
+ showCursor();
+}
+
+void EventsManager::showCursor() {
+ CursorMan.showMouse(true);
+}
+
+void EventsManager::hideCursor() {
+ CursorMan.showMouse(false);
+}
+
+bool EventsManager::isCursorVisible() {
+ return CursorMan.isVisible();
+}
+
+void EventsManager::pollEvents() {
+ uint32 timer = g_system->getMillis();
+ if (timer >= (_priorFrameCounterTime + GAME_FRAME_TIME)) {
+ _priorFrameCounterTime = timer;
+ nextFrame();
+ }
+
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_QUIT:
+ case Common::EVENT_RTL:
+ return;
+ case Common::EVENT_KEYDOWN:
+ // Check for debugger
+ if (event.kbd.keycode == Common::KEYCODE_d && (event.kbd.flags & Common::KBD_CTRL)) {
+ // Attach to the debugger
+ _vm->_debugger->attach();
+ _vm->_debugger->onFrame();
+ } else {
+ _keyCode = event.kbd.keycode;
+ }
+ break;
+ case Common::EVENT_MOUSEMOVE:
+ _mousePos = event.mouse;
+ break;
+ case Common::EVENT_LBUTTONDOWN:
+ _leftButton = true;
+ return;
+ case Common::EVENT_LBUTTONUP:
+ _leftButton = false;
+ return;
+ case Common::EVENT_RBUTTONDOWN:
+ _rightButton = true;
+ return;
+ case Common::EVENT_RBUTTONUP:
+ _rightButton = false;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void EventsManager::pollEventsAndWait() {
+ pollEvents();
+ g_system->delayMillis(10);
+}
+
+void EventsManager::clearEvents() {
+ _keyCode = Common::KEYCODE_INVALID;
+ _leftButton = _rightButton = false;
+
+}
+
+void EventsManager::debounceMouse() {
+ while (_leftButton && !_vm->shouldQuit()) {
+ pollEventsAndWait();
+ }
+}
+bool EventsManager::getKey(Common::KeyState &key) {
+ if (_keyCode == Common::KEYCODE_INVALID) {
+ return false;
+ } else {
+ key = _keyCode;
+ _keyCode = Common::KEYCODE_INVALID;
+ return true;
+ }
+}
+
+bool EventsManager::isKeyPending() const {
+ return _keyCode != Common::KEYCODE_INVALID;
+}
+
+bool EventsManager::isKeyMousePressed() {
+ bool result = _leftButton || _rightButton || isKeyPending();
+ debounceMouse();
+ clearEvents();
+
+ return result;
+}
+
+bool EventsManager::wait(uint numFrames, bool interruptable) {
+ while (!_vm->shouldQuit() && timeElapsed() < numFrames) {
+ pollEventsAndWait();
+ if (interruptable && (_leftButton || _rightButton || isKeyPending()))
+ return true;
+ }
+
+ return false;
+}
+
+void EventsManager::ipause(uint amount) {
+ updateGameCounter();
+ do {
+ _vm->_interface->draw3d(true);
+ pollEventsAndWait();
+ } while (!_vm->shouldQuit() && timeElapsed() < amount);
+}
+
+void EventsManager::nextFrame() {
+ ++_frameCounter;
+
+ // Allow debugger to update
+ _vm->_debugger->update();
+
+ // Update the screen
+ _vm->_screen->update();
+}
+
+/*------------------------------------------------------------------------*/
+
+GameEvent::GameEvent() {
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/events.h b/engines/xeen/events.h
new file mode 100644
index 0000000000..7847ecd81c
--- /dev/null
+++ b/engines/xeen/events.h
@@ -0,0 +1,121 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#ifndef XEEN_EVENTS_H
+#define XEEN_EVENTS_H
+
+#include "common/scummsys.h"
+#include "common/events.h"
+#include "xeen/sprites.h"
+
+namespace Xeen {
+
+#define GAME_FRAME_RATE (1000 / 18.2)
+
+class XeenEngine;
+
+class EventsManager {
+private:
+ XeenEngine *_vm;
+ uint32 _frameCounter;
+ uint32 _priorFrameCounterTime;
+ uint32 _gameCounter;
+ uint32 _gameCounters[6];
+ Common::KeyCode _keyCode;
+ SpriteResource _sprites;
+
+ /**
+ * Handles moving to the next game frame
+ */
+ void nextFrame();
+public:
+ bool _leftButton, _rightButton;
+ Common::Point _mousePos;
+public:
+ EventsManager(XeenEngine *vm);
+
+ ~EventsManager();
+
+ /*
+ * Set the cursor
+ */
+ void setCursor(int cursorId);
+
+ /**
+ * Show the mouse cursor
+ */
+ void showCursor();
+
+ /**
+ * Hide the mouse cursor
+ */
+ void hideCursor();
+
+ /**
+ * Returns if the mouse cursor is visible
+ */
+ bool isCursorVisible();
+
+ void pollEvents();
+
+ void pollEventsAndWait();
+
+ void clearEvents();
+
+ void debounceMouse();
+
+ bool getKey(Common::KeyState &key);
+
+ bool isKeyPending() const;
+
+ /**
+ * Returns true if a key or mouse press is pending
+ */
+ bool isKeyMousePressed();
+
+ void updateGameCounter() { _gameCounter = _frameCounter; }
+ void timeMark1() { _gameCounters[1] = _frameCounter; }
+ void timeMark2() { _gameCounters[2] = _frameCounter; }
+ void timeMark3() { _gameCounters[3] = _frameCounter; }
+ void timeMark4() { _gameCounters[4] = _frameCounter; }
+ void timeMark5() { _gameCounters[5] = _frameCounter; }
+
+ uint32 timeElapsed() const { return _frameCounter - _gameCounter; }
+ uint32 timeElapsed1() const { return _frameCounter - _gameCounters[1]; }
+ uint32 timeElapsed2() const { return _frameCounter - _gameCounters[2]; }
+ uint32 timeElapsed3() const { return _frameCounter - _gameCounters[3]; }
+ uint32 timeElapsed4() const { return _frameCounter - _gameCounters[4]; }
+ uint32 timeElapsed5() const { return _frameCounter - _gameCounters[5]; }
+
+ bool wait(uint numFrames, bool interruptable = false);
+
+ void ipause(uint amount);
+};
+
+class GameEvent {
+public:
+ GameEvent();
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_EVENTS_H */
diff --git a/engines/xeen/files.cpp b/engines/xeen/files.cpp
new file mode 100644
index 0000000000..bcee6bf9f6
--- /dev/null
+++ b/engines/xeen/files.cpp
@@ -0,0 +1,230 @@
+/* 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.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/archive.h"
+#include "common/memstream.h"
+#include "common/textconsole.h"
+#include "xeen/xeen.h"
+#include "xeen/files.h"
+
+namespace Xeen {
+
+uint16 BaseCCArchive::convertNameToId(const Common::String &resourceName) {
+ if (resourceName.empty())
+ return 0xffff;
+
+ Common::String name = resourceName;
+ name.toUppercase();
+
+ // Check if a resource number is being directly specified
+ if (name.size() == 4) {
+ char *endPtr;
+ uint16 num = (uint16)strtol(name.c_str(), &endPtr, 16);
+ if (!*endPtr)
+ return num;
+ }
+
+ const byte *msgP = (const byte *)name.c_str();
+ int total = *msgP++;
+ for (; *msgP; total += *msgP++) {
+ // Rotate the bits in 'total' right 7 places
+ total = (total & 0x007F) << 9 | (total & 0xFF80) >> 7;
+ }
+
+ return total;
+}
+
+void BaseCCArchive::loadIndex(Common::SeekableReadStream *stream) {
+ int count = stream->readUint16LE();
+
+ // Read in the data for the archive's index
+ byte *rawIndex = new byte[count * 8];
+ stream->read(rawIndex, count * 8);
+
+ // Decrypt the index
+ int ah = 0xac;
+ for (int i = 0; i < count * 8; ++i) {
+ rawIndex[i] = (byte)(((rawIndex[i] << 2 | rawIndex[i] >> 6) + ah) & 0xff);
+ ah += 0x67;
+ }
+
+ // Extract the index data into entry structures
+ _index.reserve(count);
+ const byte *entryP = &rawIndex[0];
+ for (int i = 0; i < count; ++i, entryP += 8) {
+ CCEntry entry;
+ entry._id = READ_LE_UINT16(entryP);
+ entry._offset = READ_LE_UINT32(entryP + 2) & 0xffffff;
+ entry._size = READ_LE_UINT16(entryP + 5);
+ assert(!entryP[7]);
+
+ _index.push_back(entry);
+ }
+
+ delete[] rawIndex;
+}
+
+bool BaseCCArchive::hasFile(const Common::String &name) const {
+ CCEntry ccEntry;
+ return getHeaderEntry(name, ccEntry);
+}
+
+bool BaseCCArchive::getHeaderEntry(const Common::String &resourceName, CCEntry &ccEntry) const {
+ uint16 id = convertNameToId(resourceName);
+
+ // Loop through the index
+ for (uint i = 0; i < _index.size(); ++i) {
+ if (_index[i]._id == id) {
+ ccEntry = _index[i];
+ return true;
+ }
+ }
+
+ // Could not find an entry
+ return false;
+}
+
+const Common::ArchiveMemberPtr BaseCCArchive::getMember(const Common::String &name) const {
+ if (!hasFile(name))
+ return Common::ArchiveMemberPtr();
+
+ return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
+}
+
+int BaseCCArchive::listMembers(Common::ArchiveMemberList &list) const {
+ // CC files don't maintain the original filenames, so we can't list it
+ return 0;
+}
+
+/*------------------------------------------------------------------------*/
+
+CCArchive::CCArchive(const Common::String &filename, bool encoded):
+ BaseCCArchive(), _filename(filename), _encoded(encoded) {
+ File f(filename);
+ loadIndex(&f);
+}
+
+CCArchive::CCArchive(const Common::String &filename, const Common::String &prefix,
+ bool encoded): BaseCCArchive(), _filename(filename),
+ _prefix(prefix), _encoded(encoded) {
+ _prefix.toLowercase();
+ File f(filename);
+ loadIndex(&f);
+}
+
+CCArchive::~CCArchive() {
+}
+
+bool CCArchive::getHeaderEntry(const Common::String &resourceName, CCEntry &ccEntry) const {
+ Common::String resName = resourceName;
+
+ if (!_prefix.empty() && resName.contains('|')) {
+ resName.toLowercase();
+ Common::String prefix = _prefix + "|";
+
+ if (!strncmp(resName.c_str(), prefix.c_str(), prefix.size()))
+ // Matching CC prefix, so strip it off and allow processing to
+ // continue onto the base getHeaderEntry method
+ resName = Common::String(resName.c_str() + prefix.size());
+ else
+ // Not matching prefix, so don't allow a match
+ return false;
+ }
+
+ return BaseCCArchive::getHeaderEntry(resName, ccEntry);
+}
+
+Common::SeekableReadStream *CCArchive::createReadStreamForMember(const Common::String &name) const {
+ CCEntry ccEntry;
+
+ if (getHeaderEntry(name, ccEntry)) {
+ // Open the correct CC file
+ Common::File f;
+ if (!f.open(_filename))
+ error("Could not open CC file");
+
+ // Read in the data for the specific resource
+ f.seek(ccEntry._offset);
+ byte *data = (byte *)malloc(ccEntry._size);
+ f.read(data, ccEntry._size);
+
+ if (_encoded) {
+ // Decrypt the data
+ for (int i = 0; i < ccEntry._size; ++i)
+ data[i] ^= 0x35;
+ }
+
+ // Return the data as a stream
+ return new Common::MemoryReadStream(data, ccEntry._size, DisposeAfterUse::YES);
+ }
+
+ return nullptr;
+}
+
+/*------------------------------------------------------------------------*/
+
+FileManager::FileManager(XeenEngine *vm) {
+ Common::File f;
+ int sideNum = 0;
+
+ _isDarkCc = vm->getGameID() == GType_DarkSide;
+ _sideArchives[0] = _sideArchives[1] = nullptr;
+
+ if (vm->getGameID() != GType_DarkSide) {
+ _sideArchives[0] = new CCArchive("xeen.cc", "xeen", true);
+ SearchMan.add("xeen", _sideArchives[0]);
+ sideNum = 1;
+ }
+
+ if (vm->getGameID() == GType_DarkSide || vm->getGameID() == GType_WorldOfXeen) {
+ _sideArchives[sideNum] = new CCArchive("dark.cc", "dark", true);
+ SearchMan.add("dark", _sideArchives[sideNum]);
+ }
+
+ SearchMan.add("intro", new CCArchive("intro.cc", "intro", true));
+}
+
+/*------------------------------------------------------------------------*/
+
+void File::openFile(const Common::String &filename) {
+ if (!Common::File::open(filename))
+ error("Could not open file - %s", filename.c_str());
+}
+
+void File::openFile(const Common::String &filename, Common::Archive &archive) {
+ if (!Common::File::open(filename, archive))
+ error("Could not open file - %s", filename.c_str());
+}
+
+Common::String File::readString() {
+ Common::String result;
+ char c;
+
+ while (pos() < size() && (c = (char)readByte()) != '\0')
+ result += c;
+
+ return result;
+}
+
+
+} // End of namespace Xeen
diff --git a/engines/xeen/files.h b/engines/xeen/files.h
new file mode 100644
index 0000000000..fc75031a72
--- /dev/null
+++ b/engines/xeen/files.h
@@ -0,0 +1,170 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_FILES_H
+#define XEEN_FILES_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/file.h"
+#include "common/serializer.h"
+#include "graphics/surface.h"
+#include "xeen/xsurface.h"
+
+namespace Xeen {
+
+class XeenEngine;
+class CCArchive;
+
+#define SYNC_AS(SUFFIX,STREAM,TYPE,SIZE) \
+ template<typename T> \
+ void syncAs ## SUFFIX(T &val, Version minVersion = 0, Version maxVersion = kLastVersion) { \
+ if (_version < minVersion || _version > maxVersion) \
+ return; \
+ if (_loadStream) \
+ val = static_cast<TYPE>(_loadStream->read ## STREAM()); \
+ else { \
+ TYPE tmp = (TYPE)val; \
+ _saveStream->write ## STREAM(tmp); \
+ } \
+ _bytesSynced += SIZE; \
+ }
+/*
+ * Main resource manager
+ */
+class FileManager {
+public:
+ bool _isDarkCc;
+ CCArchive *_sideArchives[2];
+public:
+ /**
+ * Instantiates the resource manager
+ */
+ FileManager(XeenEngine *vm);
+
+ void setGameCc(bool isDarkCc) { _isDarkCc = isDarkCc; }
+};
+
+/**
+ * Derived file class
+ */
+class File : public Common::File {
+public:
+ File() : Common::File() {}
+ File(const Common::String &filename) { openFile(filename); }
+ File(const Common::String &filename, Common::Archive &archive) {
+ openFile(filename, archive);
+ }
+ virtual ~File() {}
+
+ /**
+ * Opens the given file, throwing an error if it can't be opened
+ */
+ void openFile(const Common::String &filename);
+
+ /**
+ * Opens the given file, throwing an error if it can't be opened
+ */
+ void openFile(const Common::String &filename, Common::Archive &archive);
+
+ Common::String readString();
+};
+
+class XeenSerializer : public Common::Serializer {
+private:
+ Common::SeekableReadStream *_in;
+public:
+ XeenSerializer(Common::SeekableReadStream *in, Common::WriteStream *out) :
+ Common::Serializer(in, out), _in(in) {}
+
+ SYNC_AS(Sint8, Byte, int8, 1)
+
+ bool finished() const { return _in != nullptr && _in->pos() >= _in->size(); }
+};
+
+/**
+* Details of a single entry in a CC file index
+*/
+struct CCEntry {
+ uint16 _id;
+ uint32 _offset;
+ uint16 _size;
+
+ CCEntry() : _id(0), _offset(0), _size(0) {}
+ CCEntry(uint16 id, uint32 offset, uint32 size)
+ : _id(id), _offset(offset), _size(size) {
+ }
+};
+
+/**
+* Base Xeen CC file implementation
+*/
+class BaseCCArchive : public Common::Archive {
+protected:
+ Common::Array<CCEntry> _index;
+
+ /**
+ * Load the index of a given CC file
+ */
+ void loadIndex(Common::SeekableReadStream *stream);
+
+ /**
+ * Given a resource name, returns whether an entry exists, and returns
+ * the header index data for that entry
+ */
+ virtual bool getHeaderEntry(const Common::String &resourceName, CCEntry &ccEntry) const;
+public:
+ /**
+ * Hash a given filename to produce the Id that represents it
+ */
+ static uint16 convertNameToId(const Common::String &resourceName);
+public:
+ BaseCCArchive() {}
+
+ // Archive implementation
+ virtual bool hasFile(const Common::String &name) const;
+ virtual int listMembers(Common::ArchiveMemberList &list) const;
+ virtual const Common::ArchiveMemberPtr getMember(const Common::String &name) const;
+};
+
+/**
+* Xeen CC file implementation
+*/
+class CCArchive : public BaseCCArchive {
+private:
+ Common::String _filename;
+ Common::String _prefix;
+ bool _encoded;
+protected:
+ virtual bool getHeaderEntry(const Common::String &resourceName, CCEntry &ccEntry) const;
+public:
+ CCArchive(const Common::String &filename, bool encoded);
+ CCArchive(const Common::String &filename, const Common::String &prefix, bool encoded);
+ virtual ~CCArchive();
+
+ // Archive implementation
+ virtual Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_FILES_H */
diff --git a/engines/xeen/font.cpp b/engines/xeen/font.cpp
new file mode 100644
index 0000000000..87e16b5bdd
--- /dev/null
+++ b/engines/xeen/font.cpp
@@ -0,0 +1,347 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#include "common/endian.h"
+#include "xeen/font.h"
+#include "xeen/resources.h"
+
+namespace Xeen {
+
+FontSurface::FontSurface() : XSurface(), _fontData(nullptr), _bgColor(DEFAULT_BG_COLOR),
+ _fontReduced(false),_fontJustify(JUSTIFY_NONE), _msgWraps(false) {
+ setTextColor(0);
+}
+
+FontSurface::FontSurface(int wv, int hv) : XSurface(wv, hv), _fontData(nullptr), _msgWraps(false),
+ _bgColor(DEFAULT_BG_COLOR), _fontReduced(false), _fontJustify(JUSTIFY_NONE) {
+ create(w, h);
+ setTextColor(0);
+}
+
+void FontSurface::writeSymbol(int symbolId) {
+ const byte *srcP = &SYMBOLS[symbolId][0];
+
+ for (int yp = 0; yp < FONT_HEIGHT; ++yp) {
+ byte *destP = (byte *)getBasePtr(_writePos.x, _writePos.y + yp);
+
+ for (int xp = 0; xp < FONT_WIDTH; ++xp, ++destP) {
+ byte b = *srcP++;
+ if (b)
+ *destP = b;
+ }
+ }
+
+ _writePos.x += 8;
+}
+
+const char *FontSurface::writeString(const Common::String &s, const Common::Rect &bounds) {
+ _displayString = s.c_str();
+ assert(_fontData);
+
+ for (;;) {
+ const char *msgStartP = _displayString;
+ _msgWraps = false;
+
+ // Get the size of the string that can be displayed on the line
+ int xp = _fontJustify == JUSTIFY_CENTER ? bounds.left : _writePos.x;
+ while (!getNextCharWidth(xp)) {
+ if (xp >= bounds.right) {
+ --_displayString;
+ _msgWraps = true;
+ break;
+ }
+ }
+
+ // Get the end point of the text that can be displayed
+ const char *displayEnd = _displayString;
+ _displayString = msgStartP;
+
+ if (_msgWraps && _fontJustify != JUSTIFY_RIGHT && xp >= bounds.right) {
+ // Need to handle justification of text
+ // First, move backwards to find the end of the previous word
+ // for a convenient point to break the line at
+ const char *endP = displayEnd;
+ while (endP > _displayString && (*endP & 0x7f) != ' ')
+ --endP;
+
+ if (endP == _displayString) {
+ // There was no word breaks at all in the string
+ --displayEnd;
+ if (_fontJustify == JUSTIFY_NONE && _writePos.x != bounds.left) {
+ // Move to the next line
+ if (!newLine(bounds))
+ continue;
+ // Ran out of space to display string
+ break;
+ }
+ } else {
+ // Found word break, find end of previous word
+ while (endP > _displayString && (*endP & 0x7f) == ' ')
+ --endP;
+
+ displayEnd = endP;
+ }
+ }
+
+ // Justification adjustment
+ if (_fontJustify != JUSTIFY_NONE) {
+ // Figure out the width of the selected portion of the string
+ int totalWidth = 0;
+ while (!getNextCharWidth(totalWidth)) {
+ if (_displayString > displayEnd) {
+ if (*displayEnd == ' ') {
+ // Don't include any ending space as part of the total
+ totalWidth -= _fontReduced ? 4 : 5;
+ }
+ break;
+ }
+ }
+
+ // Reset starting position back to the start of the string portion
+ _displayString = msgStartP;
+
+ if (_fontJustify == JUSTIFY_RIGHT) {
+ // Right aligned
+ if (_writePos.x == bounds.left)
+ _writePos.x = bounds.right;
+ _writePos.x -= totalWidth + 1;
+ } else {
+ // Center aligned
+ if (_writePos.x == bounds.left)
+ _writePos.x = (bounds.left + bounds.right + 1 - totalWidth) / 2;
+ else
+ _writePos.x = (_writePos.x * 2 - totalWidth) / 2;
+ }
+ }
+
+ // Main character display loop
+ while (_displayString <= displayEnd) {
+ char c = getNextChar();
+
+ if (c == ' ') {
+ _writePos.x += _fontReduced ? 3 : 4;
+ } else if (c == '\r') {
+ fillRect(bounds, _bgColor);
+ addDirtyRect(bounds);
+ _writePos = Common::Point(bounds.left, bounds.top);
+ } else if (c == 1) {
+ // Turn off reduced font mode
+ _fontReduced = false;
+ } else if (c == 2) {
+ // Turn on reduced font mode
+ _fontReduced = true;
+ } else if (c == 3) {
+ // Justify text
+ c = getNextChar();
+ if (c == 'r')
+ _fontJustify = JUSTIFY_RIGHT;
+ else if (c == 'c')
+ _fontJustify = JUSTIFY_CENTER;
+ else
+ _fontJustify = JUSTIFY_NONE;
+ } else if (c == 4) {
+ // Draw an empty box of a given width
+ int wv = fontAtoi();
+ Common::Point pt = _writePos;
+ if (_fontJustify == JUSTIFY_RIGHT)
+ pt.x -= wv;
+
+ Common::Rect r(pt.x, pt.y, pt.x + wv, pt.y + (_fontReduced ? 9 : 10));
+ fillRect(r, _bgColor);
+ } else if (c == 5) {
+ continue;
+ } else if (c == 6) {
+ // Non-breakable space
+ writeChar(' ', bounds);
+ } else if (c == 7) {
+ // Set text background color
+ int bgColor = fontAtoi();
+ _bgColor = (bgColor < 0 || bgColor > 255) ? DEFAULT_BG_COLOR : bgColor;
+ } else if (c == 8) {
+ // Draw a character outline
+ c = getNextChar();
+ if (c == ' ') {
+ c = '\0';
+ _writePos.x -= 3;
+ } else {
+ if (c == 6)
+ c = ' ';
+ byte charSize = _fontData[0x1000 + (int)c + (_fontReduced ? 0x80 : 0)];
+ _writePos.x -= charSize;
+ }
+
+ if (_writePos.x < bounds.left)
+ _writePos.x = bounds.left;
+
+ if (c) {
+ int oldX = _writePos.x;
+ byte oldColor[4];
+ Common::copy(&_textColors[0], &_textColors[4], &oldColor[0]);
+
+ _textColors[1] = _textColors[2] = _textColors[3] = _bgColor;
+ writeChar(c, bounds);
+
+ Common::copy(&oldColor[0], &oldColor[4], &_textColors[0]);
+ _writePos.x = oldX;
+ }
+ } else if (c == 9) {
+ // Skip x position
+ int xAmount = fontAtoi();
+ _writePos.x = MIN(bounds.left + xAmount, (int)bounds.right);
+ } else if (c == 10) {
+ // Newline
+ if (newLine(bounds))
+ break;
+ } else if (c == 11) {
+ // Set y position
+ int yp = fontAtoi();
+ _writePos.y = MIN(bounds.top + yp, (int)bounds.bottom);
+ } else if (c == 12) {
+ // Set text colors
+ int idx = fontAtoi(2);
+ if (idx < 0)
+ idx = 0;
+ setTextColor(idx);
+ } else if (c < ' ') {
+ // End of string or invalid command
+ _displayString = nullptr;
+ break;
+ } else {
+ // Standard character - write it out
+ writeChar(c, bounds);
+ }
+ }
+
+ if (!_displayString)
+ break;
+ if ( _displayString > displayEnd && _fontJustify != JUSTIFY_RIGHT && _msgWraps
+ && newLine(bounds))
+ break;
+ }
+
+ return _displayString;
+}
+
+char FontSurface::getNextChar() {
+ return *_displayString++ & 0x7f;
+}
+
+bool FontSurface::getNextCharWidth(int &total) {
+ char c = getNextChar();
+
+ if (c > ' ') {
+ total += _fontData[0x1000 + (int)c + (_fontReduced ? 0x80 : 0)];
+ return false;
+ } else if (c == ' ') {
+ total += 4;
+ return false;
+ } else if (c == 8) {
+ c = getNextChar();
+ if (c == ' ') {
+ total -= 2;
+ return false;
+ } else {
+ _displayString -= 2;
+ return true;
+ }
+ } else if (c == 12) {
+ c = getNextChar();
+ if (c != 'd')
+ getNextChar();
+ return false;
+ } else {
+ --_displayString;
+ return true;
+ }
+}
+
+bool FontSurface::newLine(const Common::Rect &bounds) {
+ // Move past any spaces currently being pointed to
+ while ((*_displayString & 0x7f) == ' ')
+ ++_displayString;
+
+ _msgWraps = false;
+ _writePos.x = bounds.left;
+
+ int hv = _fontReduced ? 9 : 10;
+ _writePos.y += hv;
+
+ return ((_writePos.y + hv - 1) > bounds.bottom);
+}
+
+int FontSurface::fontAtoi(int len) {
+ int total = 0;
+ for (int i = 0; i < len; ++i) {
+ char c = getNextChar();
+ if (c == ' ')
+ c = '0';
+
+ int digit = c - '0';
+ if (digit < 0 || digit > 9)
+ return -1;
+
+ total = total * 10 + digit;
+ }
+
+ return total;
+}
+
+void FontSurface::setTextColor(int idx) {
+ const byte *colP = &TEXT_COLORS[idx][0];
+ Common::copy(colP, colP + 4, &_textColors[0]);
+}
+
+void FontSurface::writeChar(char c, const Common::Rect &clipRect) {
+ // Get y position, handling kerning
+ int y = _writePos.y;
+ if (c == 'g' || c == 'p' || c == 'q' || c == 'y')
+ ++y;
+
+ // Get pointers into font data and surface to write pixels to
+ int charIndex = (int)c + (_fontReduced ? 0x80 : 0);
+ const byte *srcP = &_fontData[charIndex * 16];
+
+ for (int yp = 0; yp < FONT_HEIGHT; ++yp, ++y) {
+ uint16 lineData = READ_LE_UINT16(srcP); srcP += 2;
+ byte *destP = (byte *)getBasePtr(_writePos.x, y);
+
+ // Ignore line if it's outside the clipping rect
+ if (y < clipRect.top || y >= clipRect.bottom)
+ continue;
+ const byte *lineStart = (const byte *)getBasePtr(clipRect.left, y);
+ const byte *lineEnd = (const byte *)getBasePtr(clipRect.right, y);
+
+ for (int xp = 0; xp < FONT_WIDTH; ++xp, ++destP) {
+ int colIndex = lineData & 3;
+ lineData >>= 2;
+
+ if (colIndex && destP >= lineStart && destP < lineEnd)
+ *destP = _textColors[colIndex];
+ }
+ }
+
+ addDirtyRect(Common::Rect(_writePos.x, _writePos.y, _writePos.x + FONT_WIDTH,
+ _writePos.y + FONT_HEIGHT));
+ _writePos.x += _fontData[0x1000 + charIndex];
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/font.h b/engines/xeen/font.h
new file mode 100644
index 0000000000..021205d5e5
--- /dev/null
+++ b/engines/xeen/font.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.
+ *
+ */
+
+#ifndef XEEN_FONT_H
+#define XEEN_FONT_H
+
+#include "xeen/xsurface.h"
+
+namespace Xeen {
+
+#define FONT_WIDTH 8
+#define FONT_HEIGHT 8
+#define DEFAULT_BG_COLOR 0x99
+
+enum Justify { JUSTIFY_NONE = 0, JUSTIFY_CENTER = 1, JUSTIFY_RIGHT = 2 };
+
+class FontSurface: public XSurface {
+private:
+ const char *_displayString;
+ bool _msgWraps;
+
+ /**
+ * Return the next pending character to display
+ */
+ char getNextChar();
+
+ /**
+ * Return the width of a given character
+ */
+ bool getNextCharWidth(int &total);
+
+ /**
+ * Handles moving to the next line of the given bounded area
+ */
+ bool newLine(const Common::Rect &bounds);
+
+ /**
+ * Extract a number of a given maximum length from the string
+ */
+ int fontAtoi(int len = 3);
+
+ /**
+ * Set the text colors based on the specified index in the master text colors list
+ */
+ void setTextColor(int idx);
+
+ /**
+ * Wrie a character to the surface
+ */
+ void writeChar(char c, const Common::Rect &clipRect);
+public:
+ const byte *_fontData;
+ Common::Point _writePos;
+ byte _textColors[4];
+ byte _bgColor;
+ bool _fontReduced;
+ Justify _fontJustify;
+public:
+ FontSurface();
+ FontSurface(int wv, int hv);
+ virtual ~FontSurface() {}
+
+ /**
+ * Draws a symbol to the surface.
+ * @param symbolId Symbol number from 0 to 19
+ */
+ void writeSymbol(int symbolId);
+
+ /**
+ * Write a string to the surface
+ * @param s String to display
+ * @param clipRect Window bounds to display string within
+ * @returns Any string remainder that couldn't be displayed
+ * @remarks Note that bounds is just used for wrapping purposes. Unless
+ * justification is set, the message will be written at _writePos
+ */
+ const char *writeString(const Common::String &s, const Common::Rect &clipRect);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_FONT_H */
diff --git a/engines/xeen/interface.cpp b/engines/xeen/interface.cpp
new file mode 100644
index 0000000000..9f9df190f7
--- /dev/null
+++ b/engines/xeen/interface.cpp
@@ -0,0 +1,2291 @@
+/* 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.
+ *
+ */
+
+#include "xeen/interface.h"
+#include "xeen/dialogs_automap.h"
+#include "xeen/dialogs_char_info.h"
+#include "xeen/dialogs_control_panel.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/dialogs_fight_options.h"
+#include "xeen/dialogs_info.h"
+#include "xeen/dialogs_items.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/dialogs_quests.h"
+#include "xeen/dialogs_quick_ref.h"
+#include "xeen/dialogs_spells.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+#include "xeen/dialogs_party.h"
+
+namespace Xeen {
+
+PartyDrawer::PartyDrawer(XeenEngine *vm): _vm(vm) {
+ _restoreSprites.load("restorex.icn");
+ _hpSprites.load("hpbars.icn");
+ _dseFace.load("dse.fac");
+ _hiliteChar = -1;
+}
+
+void PartyDrawer::drawParty(bool updateFlag) {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+ bool inCombat = _vm->_mode == MODE_COMBAT;
+ _restoreSprites.draw(screen, 0, Common::Point(8, 149));
+
+ // Handle drawing the party faces
+ uint partyCount = inCombat ? combat._combatParty.size() : party._activeParty.size();
+ for (uint idx = 0; idx < partyCount; ++idx) {
+ Character &ps = inCombat ? *combat._combatParty[idx] : party._activeParty[idx];
+ Condition charCondition = ps.worstCondition();
+ int charFrame = FACE_CONDITION_FRAMES[charCondition];
+
+ SpriteResource *sprites = (charFrame > 4) ? &_dseFace : ps._faceSprites;
+ if (charFrame > 4)
+ charFrame -= 5;
+
+ sprites->draw(screen, charFrame, Common::Point(CHAR_FACES_X[idx], 150));
+ }
+
+ for (uint idx = 0; idx < partyCount; ++idx) {
+ Character &ps = inCombat ? *combat._combatParty[idx] : party._activeParty[idx];
+
+ // Draw the Hp bar
+ int maxHp = ps.getMaxHP();
+ int frame;
+ if (ps._currentHp < 1)
+ frame = 4;
+ else if (ps._currentHp > maxHp)
+ frame = 3;
+ else if (ps._currentHp == maxHp)
+ frame = 0;
+ else if (ps._currentHp < (maxHp / 4))
+ frame = 2;
+ else
+ frame = 1;
+
+ _hpSprites.draw(screen, frame, Common::Point(HP_BARS_X[idx], 182));
+ }
+
+ if (_hiliteChar != -1)
+ res._globalSprites.draw(screen, 8, Common::Point(CHAR_FACES_X[_hiliteChar] - 1, 149));
+
+ if (updateFlag)
+ screen._windows[33].update();
+}
+
+void PartyDrawer::highlightChar(int charId) {
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+
+ if (charId != _hiliteChar && _hiliteChar != HILIGHT_CHAR_DISABLED) {
+ // Handle deselecting any previusly selected char
+ if (_hiliteChar != -1) {
+ res._globalSprites.draw(screen, 9 + _hiliteChar,
+ Common::Point(CHAR_FACES_X[_hiliteChar] - 1, 149));
+ }
+
+ // Highlight new character
+ res._globalSprites.draw(screen, 8, Common::Point(CHAR_FACES_X[charId] - 1, 149));
+ _hiliteChar = charId;
+ screen._windows[33].update();
+ }
+}
+
+void PartyDrawer::unhighlightChar() {
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+
+ if (_hiliteChar != -1) {
+ res._globalSprites.draw(screen, _hiliteChar + 9,
+ Common::Point(CHAR_FACES_X[_hiliteChar] - 1, 149));
+ _hiliteChar = -1;
+ screen._windows[33].update();
+ }
+}
+
+void PartyDrawer::resetHighlight() {
+ _hiliteChar = -1;
+}
+/*------------------------------------------------------------------------*/
+
+Interface::Interface(XeenEngine *vm) : ButtonContainer(), InterfaceMap(vm),
+ PartyDrawer(vm), _vm(vm) {
+ _buttonsLoaded = false;
+ _intrIndex1 = 0;
+ _steppingFX = 0;
+ _falling = false;
+ _blessedUIFrame = 0;
+ _powerShieldUIFrame = 0;
+ _holyBonusUIFrame = 0;
+ _heroismUIFrame = 0;
+ _flipUIFrame = 0;
+ _face1UIFrame = 0;
+ _face2UIFrame = 0;
+ _levitateUIFrame = 0;
+ _spotDoorsUIFrame = 0;
+ _dangerSenseUIFrame = 0;
+ _face1State = _face2State = 0;
+ _upDoorText = false;
+ _tillMove = 0;
+ Common::fill(&_charFX[0], &_charFX[MAX_ACTIVE_PARTY], 0);
+
+ initDrawStructs();
+}
+
+void Interface::initDrawStructs() {
+ _mainList[0] = DrawStruct(7, 232, 74);
+ _mainList[1] = DrawStruct(0, 235, 75);
+ _mainList[2] = DrawStruct(2, 260, 75);
+ _mainList[3] = DrawStruct(4, 286, 75);
+ _mainList[4] = DrawStruct(6, 235, 96);
+ _mainList[5] = DrawStruct(8, 260, 96);
+ _mainList[6] = DrawStruct(10, 286, 96);
+ _mainList[7] = DrawStruct(12, 235, 117);
+ _mainList[8] = DrawStruct(14, 260, 117);
+ _mainList[9] = DrawStruct(16, 286, 117);
+ _mainList[10] = DrawStruct(20, 235, 148);
+ _mainList[11] = DrawStruct(22, 260, 148);
+ _mainList[12] = DrawStruct(24, 286, 148);
+ _mainList[13] = DrawStruct(26, 235, 169);
+ _mainList[14] = DrawStruct(28, 260, 169);
+ _mainList[15] = DrawStruct(30, 286, 169);
+}
+
+void Interface::setup() {
+ _borderSprites.load("border.icn");
+ _spellFxSprites.load("spellfx.icn");
+ _fecpSprites.load("fecp.brd");
+ _blessSprites.load("bless.icn");
+ _charPowSprites.load("charpow.icn");
+ _uiSprites.load("inn.icn");
+
+ Party &party = *_vm->_party;
+ party.loadActiveParty();
+ party._newDay = party._minutes < 300;
+}
+
+void Interface::startup() {
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+ _iconSprites.load("main.icn");
+
+ animate3d();
+ if (_vm->_map->_isOutdoors) {
+ setIndoorsMonsters();
+ setIndoorsObjects();
+ } else {
+ setOutdoorsMonsters();
+ setOutdoorsObjects();
+ }
+ draw3d(false);
+
+ res._globalSprites.draw(screen._windows[1], 5, Common::Point(232, 9));
+ drawParty(false);
+
+ _mainList[0]._sprites = &res._globalSprites;
+ for (int i = 1; i < 16; ++i)
+ _mainList[i]._sprites = &_iconSprites;
+
+ setMainButtons();
+
+ _tillMove = false;
+}
+
+void Interface::mainIconsPrint() {
+ Screen &screen = *_vm->_screen;
+ screen._windows[38].close();
+ screen._windows[12].close();
+ screen._windows[0].drawList(_mainList, 16);
+ screen._windows[34].update();
+}
+
+void Interface::setMainButtons(bool combatMode) {
+ clearButtons();
+
+ addButton(Common::Rect(235, 75, 259, 95), Common::KEYCODE_s, &_iconSprites);
+ addButton(Common::Rect(260, 75, 284, 95), Common::KEYCODE_c, &_iconSprites);
+ addButton(Common::Rect(286, 75, 310, 95), Common::KEYCODE_r, &_iconSprites);
+ addButton(Common::Rect(235, 96, 259, 116), Common::KEYCODE_b, &_iconSprites);
+ addButton(Common::Rect(260, 96, 284, 116), Common::KEYCODE_d, &_iconSprites);
+ addButton(Common::Rect(286, 96, 310, 116), Common::KEYCODE_v, &_iconSprites);
+ addButton(Common::Rect(235, 117, 259, 137), Common::KEYCODE_m, &_iconSprites);
+ addButton(Common::Rect(260, 117, 284, 137), Common::KEYCODE_i, &_iconSprites);
+ addButton(Common::Rect(286, 117, 310, 137), Common::KEYCODE_q, &_iconSprites);
+ addButton(Common::Rect(109, 137, 122, 147), Common::KEYCODE_TAB, &_iconSprites);
+ addButton(Common::Rect(235, 148, 259, 168), Common::KEYCODE_LEFT, &_iconSprites);
+ addButton(Common::Rect(260, 148, 284, 168), Common::KEYCODE_UP, &_iconSprites);
+ addButton(Common::Rect(286, 148, 310, 168), Common::KEYCODE_RIGHT, &_iconSprites);
+ addButton(Common::Rect(235, 169, 259, 189), (Common::KBD_CTRL << 16) |Common::KEYCODE_LEFT, &_iconSprites);
+ addButton(Common::Rect(260, 169, 284, 189), Common::KEYCODE_DOWN, &_iconSprites);
+ addButton(Common::Rect(286, 169, 310, 189), (Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT, &_iconSprites);
+ addButton(Common::Rect(236, 11, 308, 69), Common::KEYCODE_EQUALS);
+ addButton(Common::Rect(239, 27, 312, 37), Common::KEYCODE_1);
+ addButton(Common::Rect(239, 37, 312, 47), Common::KEYCODE_2);
+ addButton(Common::Rect(239, 47, 312, 57), Common::KEYCODE_3);
+ addPartyButtons(_vm);
+
+ if (combatMode) {
+ _buttons[0]._value = Common::KEYCODE_f;
+ _buttons[1]._value = Common::KEYCODE_c;
+ _buttons[2]._value = Common::KEYCODE_a;
+ _buttons[3]._value = Common::KEYCODE_u;
+ _buttons[4]._value = Common::KEYCODE_r;
+ _buttons[5]._value = Common::KEYCODE_b;
+ _buttons[6]._value = Common::KEYCODE_o;
+ _buttons[7]._value = Common::KEYCODE_i;
+ _buttons[16]._value = 0;
+ }
+}
+
+void Interface::perform() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Scripts &scripts = *_vm->_scripts;
+ SoundManager &sound = *_vm->_sound;
+ Spells &spells = *_vm->_spells;
+ const Common::Rect WAIT_BOUNDS(8, 8, 224, 140);
+
+ events.updateGameCounter();
+ draw3d(true);
+
+ // Wait for a frame or a user event
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+
+ if (events._leftButton && WAIT_BOUNDS.contains(events._mousePos))
+ _buttonValue = Common::KEYCODE_SPACE;
+ } while (!_buttonValue && events.timeElapsed() < 1 && !_vm->_party->_partyDead);
+
+ if (!_buttonValue && !_vm->_party->_partyDead)
+ return;
+
+ if (_buttonValue == Common::KEYCODE_SPACE) {
+ int lookupId = map.mazeLookup(party._mazePosition,
+ WALL_SHIFTS[party._mazeDirection][2]);
+
+ bool eventsFlag = true;
+ switch (lookupId) {
+ case 1:
+ if (!map._isOutdoors) {
+ scripts.openGrate(13, 1);
+ eventsFlag = _buttonValue != 0;
+ }
+
+ case 6:
+ // Open grate being closed
+ if (!map._isOutdoors) {
+ scripts.openGrate(9, 0);
+ eventsFlag = _buttonValue != 0;
+ }
+ break;
+ case 9:
+ // Closed grate being opened
+ if (!map._isOutdoors) {
+ scripts.openGrate(6, 0);
+ eventsFlag = _buttonValue != 0;
+ }
+ break;
+ case 13:
+ if (!map._isOutdoors) {
+ scripts.openGrate(1, 1);
+ eventsFlag = _buttonValue != 0;
+ }
+ break;
+ default:
+ break;
+ }
+ if (eventsFlag) {
+ scripts.checkEvents();
+ if (_vm->shouldQuit())
+ return;
+ }
+ }
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_TAB:
+ // Stop mosters doing any movement
+ combat._moveMonsters = false;
+ if (ControlPanel::show(_vm) == -1) {
+ _vm->_quitMode = 2;
+ } else {
+ combat._moveMonsters = 1;
+ }
+ break;
+
+ case Common::KEYCODE_SPACE:
+ case Common::KEYCODE_w:
+ // Wait one turn
+ chargeStep();
+ combat.moveMonsters();
+ _upDoorText = false;
+ _flipDefaultGround = !_flipDefaultGround;
+ _flipGround = !_flipGround;
+
+ stepTime();
+ break;
+
+ case (Common::KBD_CTRL << 16) | Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP4:
+ if (checkMoveDirection((Common::KBD_CTRL << 16) | Common::KEYCODE_LEFT)) {
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ --party._mazePosition.x;
+ break;
+ case DIR_SOUTH:
+ ++party._mazePosition.x;
+ break;
+ case DIR_EAST:
+ ++party._mazePosition.y;
+ break;
+ case DIR_WEST:
+ --party._mazePosition.y;
+ break;
+ default:
+ break;
+ }
+
+ chargeStep();
+ _isAnimReset = true;
+ party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
+ _flipSky = !_flipSky;
+ stepTime();
+ }
+ break;
+
+ case (Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP6:
+ if (checkMoveDirection((Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT)) {
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ ++party._mazePosition.x;
+ break;
+ case DIR_SOUTH:
+ --party._mazePosition.x;
+ break;
+ case DIR_EAST:
+ --party._mazePosition.y;
+ break;
+ case DIR_WEST:
+ ++party._mazePosition.y;
+ break;
+ default:
+ break;
+ }
+
+ chargeStep();
+ _isAnimReset = true;
+ party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
+ _flipSky = !_flipSky;
+ stepTime();
+ }
+ break;
+
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP7:
+ party._mazeDirection = (Direction)((int)party._mazeDirection - 1);
+ _isAnimReset = true;
+ party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
+ _flipSky = !_flipSky;
+ stepTime();
+ break;
+
+ case Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP9:
+ party._mazeDirection = (Direction)((int)party._mazeDirection + 1);
+ _isAnimReset = true;
+ party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
+ _flipSky = !_flipSky;
+ stepTime();
+ break;
+
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ if (checkMoveDirection(Common::KEYCODE_UP)) {
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ ++party._mazePosition.y;
+ break;
+ case DIR_SOUTH:
+ --party._mazePosition.y;
+ break;
+ case DIR_EAST:
+ ++party._mazePosition.x;
+ break;
+ case DIR_WEST:
+ --party._mazePosition.x;
+ break;
+ default:
+ break;
+ }
+
+ chargeStep();
+ stepTime();
+ }
+ break;
+
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ if (checkMoveDirection(Common::KEYCODE_DOWN)) {
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ --party._mazePosition.y;
+ break;
+ case DIR_SOUTH:
+ ++party._mazePosition.y;
+ break;
+ case DIR_EAST:
+ --party._mazePosition.x;
+ break;
+ case DIR_WEST:
+ ++party._mazePosition.x;
+ break;
+ default:
+ break;
+ }
+
+ chargeStep();
+ stepTime();
+ }
+ break;
+
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ CharacterInfo::show(_vm, _buttonValue);
+ if (party._stepped)
+ combat.moveMonsters();
+ }
+ break;
+
+ case Common::KEYCODE_EQUALS:
+ case Common::KEYCODE_KP_EQUALS:
+ // Toggle minimap
+ combat._moveMonsters = false;
+ party._automapOn = !party._automapOn;
+ combat._moveMonsters = true;
+ break;
+
+ case Common::KEYCODE_b:
+ chargeStep();
+
+ if (map.getCell(2) < map.mazeData()._difficulties._wallNoPass
+ && !map._isOutdoors) {
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ ++party._mazePosition.y;
+ break;
+ case DIR_EAST:
+ ++party._mazePosition.x;
+ break;
+ case DIR_SOUTH:
+ --party._mazePosition.y;
+ break;
+ case DIR_WEST:
+ --party._mazePosition.x;
+ break;
+ default:
+ break;
+ }
+ chargeStep();
+ stepTime();
+ } else {
+ bash(party._mazePosition, party._mazeDirection);
+ }
+ break;
+
+ case Common::KEYCODE_c: {
+ // Cast spell
+ if (_tillMove) {
+ combat.moveMonsters();
+ draw3d(true);
+ }
+
+ int result = 0;
+ Character *c = &party._activeParty[(spells._lastCaster < 0 ||
+ spells._lastCaster >= (int)party._activeParty.size()) ?
+ (int)party._activeParty.size() - 1 : spells._lastCaster];
+ do {
+ int spellId = CastSpell::show(_vm, c);
+ if (spellId == -1 || c == nullptr)
+ break;
+
+ result = spells.castSpell(c, (MagicSpell)spellId);
+ } while (result != -1);
+
+ if (result == 1) {
+ chargeStep();
+ doStepCode();
+ }
+ break;
+ }
+
+ case Common::KEYCODE_i:
+ // Show Info dialog
+ combat._moveMonsters = false;
+ InfoDialog::show(_vm);
+ combat._moveMonsters = true;
+ break;
+
+ case Common::KEYCODE_m:
+ // Show map dialog
+ AutoMapDialog::show(_vm);
+ break;
+
+ case Common::KEYCODE_q:
+ // Show the quick reference dialog
+ QuickReferenceDialog::show(_vm);
+ break;
+
+ case Common::KEYCODE_r:
+ // Rest
+ rest();
+ break;
+
+ case Common::KEYCODE_s:
+ // Shoot
+ if (!party.canShoot()) {
+ sound.playFX(21);
+ } else {
+ if (_tillMove) {
+ combat.moveMonsters();
+ draw3d(true);
+ }
+
+ if (combat._attackMonsters[0] != -1 || combat._attackMonsters[1] != -1
+ || combat._attackMonsters[2] != -1) {
+ if ((_vm->_mode == MODE_1 || _vm->_mode == MODE_SLEEPING)
+ && !combat._monstersAttacking && !_charsShooting) {
+ doCombat();
+ }
+ }
+
+ combat.shootRangedWeapon();
+ chargeStep();
+ doStepCode();
+ }
+ break;
+
+ case Common::KEYCODE_v:
+ // Show the quests dialog
+ Quests::show(_vm);
+ break;
+
+ case Common::KEYCODE_x:
+ // ****DEBUG***
+ PartyDialog::show(_vm); //***DEBUG****
+ default:
+ break;
+ }
+}
+
+void Interface::chargeStep() {
+ if (!_vm->_party->_partyDead) {
+ _vm->_party->changeTime(_vm->_map->_isOutdoors ? 10 : 1);
+ if (_tillMove) {
+ _vm->_combat->moveMonsters();
+ }
+
+ _tillMove = 3;
+ }
+}
+
+void Interface::stepTime() {
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ doStepCode();
+
+ if (++party._ctr24 == 24)
+ party._ctr24 = 0;
+
+ if (_buttonValue != Common::KEYCODE_SPACE && _buttonValue != Common::KEYCODE_w) {
+ _steppingFX ^= 1;
+ sound.playFX(_steppingFX + 7);
+ }
+
+ _upDoorText = false;
+ _flipDefaultGround = !_flipDefaultGround;
+ _flipGround = !_flipGround;
+}
+
+void Interface::doStepCode() {
+ Combat &combat = *_vm->_combat;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Scripts &scripts = *_vm->_scripts;
+ int damage = 0;
+
+ party._stepped = true;
+ _upDoorText = false;
+
+ map.getCell(2);
+ int surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];
+
+ switch (surfaceId) {
+ case SURFTYPE_SPACE:
+ // Wheeze.. can't breathe in space! Explosive decompression, here we come
+ party._partyDead = true;
+ break;
+ case SURFTYPE_LAVA:
+ // It burns, it burns!
+ damage = 100;
+ party._damageType = DT_FIRE;
+ break;
+ case SURFTYPE_SKY:
+ // We can fly, we can.. oh wait, we can't!
+ damage = 100;
+ party._damageType = DT_PHYSICAL;
+ _falling = true;
+ break;
+ case SURFTYPE_DESERT:
+ // Without navigation skills, simulate getting lost by adding extra time
+ if (map._isOutdoors && !party.checkSkill(NAVIGATOR))
+ party.addTime(170);
+ break;
+ case SURFTYPE_CLOUD:
+ if (!party._levitateActive) {
+ party._damageType = DT_PHYSICAL;
+ _falling = true;
+ damage = 100;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (_vm->_files->_isDarkCc && party._gameFlags[374]) {
+ _falling = false;
+ } else {
+ if (_falling)
+ startFalling(false);
+
+ if ((party._mazePosition.x & 16) || (party._mazePosition.y & 16)) {
+ if (map._isOutdoors)
+ map.getNewMaze();
+ }
+
+ if (damage) {
+ _flipGround = !_flipGround;
+ draw3d(true);
+
+ int oldVal = scripts._v2;
+ scripts._v2 = 0;
+ combat.giveCharDamage(damage, combat._damageType, 0);
+
+ scripts._v2 = oldVal;
+ _flipGround = !_flipGround;
+ } else if (party._partyDead) {
+ draw3d(true);
+ }
+ }
+}
+
+void Interface::startFalling(bool flag) {
+ Combat &combat = *_vm->_combat;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Scripts &scripts = *_vm->_scripts;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ if (isDarkCc && party._gameFlags[374]) {
+ _falling = 0;
+ return;
+ }
+
+ _falling = false;
+ draw3d(true);
+ _falling = 2;
+ draw3d(false);
+
+ if (flag) {
+ if (!isDarkCc || party._fallMaze != 0) {
+ party._mazeId = party._fallMaze;
+ party._mazePosition = party._fallPosition;
+ }
+ }
+
+ _falling = true;
+ map.load(party._mazeId);
+ if (flag) {
+ if (((party._mazePosition.x & 16) || (party._mazePosition.y & 16)) &&
+ map._isOutdoors) {
+ map.getNewMaze();
+ }
+ }
+
+ if (isDarkCc) {
+ switch (party._mazeId - 25) {
+ case 0:
+ case 26:
+ case 27:
+ case 28:
+ case 29:
+ party._mazeId = 24;
+ party._mazePosition = Common::Point(11, 9);
+ break;
+ case 1:
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ party._mazeId = 12;
+ party._mazePosition = Common::Point(6, 15);
+ break;
+ case 2:
+ case 34:
+ case 35:
+ case 36:
+ case 37:
+ case 51:
+ case 52:
+ case 53:
+ party._mazeId = 15;
+ party._mazePosition = Common::Point(4, 12);
+ party._mazeDirection = DIR_SOUTH;
+ break;
+ case 40:
+ case 41:
+ party._mazeId = 14;
+ party._mazePosition = Common::Point(8, 3);
+ break;
+ case 44:
+ case 45:
+ party._mazeId = 1;
+ party._mazePosition = Common::Point(8, 7);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ case 49:
+ party._mazeId = 12;
+ party._mazePosition = Common::Point(11, 13);
+ party._mazeDirection = DIR_SOUTH;
+ break;
+ case 57:
+ case 58:
+ case 59:
+ party._mazeId = 5;
+ party._mazePosition = Common::Point(12, 7);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ case 60:
+ party._mazeId = 6;
+ party._mazePosition = Common::Point(12, 3);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ default:
+ party._mazeId = 23;
+ party._mazePosition = Common::Point(12, 10);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ }
+ } else {
+ if (party._mazeId > 89 && party._mazeId < 113) {
+ party._mazeId += 168;
+ } else {
+ switch (party._mazeId - 25) {
+ case 0:
+ party._mazeId = 89;
+ party._mazePosition = Common::Point(2, 14);
+ break;
+ case 1:
+ party._mazeId = 109;
+ party._mazePosition = Common::Point(13, 14);
+ break;
+ case 2:
+ party._mazeId = 112;
+ party._mazePosition = Common::Point(13, 3);
+ break;
+ case 3:
+ party._mazeId = 92;
+ party._mazePosition = Common::Point(2, 3);
+ break;
+ case 12:
+ case 13:
+ party._mazeId = 14;
+ party._mazePosition = Common::Point(10, 2);
+ break;
+ case 16:
+ case 17:
+ case 18:
+ party._mazeId = 4;
+ party._mazePosition = Common::Point(5, 14);
+ break;
+ case 20:
+ case 21:
+ case 22:
+ party._mazeId = 21;
+ party._mazePosition = Common::Point(9, 11);
+ break;
+ case 24:
+ case 25:
+ case 26:
+ party._mazeId = 1;
+ party._mazePosition = Common::Point(10, 4);
+ break;
+ case 28:
+ case 29:
+ case 30:
+ case 31:
+ party._mazeId = 26;
+ party._mazePosition = Common::Point(12, 10);
+ break;
+ case 32:
+ case 33:
+ case 34:
+ case 35:
+ party._mazeId = 3;
+ party._mazePosition = Common::Point(4, 9);
+ break;
+ case 36:
+ case 37:
+ case 38:
+ case 39:
+ party._mazeId = 16;
+ party._mazePosition = Common::Point(2, 7);
+ break;
+ case 40:
+ case 41:
+ case 42:
+ case 43:
+ party._mazeId = 23;
+ party._mazePosition = Common::Point(10, 9);
+ break;
+ case 44:
+ case 45:
+ case 46:
+ case 47:
+ party._mazeId = 13;
+ party._mazePosition = Common::Point(2, 10);
+ break;
+ case 103:
+ case 104:
+ map._loadDarkSide = false;
+ party._mazeId = 8;
+ party._mazePosition = Common::Point(11, 15);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ case 105:
+ party._mazeId = 24;
+ party._mazePosition = Common::Point(11, 9);
+ break;
+ case 106:
+ party._mazeId = 12;
+ party._mazePosition = Common::Point(6, 15);
+ break;
+ case 107:
+ party._mazeId = 15;
+ party._mazePosition = Common::Point(4, 12);
+ break;
+ default:
+ party._mazeId = 29;
+ party._mazePosition = Common::Point(25, 21);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ }
+ }
+ }
+
+ _flipGround ^= 1;
+ draw3d(true);
+ int tempVal = scripts._v2;
+ scripts._v2 = 0;
+ combat.giveCharDamage(party._fallDamage, DT_PHYSICAL, 0);
+ scripts._v2 = tempVal;
+
+ _flipGround ^= 1;
+}
+
+bool Interface::checkMoveDirection(int key) {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ Direction dir = party._mazeDirection;
+
+ switch (key) {
+ case (Common::KBD_CTRL << 16) | Common::KEYCODE_LEFT:
+ party._mazeDirection = (party._mazeDirection == DIR_NORTH) ? DIR_WEST :
+ (Direction)(party._mazeDirection - 1);
+ break;
+ case (Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT:
+ party._mazeDirection = (party._mazeDirection == DIR_WEST) ? DIR_NORTH :
+ (Direction)(party._mazeDirection + 1);
+ break;
+ case Common::KEYCODE_DOWN:
+ party._mazeDirection = (Direction)((int)party._mazeDirection ^ 2);
+ break;
+ default:
+ break;
+ }
+
+ map.getCell(7);
+ int startSurfaceId = map._currentSurfaceId;
+ int surfaceId;
+
+ if (map._isOutdoors) {
+ party._mazeDirection = dir;
+
+ switch (map._currentWall) {
+ case 5:
+ if (_vm->_files->_isDarkCc)
+ goto check;
+
+ // Deliberate FAll-through
+ case 0:
+ case 2:
+ case 4:
+ case 8:
+ case 11:
+ case 13:
+ case 14:
+ surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];
+ if (surfaceId == SURFTYPE_WATER) {
+ if (party.checkSkill(SWIMMING) || party._walkOnWaterActive)
+ return true;
+ } else if (surfaceId == SURFTYPE_DWATER) {
+ if (party._walkOnWaterActive)
+ return true;
+ } else if (surfaceId != SURFTYPE_SPACE) {
+ return true;
+ }
+
+ sound.playFX(21);
+ return false;
+
+ case 1:
+ case 7:
+ case 9:
+ case 10:
+ case 12:
+ check:
+ if (party.checkSkill(MOUNTAINEER))
+ return true;
+
+ sound.playFX(21);
+ return false;
+
+ default:
+ break;
+ }
+ } else {
+ surfaceId = map.getCell(2);
+ if (surfaceId >= map.mazeData()._difficulties._wallNoPass) {
+ party._mazeDirection = dir;
+ sound.playFX(46);
+ return false;
+ } else {
+ party._mazeDirection = dir;
+
+ if (startSurfaceId == SURFTYPE_SWAMP || party.checkSkill(SWIMMING) ||
+ party._walkOnWaterActive) {
+ sound.playFX(46);
+ return false;
+ } else {
+ if (_buttonValue == Common::KEYCODE_UP && _wo[107]) {
+ _openDoor = true;
+ sound.playFX(47);
+ draw3d(true);
+ _openDoor = false;
+ }
+ return true;
+ }
+ }
+ }
+
+ return true;
+}
+
+void Interface::rest() {
+ EventsManager &events = *_vm->_events;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+
+ map.cellFlagLookup(party._mazePosition);
+
+ if ((map._currentCantRest || (map.mazeData()._mazeFlags & RESTRICTION_REST))
+ && _vm->_mode != MODE_12) {
+ ErrorScroll::show(_vm, TOO_DANGEROUS_TO_REST, WT_NONFREEZED_WAIT);
+ } else {
+ // Check whether any character is in danger of dying
+ bool dangerFlag = false;
+ for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
+ for (int attrib = MIGHT; attrib <= LUCK; ++attrib) {
+ if (party._activeParty[charIdx].getStat((Attribute)attrib) < 1)
+ dangerFlag = true;
+ }
+ }
+
+ if (dangerFlag) {
+ if (!Confirm::show(_vm, SOME_CHARS_MAY_DIE))
+ return;
+ }
+
+ // Mark all the players as being asleep
+ for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
+ party._activeParty[charIdx]._conditions[ASLEEP] = 1;
+ }
+ drawParty(true);
+
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_SLEEPING;
+
+ if (oldMode == MODE_12) {
+ party.changeTime(8 * 60);
+ } else {
+ for (int idx = 0; idx < 10; ++idx) {
+ chargeStep();
+ draw3d(true);
+
+ if (_vm->_mode == MODE_1) {
+ _vm->_mode = oldMode;
+ return;
+ }
+ }
+
+ party.changeTime(map._isOutdoors ? 380 : 470);
+ }
+
+ if (_vm->getRandomNumber(1, 20) == 1) {
+ // Show dream
+ screen.saveBackground();
+ screen.fadeOut(4);
+ events.hideCursor();
+
+ screen.loadBackground("scene1.raw");
+ screen._windows[0].update();
+ screen.fadeIn(4);
+
+ events.updateGameCounter();
+ while (!_vm->shouldQuit() && events.timeElapsed() < 7)
+ events.pollEventsAndWait();
+
+ File f("dreams2.voc");
+ sound.playSample(&f, 1);
+ while (!_vm->shouldQuit() && sound.playSample(1, 0))
+ events.pollEventsAndWait();
+ f.close();
+
+ f.openFile("laff1.voc");
+ sound.playSample(&f, 1);
+ while (!_vm->shouldQuit() && sound.playSample(1, 0))
+ events.pollEventsAndWait();
+ f.close();
+
+ events.updateGameCounter();
+ while (!_vm->shouldQuit() && events.timeElapsed() < 7)
+ events.pollEventsAndWait();
+
+ screen.fadeOut(4);
+ events.setCursor(0);
+ screen.restoreBackground();
+ screen._windows[0].update();
+
+ screen.fadeIn(4);
+ }
+
+ party.resetTemps();
+
+ // Wake up the party
+ bool starving = false;
+ int foodConsumed = 0;
+ for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
+ Character &c = party._activeParty[charIdx];
+ c._conditions[ASLEEP] = 0;
+
+ if (party._food == 0) {
+ starving = true;
+ } else {
+ party._rested = true;
+ Condition condition = c.worstCondition();
+
+ if (condition < DEAD || condition > ERADICATED) {
+ --party._food;
+ ++foodConsumed;
+ party._heroism = 0;
+ party._holyBonus = 0;
+ party._powerShield = 0;
+ party._blessed = 0;
+ c._conditions[UNCONSCIOUS] = 0;
+ c._currentHp = c.getMaxHP();
+ c._currentSp = c.getMaxSP();
+ }
+ }
+ }
+
+ drawParty(true);
+ _vm->_mode = oldMode;
+ doStepCode();
+ draw3d(true);
+
+ ErrorScroll::show(_vm, Common::String::format(REST_COMPLETE,
+ starving ? PARTY_IS_STARVING : HIT_SPELL_POINTS_RESTORED,
+ foodConsumed));
+ party.checkPartyDead();
+ }
+}
+
+void Interface::bash(const Common::Point &pt, Direction direction) {
+ EventsManager &events = *_vm->_events;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+
+ if (map._isOutdoors)
+ return;
+
+ sound.playFX(31);
+
+ uint charNum1 = 0, charNum2 = 0;
+ for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
+ Character &c = party._activeParty[charIdx];
+ Condition condition = c.worstCondition();
+
+ if (!(condition == ASLEEP || (condition >= PARALYZED &&
+ condition <= ERADICATED))) {
+ if (charNum1) {
+ charNum2 = charIdx + 1;
+ break;
+ } else {
+ charNum1 = charIdx + 1;
+ }
+ }
+ }
+
+ party._activeParty[charNum1 - 1].subtractHitPoints(2);
+ _charPowSprites.draw(screen._windows[0], 0,
+ Common::Point(CHAR_FACES_X[charNum1 - 1], 150));
+ screen._windows[0].update();
+
+ if (charNum2) {
+ party._activeParty[charNum2 - 1].subtractHitPoints(2);
+ _charPowSprites.draw(screen._windows[0], 0,
+ Common::Point(CHAR_FACES_X[charNum2 - 1], 150));
+ screen._windows[0].update();
+ }
+
+ int cell = map.mazeLookup(Common::Point(pt.x + SCREEN_POSITIONING_X[direction][7],
+ pt.y + SCREEN_POSITIONING_Y[direction][7]), 0, 0xffff);
+ if (cell != INVALID_CELL) {
+ int v = map.getCell(2);
+
+ if (v == 7) {
+ ++_wo[207];
+ ++_wo[267];
+ ++_wo[287];
+ } else if (v == 14) {
+ ++_wo[267];
+ ++_wo[287];
+ } else if (v == 15) {
+ ++_wo[287];
+ } else {
+ int might = party._activeParty[charNum1 - 1].getStat(MIGHT) +
+ _vm->getRandomNumber(1, 30);
+ if (charNum2)
+ might += party._activeParty[charNum2 - 1].getStat(MIGHT);
+
+ int bashThreshold = (v == 9) ? map.mazeData()._difficulties._bashGrate :
+ map.mazeData()._difficulties._bashWall;
+ if (might >= bashThreshold) {
+ // Remove the wall on the current cell, and the reverse wall
+ // on the cell we're bashing through to
+ map.setWall(pt, direction, 3);
+ switch (direction) {
+ case DIR_NORTH:
+ map.setWall(Common::Point(pt.x, pt.y + 1), DIR_SOUTH, 3);
+ break;
+ case DIR_EAST:
+ map.setWall(Common::Point(pt.x + 1, pt.y), DIR_WEST, 3);
+ break;
+ case DIR_SOUTH:
+ map.setWall(Common::Point(pt.x, pt.y - 1), DIR_NORTH, 3);
+ break;
+ case DIR_WEST:
+ map.setWall(Common::Point(pt.x - 1, pt.y), DIR_EAST, 3);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ party.checkPartyDead();
+ events.ipause(2);
+ drawParty(true);
+}
+
+void Interface::draw3d(bool updateFlag, bool skipDelay) {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Scripts &scripts = *_vm->_scripts;
+
+ if (screen._windows[11]._enabled)
+ return;
+
+ _flipUIFrame = (_flipUIFrame + 1) % 4;
+ if (_flipUIFrame == 0)
+ _flipWater = !_flipWater;
+ if (_tillMove && (_vm->_mode == MODE_1 || _vm->_mode == MODE_COMBAT) &&
+ !combat._monstersAttacking && combat._moveMonsters) {
+ if (--_tillMove == 0)
+ combat.moveMonsters();
+ }
+
+ // Draw the map
+ drawMap();
+
+ // Draw the minimap
+ drawMiniMap();
+
+ if (_falling == 1)
+ handleFalling();
+
+ if (_falling == 2) {
+ screen.saveBackground(1);
+ }
+
+ assembleBorder();
+
+ // Draw any on-screen text if flagged to do so
+ if (_upDoorText && combat._attackMonsters[0] == -1) {
+ screen._windows[3].writeString(_screenText);
+ }
+
+ if (updateFlag) {
+ screen._windows[1].update();
+ screen._windows[3].update();
+ }
+
+ if (combat._attackMonsters[0] != -1 || combat._attackMonsters[1] != -1
+ || combat._attackMonsters[2] != -1) {
+ if ((_vm->_mode == MODE_1 || _vm->_mode == MODE_SLEEPING) &&
+ !combat._monstersAttacking && !_charsShooting && combat._moveMonsters) {
+ doCombat();
+ if (scripts._eventSkipped)
+ scripts.checkEvents();
+ }
+ }
+
+ party._stepped = false;
+ if (_vm->_mode == MODE_9) {
+ // TODO: Save current scripts data?
+ }
+
+ if (!skipDelay)
+ events.wait(2);
+}
+
+void Interface::handleFalling() {
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Window &w = screen._windows[3];
+ File voc1("scream.voc");
+ File voc2("unnh.voc");
+ saveFall();
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ party._activeParty[idx]._faceSprites->draw(screen._windows[0], 4,
+ Common::Point(CHAR_FACES_X[idx], 150));
+ }
+
+ screen._windows[33].update();
+ sound.playFX(11);
+ sound.playSample(&voc1, 0);
+
+ for (int idx = 0, incr = 2; idx < 133; ++incr, idx += incr) {
+ fall(idx);
+ assembleBorder();
+ w.update();
+ }
+
+ fall(132);
+ assembleBorder();
+ w.update();
+
+ sound.playSample(nullptr, 0);
+ sound.playSample(&voc2, 0);
+ sound.playFX(31);
+
+ fall(127);
+ assembleBorder();
+ w.update();
+
+ fall(132);
+ assembleBorder();
+ w.update();
+
+ fall(129);
+ assembleBorder();
+ w.update();
+
+ fall(132);
+ assembleBorder();
+ w.update();
+
+ shake(10);
+}
+
+void Interface::saveFall() {
+ // TODO
+}
+
+void Interface::fall(int v) {
+ // TODO
+}
+
+void Interface::shake(int time) {
+ // TODO
+}
+
+void Interface::drawMiniMap() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+ Window &window1 = screen._windows[1];
+
+ if (screen._windows[2]._enabled || screen._windows[10]._enabled)
+ return;
+ if (!party._automapOn && !party._wizardEyeActive) {
+ // Draw the Might & Magic logo
+ res._globalSprites.draw(window1, 5, Common::Point(232, 9));
+ return;
+ }
+
+ int v, frame;
+ int frame2 = _overallFrame * 2;
+ bool eyeActive = party._wizardEyeActive;
+ if (party._automapOn)
+ party._wizardEyeActive = false;
+
+ if (map._isOutdoors) {
+ res._globalSprites.draw(window1, 15, Common::Point(237, 12));
+
+ for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
+ for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
+ 4);
+ frame = map.mazeDataCurrent()._surfaceTypes[v];
+
+ if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, frame, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
+ for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
+ 4);
+ frame = map.mazeData()._wallTypes[v];
+
+ if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, frame + 16, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
+ for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
+ 4);
+
+ if (v != -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, v + 32, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ // Draw the direction arrow
+ res._globalSprites.draw(window1, party._mazeDirection + 1,
+ Common::Point(267, 36));
+ }
+ else {
+ frame2 = (frame2 + 2) % 8;
+
+ // First draw the default surface bases for each cell to show
+ for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
+ for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
+ 0, 0xffff);
+
+ if (v != INVALID_CELL && (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, 0, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ // Draw correct surface bases for revealed tiles
+ for (int rowNum = 0, yp = 17, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
+ for (int colNum = 0, xp = 242, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
+ 0, 0xffff);
+ int surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];
+
+ if (v != INVALID_CELL && map._currentSurfaceId &&
+ (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, surfaceId + 36, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ v = map.mazeLookup(Common::Point(party._mazePosition.x - 4, party._mazePosition.y + 4), 0xffff, 0);
+ if (v != INVALID_CELL && map._currentSurfaceId &&
+ (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1,
+ map.mazeData()._surfaceTypes[map._currentSurfaceId] + 36,
+ Common::Point(232, 9));
+ }
+
+ // Handle drawing surface sprites partially clipped at the left edge
+ for (int rowNum = 0, yp = 17, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, --yDiff, yp += 8) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x - 4, party._mazePosition.y + yDiff),
+ 0, 0xffff);
+
+ if (v != INVALID_CELL && map._currentSurfaceId &&
+ (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1,
+ map.mazeData()._surfaceTypes[map._currentSurfaceId] + 36,
+ Common::Point(232, yp));
+ }
+ }
+
+ // Handle drawing surface sprites partially clipped at the top edge
+ for (int colNum = 0, xp = 242, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, ++xDiff, xp += 8) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + 4),
+ 0, 0xffff);
+
+ if (v != INVALID_CELL && map._currentSurfaceId &&
+ (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1,
+ map.mazeData()._surfaceTypes[map._currentSurfaceId] + 36,
+ Common::Point(xp, 9));
+ }
+ }
+
+ //
+ for (int idx = 0, xp = 237, yp = 60, xDiff = -3; idx < MINIMAP_SIZE;
+ ++idx, ++xDiff, xp += 10, yp -= 8) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x - 4, party._mazePosition.y - 3 + idx),
+ 12, 0xffff);
+
+ switch (v) {
+ case 1:
+ frame = 18;
+ break;
+ case 3:
+ frame = 22;
+ break;
+ case 4:
+ case 13:
+ frame = 16;
+ break;
+ case 5:
+ case 8:
+ frame = 2;
+ break;
+ case 6:
+ frame = 30;
+ break;
+ case 7:
+ frame = 32;
+ break;
+ case 9:
+ frame = 24;
+ break;
+ case 10:
+ frame = 28;
+ break;
+ case 11:
+ frame = 14;
+ break;
+ case 12:
+ frame = frame2 + 4;
+ break;
+ case 14:
+ frame = 24;
+ break;
+ case 15:
+ frame = 26;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive))
+ map._tileSprites.draw(window1, frame, Common::Point(222, yp));
+
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x - 3 + idx, party._mazePosition.y + 4),
+ 0);
+
+ switch (v) {
+ case 1:
+ frame = 19;
+ break;
+ case 2:
+ frame = 35;
+ break;
+ case 3:
+ frame = 23;
+ break;
+ case 4:
+ case 13:
+ frame = 17;
+ break;
+ case 5:
+ case 8:
+ frame = 3;
+ break;
+ case 6:
+ frame = 31;
+ break;
+ case 7:
+ frame = 33;
+ break;
+ case 9:
+ frame = 21;
+ break;
+ case 10:
+ frame = 29;
+ break;
+ case 11:
+ frame = 15;
+ break;
+ case 12:
+ frame = frame2 + 5;
+ break;
+ case 14:
+ frame = 25;
+ break;
+ case 15:
+ frame = 27;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive))
+ map._tileSprites.draw(window1, frame, Common::Point(xp, 4));
+ }
+
+ // Draw the front/back walls of cells in the minimap
+ for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE;
+ ++rowNum, --yDiff, yp += 8) {
+ for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE;
+ ++colNum, ++xDiff, xp += 10) {
+ if (colNum == 4 && rowNum == 4) {
+ // Center of the minimap. Draw the direction arrow
+ res._globalSprites.draw(window1, party._mazeDirection + 1,
+ Common::Point(272, 40));
+ }
+
+ v = map.mazeLookup(Common::Point(party._mazePosition.x + xDiff,
+ party._mazePosition.y + yDiff), 12, 0xffff);
+ switch (v) {
+ case 1:
+ frame = 18;
+ break;
+ case 3:
+ frame = 22;
+ break;
+ case 4:
+ case 13:
+ frame = 16;
+ break;
+ case 5:
+ case 8:
+ frame = 2;
+ break;
+ case 6:
+ frame = 30;
+ break;
+ case 7:
+ frame = 32;
+ break;
+ case 9:
+ frame = 20;
+ break;
+ case 10:
+ frame = 28;
+ break;
+ case 11:
+ frame = 14;
+ break;
+ case 12:
+ frame = frame2 + 4;
+ break;
+ case 14:
+ frame = 24;
+ break;
+ case 15:
+ frame = 26;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, frame, Common::Point(xp, yp));
+ }
+
+ v = map.mazeLookup(Common::Point(party._mazePosition.x + xDiff,
+ party._mazePosition.y + yDiff), 12, 0xffff);
+ switch (v) {
+ case 1:
+ frame = 19;
+ break;
+ case 2:
+ frame = 35;
+ break;
+ case 3:
+ frame = 23;
+ break;
+ case 4:
+ case 13:
+ frame = 17;
+ break;
+ case 5:
+ case 8:
+ frame = 3;
+ break;
+ case 6:
+ frame = 31;
+ break;
+ case 7:
+ frame = 33;
+ break;
+ case 9:
+ frame = 21;
+ break;
+ case 10:
+ frame = 29;
+ break;
+ case 11:
+ frame = 15;
+ break;
+ case 12:
+ frame = frame2 + 5;
+ break;
+ case 14:
+ frame = 25;
+ break;
+ case 15:
+ frame = 27;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (v == -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, frame, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ // Draw the top of blocked/wall cells on the map
+ for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
+ for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
+ 0, 0xffff);
+
+ if (v == INVALID_CELL || (!map._currentSteppedOn && !party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, 1, Common::Point(xp, yp));
+ }
+ }
+ }
+ }
+
+ // Draw outer rectangle around the automap
+ res._globalSprites.draw(window1, 6, Common::Point(223, 3));
+ party._wizardEyeActive = eyeActive;
+}
+
+void Interface::assembleBorder() {
+ Combat &combat = *_vm->_combat;
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+
+ // Draw the outer frame
+ res._globalSprites.draw(screen._windows[0], 0, Common::Point(8, 8));
+
+ // Draw the animating bat character on the left screen edge to indicate
+ // that the party is being levitated
+ _borderSprites.draw(screen._windows[0], _vm->_party->_levitateActive ? _levitateUIFrame + 16 : 16,
+ Common::Point(0, 82));
+ _levitateUIFrame = (_levitateUIFrame + 1) % 12;
+
+ // Draw UI element to indicate whether can spot hidden doors
+ _borderSprites.draw(screen,
+ (_thinWall && _vm->_party->checkSkill(SPOT_DOORS)) ? _spotDoorsUIFrame + 28 : 28,
+ Common::Point(194, 91));
+ _spotDoorsUIFrame = (_spotDoorsUIFrame + 1) % 12;
+
+ // Draw UI element to indicate whether can sense danger
+ _borderSprites.draw(screen,
+ (combat._dangerPresent && _vm->_party->checkSkill(DANGER_SENSE)) ? _spotDoorsUIFrame + 40 : 40,
+ Common::Point(107, 9));
+ _dangerSenseUIFrame = (_dangerSenseUIFrame + 1) % 12;
+
+ // Handle the face UI elements for indicating clairvoyance status
+ _face1UIFrame = (_face1UIFrame + 1) % 4;
+ if (_face1State == 0)
+ _face1UIFrame += 4;
+ else if (_face1State == 2)
+ _face1UIFrame = 0;
+
+ _face2UIFrame = (_face2UIFrame + 1) % 4 + 12;
+ if (_face2State == 0)
+ _face2UIFrame += 252;
+ else if (_face2State == 2)
+ _face2UIFrame = 0;
+
+ if (!_vm->_party->_clairvoyanceActive) {
+ _face1UIFrame = 0;
+ _face2UIFrame = 8;
+ }
+
+ _borderSprites.draw(screen, _face1UIFrame, Common::Point(0, 32));
+ _borderSprites.draw(screen,
+ screen._windows[10]._enabled || screen._windows[2]._enabled ?
+ 52 : _face2UIFrame,
+ Common::Point(215, 32));
+
+ // Draw resistence indicators
+ if (!screen._windows[10]._enabled && !screen._windows[2]._enabled
+ && screen._windows[38]._enabled) {
+ _fecpSprites.draw(screen, _vm->_party->_fireResistence ? 1 : 0,
+ Common::Point(2, 2));
+ _fecpSprites.draw(screen, _vm->_party->_electricityResistence ? 3 : 2,
+ Common::Point(219, 2));
+ _fecpSprites.draw(screen, _vm->_party->_coldResistence ? 5 : 4,
+ Common::Point(2, 134));
+ _fecpSprites.draw(screen, _vm->_party->_poisonResistence ? 7 : 6,
+ Common::Point(219, 134));
+ } else {
+ _fecpSprites.draw(screen, _vm->_party->_fireResistence ? 9 : 8,
+ Common::Point(8, 8));
+ _fecpSprites.draw(screen, _vm->_party->_electricityResistence ? 10 : 11,
+ Common::Point(219, 8));
+ _fecpSprites.draw(screen, _vm->_party->_coldResistence ? 12 : 13,
+ Common::Point(8, 134));
+ _fecpSprites.draw(screen, _vm->_party->_poisonResistence ? 14 : 15,
+ Common::Point(219, 134));
+ }
+
+ // Draw UI element for blessed
+ _blessSprites.draw(screen, 16, Common::Point(33, 137));
+ if (_vm->_party->_blessed) {
+ _blessedUIFrame = (_blessedUIFrame + 1) % 4;
+ _blessSprites.draw(screen, _blessedUIFrame, Common::Point(33, 137));
+ }
+
+ // Draw UI element for power shield
+ if (_vm->_party->_powerShield) {
+ _powerShieldUIFrame = (_powerShieldUIFrame + 1) % 4;
+ _blessSprites.draw(screen, _powerShieldUIFrame + 4,
+ Common::Point(55, 137));
+ }
+
+ // Draw UI element for holy bonus
+ if (_vm->_party->_holyBonus) {
+ _holyBonusUIFrame = (_holyBonusUIFrame + 1) % 4;
+ _blessSprites.draw(screen, _holyBonusUIFrame + 8, Common::Point(160, 137));
+ }
+
+ // Draw UI element for heroism
+ if (_vm->_party->_heroism) {
+ _heroismUIFrame = (_heroismUIFrame + 1) % 4;
+ _blessSprites.draw(screen, _heroismUIFrame + 12, Common::Point(182, 137));
+ }
+
+ // Draw direction character if direction sense is active
+ if (_vm->_party->checkSkill(DIRECTION_SENSE) && !_vm->_noDirectionSense) {
+ const char *dirText = DIRECTION_TEXT_UPPER[_vm->_party->_mazeDirection];
+ Common::String msg = Common::String::format(
+ "\002""08\003""c\013""139\011""116%c\014""d\001", *dirText);
+ screen._windows[0].writeString(msg);
+ }
+
+ // Draw view frame
+ if (screen._windows[12]._enabled)
+ screen._windows[12].frame();
+}
+
+void Interface::doCombat() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Scripts &scripts = *_vm->_scripts;
+ Spells &spells = *_vm->_spells;
+ SoundManager &sound = *_vm->_sound;
+ bool upDoorText = _upDoorText;
+ bool reloadMap = false;
+
+ _upDoorText = false;
+ combat._combatMode = COMBATMODE_2;
+ _vm->_mode = MODE_COMBAT;
+
+ _iconSprites.load("combat.icn");
+ for (int idx = 1; idx < 16; ++idx)
+ _mainList[idx]._sprites = &_iconSprites;
+
+ // Set the combat buttons
+ setMainButtons(true);
+ mainIconsPrint();
+
+ combat._combatParty.clear();
+ combat._charsGone.clear();
+ combat._charsBlocked.clear();
+ combat._charsArray1[0] = 0;
+ combat._charsArray1[1] = 0;
+ combat._charsArray1[2] = 0;
+ combat._monstersAttacking = 0;
+ combat._partyRan = false;
+
+ // Set up the combat party
+ combat.setupCombatParty();
+ combat.setSpeedTable();
+
+ // Initialize arrays for character/monster states
+ combat._charsGone.resize(combat._speedTable.size());
+ combat._charsBlocked.resize(combat._speedTable.size());
+ Common::fill(&combat._charsGone[0], &combat._charsGone[0] + combat._speedTable.size(), 0);
+ Common::fill(&combat._charsBlocked[0], &combat._charsBlocked[0] + combat._speedTable.size(), false);
+
+ combat._whosSpeed = -1;
+ combat._whosTurn = -1;
+ resetHighlight();
+
+ nextChar();
+
+ if (!party._dead) {
+ combat.setSpeedTable();
+
+ if (_tillMove) {
+ combat.moveMonsters();
+ draw3d(true);
+ }
+
+ Window &w = screen._windows[2];
+ w.open();
+ bool breakFlag = false;
+
+ while (!_vm->shouldQuit() && !breakFlag) {
+ highlightChar(combat._whosTurn);
+ combat.setSpeedTable();
+
+ // Write out the description of the monsters being battled
+ w.writeString(combat.getMonsterDescriptions());
+ _iconSprites.draw(screen, 32, Common::Point(233, combat._monsterIndex * 10 + 27),
+ 0x8010000);
+ w.update();
+
+ // Wait for keypress
+ int index = 0;
+ do {
+ events.updateGameCounter();
+ draw3d(true);
+
+ if (++index == 5 && combat._attackMonsters[0] != -1) {
+ MazeMonster &monster = map._mobData._monsters[combat._monster2Attack];
+ MonsterStruct &monsterData = *monster._monsterData;
+ sound.playFX(monsterData._fx);
+ }
+
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ } while (!_vm->shouldQuit() && events.timeElapsed() < 1 && !_buttonValue);
+ } while (!_vm->shouldQuit() && !_buttonValue);
+ if (_vm->shouldQuit())
+ return;
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_TAB:
+ // Show the control panel
+ if (ControlPanel::show(_vm) == 2) {
+ reloadMap = true;
+ breakFlag = true;
+ } else {
+ highlightChar(combat._whosTurn);
+ }
+ break;
+
+ case Common::KEYCODE_1:
+ case Common::KEYCODE_2:
+ case Common::KEYCODE_3:
+ _buttonValue -= Common::KEYCODE_1;
+ if (combat._attackMonsters[_buttonValue] != -1) {
+ combat._monster2Attack = combat._attackMonsters[_buttonValue];
+ combat._monsterIndex = _buttonValue;
+ }
+ break;
+
+ case Common::KEYCODE_a:
+ // Attack
+ combat.attack(*combat._combatParty[combat._whosTurn], RT_SINGLE);
+ nextChar();
+ break;
+
+ case Common::KEYCODE_b:
+ // Block
+ combat.block();
+ nextChar();
+ break;
+
+ case Common::KEYCODE_c: {
+ // Cast spell
+ int spellId = CastSpell::show(_vm);
+ if (spellId != -1) {
+ Character *c = combat._combatParty[combat._whosTurn];
+ spells.castSpell(c, (MagicSpell)spellId);
+ nextChar();
+ } else {
+ highlightChar(combat._combatParty[combat._whosTurn]->_rosterId);
+ }
+ break;
+ }
+
+ case Common::KEYCODE_f:
+ // Quick Fight
+ combat.quickFight();
+ nextChar();
+ break;
+
+ case Common::KEYCODE_i:
+ // Info dialog
+ InfoDialog::show(_vm);
+ highlightChar(combat._whosTurn);
+ break;
+
+ case Common::KEYCODE_o:
+ // Fight Options
+ FightOptions::show(_vm);
+ highlightChar(combat._whosTurn);
+ break;
+
+ case Common::KEYCODE_q:
+ // Quick Reference dialog
+ QuickReferenceDialog::show(_vm);
+ highlightChar(combat._whosTurn);
+ break;
+
+ case Common::KEYCODE_r:
+ // Run from combat
+ combat.run();
+ nextChar();
+
+ if (_vm->_mode == MODE_1) {
+ warning("TODO: loss of treasure");
+ party.moveToRunLocation();
+ breakFlag = true;
+ }
+ break;
+
+ case Common::KEYCODE_u: {
+ int whosTurn = combat._whosTurn;
+ ItemsDialog::show(_vm, combat._combatParty[combat._whosTurn], ITEMMODE_COMBAT);
+ if (combat._whosTurn == whosTurn) {
+ highlightChar(combat._whosTurn);
+ } else {
+ combat._whosTurn = whosTurn;
+ nextChar();
+ }
+ break;
+ }
+
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ // Show character info
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)combat._combatParty.size()) {
+ CharacterInfo::show(_vm, _buttonValue);
+ }
+ highlightChar(combat._whosTurn);
+ break;
+
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_RIGHT:
+ // Rotate party direction left or right
+ if (_buttonValue == Common::KEYCODE_LEFT) {
+ party._mazeDirection = (party._mazeDirection == DIR_NORTH) ?
+ DIR_WEST : (Direction)((int)party._mazeDirection - 1);
+ } else {
+ party._mazeDirection = (party._mazeDirection == DIR_WEST) ?
+ DIR_NORTH : (Direction)((int)party._mazeDirection + 1);
+ }
+
+ _flipSky ^= 1;
+ if (_tillMove)
+ combat.moveMonsters();
+ party._stepped = true;
+ break;
+ }
+
+ // Handling for if the combat turn is complete
+ if (combat.allHaveGone()) {
+ Common::fill(&combat._charsGone[0], &combat._charsGone[combat._charsGone.size()], false);
+ Common::fill(&combat._charsBlocked[0], &combat._charsBlocked[combat._charsBlocked.size()], false);
+ combat.setSpeedTable();
+ combat._whosTurn = -1;
+ combat._whosSpeed = -1;
+ nextChar();
+
+ for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
+ MazeMonster &monster = map._mobData._monsters[idx];
+ if (monster._spriteId == 53) {
+ warning("TODO: Monster 53's HP is altered here?!");
+ }
+ }
+
+ combat.moveMonsters();
+ setIndoorsMonsters();
+ party.changeTime(1);
+ }
+
+ if (combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1
+ && combat._attackMonsters[2] == -1) {
+ party.changeTime(1);
+ draw3d(true);
+
+ if (combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1
+ && combat._attackMonsters[2] == -1)
+ break;
+ }
+
+ party.checkPartyDead();
+ if (party._dead || _vm->_mode != MODE_COMBAT)
+ break;
+ }
+
+ _vm->_mode = MODE_1;
+ if (combat._partyRan && (combat._attackMonsters[0] != -1 ||
+ combat._attackMonsters[1] != -1 || combat._attackMonsters[2] != -1)) {
+ party.checkPartyDead();
+ if (!party._dead) {
+ party.moveToRunLocation();
+
+ for (uint idx = 0; idx < combat._combatParty.size(); ++idx) {
+ Character &c = *combat._combatParty[idx];
+ if (c.isDisabled())
+ c._conditions[DEAD] = 1;
+ }
+ }
+ }
+
+ w.close();
+ events.clearEvents();
+
+ _vm->_mode = MODE_COMBAT;
+ draw3d(true);
+ party.giveTreasure();
+ _vm->_mode = MODE_1;
+ party._stepped = true;
+ unhighlightChar();
+
+ combat.setupCombatParty();
+ drawParty(true);
+ }
+
+ _iconSprites.load("main.icn");
+ for (int idx = 1; idx < 16; ++idx)
+ _mainList[idx]._sprites = &_iconSprites;
+
+ setMainButtons();
+ mainIconsPrint();
+ combat._monster2Attack = -1;
+
+ if (upDoorText) {
+ map.cellFlagLookup(party._mazePosition);
+ if (map._currentIsEvent)
+ scripts.checkEvents();
+ }
+
+ if (reloadMap) {
+ sound.playFX(51);
+ map._loadDarkSide = _vm->getGameID() != GType_WorldOfXeen;
+ map.load(_vm->getGameID() == GType_WorldOfXeen ? 28 : 29);
+ party._mazeDirection = _vm->getGameID() == GType_WorldOfXeen ?
+ DIR_EAST : DIR_SOUTH;
+ }
+
+ combat._combatMode = COMBATMODE_1;
+}
+
+void Interface::nextChar() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+
+ if (combat.allHaveGone())
+ return;
+ if ((combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1 &&
+ combat._attackMonsters[2] == -1) || combat._combatParty.size() == 0) {
+ _vm->_mode = MODE_1;
+ return;
+ }
+
+ // Loop for potentially multiple monsters attacking until it's time
+ // for one of the party's turn
+ for (;;) {
+ // Check if party is dead
+ party.checkPartyDead();
+ if (party._dead) {
+ _vm->_mode = MODE_1;
+ break;
+ }
+
+ int idx;
+ for (idx = 0; idx < (int)combat._speedTable.size(); ++idx) {
+ if (combat._whosTurn != -1) {
+ combat._charsGone[combat._whosTurn] = true;
+ }
+
+ combat._whosSpeed = (combat._whosSpeed + 1) % combat._speedTable.size();
+ combat._whosTurn = combat._speedTable[combat._whosSpeed];
+ if (combat.allHaveGone()) {
+ idx = -1;
+ break;
+ }
+
+ if (combat._whosTurn < (int)combat._combatParty.size()) {
+ // If it's a party member, only allow them to become active if
+ // they're still conscious
+ if (combat._combatParty[idx]->isDisabledOrDead())
+ continue;
+ }
+
+ break;
+ }
+
+ if (idx == -1) {
+ if (!combat.charsCantAct())
+ return;
+
+ combat.setSpeedTable();
+ combat._whosTurn = -1;
+ combat._whosSpeed = -1;
+ Common::fill(&combat._charsGone[0], &combat._charsGone[0] + combat._charsGone.size(), 0);
+ continue;
+ }
+
+ if (combat._whosTurn < (int)combat._combatParty.size()) {
+ // It's a party character's turn now, so highlight the character
+ if (!combat.allHaveGone()) {
+ highlightChar(combat._whosTurn);
+ }
+ break;
+ } else {
+ // It's a monster's turn to attack
+ combat.doMonsterTurn(0);
+ if (!party._dead) {
+ party.checkPartyDead();
+ if (party._dead)
+ break;
+ }
+ }
+ }
+}
+
+void Interface::spellFX(Character *c) {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+
+ // Ensure there's no alraedy running effect for the given character
+ uint charIndex;
+ for (charIndex = 0; charIndex < party._activeParty.size(); ++charIndex) {
+ if (&party._activeParty[charIndex] == c)
+ break;
+ }
+ if (charIndex == party._activeParty.size() || _charFX[charIndex])
+ return;
+
+ if (screen._windows[12]._enabled)
+ screen._windows[12].close();
+
+ if (combat._combatMode == COMBATMODE_2) {
+ for (uint idx = 0; idx < combat._combatParty.size(); ++idx) {
+ if (combat._combatParty[idx]->_rosterId == c->_rosterId) {
+ charIndex = idx;
+ break;
+ }
+ }
+ }
+
+ int tillMove = _tillMove;
+ _tillMove = 0;
+ sound.playFX(20);
+
+ for (int frameNum = 0; frameNum < 4; ++frameNum) {
+ events.updateGameCounter();
+ _spellFxSprites.draw(screen, frameNum, Common::Point(
+ CHAR_FACES_X[charIndex], 150));
+
+ if (!screen._windows[11]._enabled)
+ draw3d(false);
+
+ screen._windows[0].update();
+ events.wait(screen._windows[11]._enabled ? 2 : 1);
+ }
+
+ drawParty(true);
+ _tillMove = tillMove;
+}
+
+
+} // End of namespace Xeen
diff --git a/engines/xeen/interface.h b/engines/xeen/interface.h
new file mode 100644
index 0000000000..074d7fdafb
--- /dev/null
+++ b/engines/xeen/interface.h
@@ -0,0 +1,183 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_INTERFACE_H
+#define XEEN_INTERFACE_H
+
+#include "common/scummsys.h"
+#include "xeen/dialogs.h"
+#include "xeen/interface_map.h"
+#include "xeen/party.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+class XeenEngine;
+
+#define MINIMAP_SIZE 7
+#define HILIGHT_CHAR_DISABLED -2
+
+/**
+ * Class responsible for drawing the images of the characters in the party
+ */
+class PartyDrawer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _dseFace;
+ SpriteResource _hpSprites;
+ SpriteResource _restoreSprites;
+ int _hiliteChar;
+public:
+ PartyDrawer(XeenEngine *vm);
+
+ void drawParty(bool updateFlag);
+
+ void highlightChar(int charId);
+
+ void unhighlightChar();
+
+ void resetHighlight();
+};
+
+/**
+ * Implements the main in-game interface
+ */
+class Interface: public ButtonContainer, public InterfaceMap, public PartyDrawer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _uiSprites;
+ SpriteResource _iconSprites;
+ SpriteResource _borderSprites;
+ SpriteResource _spellFxSprites;
+ SpriteResource _fecpSprites;
+ SpriteResource _blessSprites;
+ DrawStruct _mainList[16];
+
+ bool _buttonsLoaded;
+ int _steppingFX;
+ int _blessedUIFrame;
+ int _powerShieldUIFrame;
+ int _holyBonusUIFrame;
+ int _heroismUIFrame;
+ int _flipUIFrame;
+
+ void initDrawStructs();
+
+ void loadSprites();
+
+ void setupBackground();
+
+ void setMainButtons(bool combatMode = false);
+
+ void chargeStep();
+
+ /**
+ * Handles incrementing game time
+ */
+ void stepTime();
+
+ void doStepCode();
+
+ /**
+ * Check movement in the given direction
+ */
+ bool checkMoveDirection(int key);
+
+ /**
+ * Handle doing the falling
+ */
+ void handleFalling();
+
+ void saveFall();
+
+ void fall(int v);
+
+ /**
+ * Shake the screen
+ */
+ void shake(int time);
+
+ /**
+ * Draw the minimap
+ */
+ void drawMiniMap();
+
+ /**
+ * Select next character or monster to be attacking
+ */
+ void nextChar();
+public:
+ int _intrIndex1;
+ Common::String _interfaceText;
+ int _falling;
+ int _face1State, _face2State;
+ int _face1UIFrame, _face2UIFrame;
+ int _spotDoorsUIFrame;
+ int _dangerSenseUIFrame;
+ int _levitateUIFrame;
+ bool _upDoorText;
+ Common::String _screenText;
+ byte _tillMove;
+ int _charFX[6];
+public:
+ Interface(XeenEngine *vm);
+
+ virtual ~Interface() {}
+
+ void setup();
+
+ void manageCharacters(bool soundPlayed);
+
+ void startup();
+
+ void mainIconsPrint();
+
+ /**
+ * Start the party falling
+ */
+ void startFalling(bool v);
+
+ /**
+ * Waits for a keypress or click, whilst still allowing the game scene to
+ * be animated.
+ */
+ void perform();
+
+ void rest();
+
+ void bash(const Common::Point &pt, Direction direction);
+
+ void draw3d(bool updateFlag, bool skipDelay = false);
+
+ /**
+ * Draw the display borders
+ */
+ void assembleBorder();
+
+ void doCombat();
+
+ void spellFX(Character *c);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_INTERFACE_H */
diff --git a/engines/xeen/interface_map.cpp b/engines/xeen/interface_map.cpp
new file mode 100644
index 0000000000..af39f38c28
--- /dev/null
+++ b/engines/xeen/interface_map.cpp
@@ -0,0 +1,4445 @@
+/* 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.
+ *
+ */
+
+#include "xeen/interface.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+OutdoorDrawList::OutdoorDrawList() : _sky1(_data[0]), _sky2(_data[1]),
+ _groundSprite(_data[2]), _attackImgs1(&_data[124]), _attackImgs2(&_data[95]),
+ _attackImgs3(&_data[76]), _attackImgs4(&_data[53]), _groundTiles(&_data[3]) {
+ _data[0] = DrawStruct(0, 8, 8);
+ _data[1] = DrawStruct(1, 8, 25);
+ _data[2] = DrawStruct(0, 8, 67);
+ _data[3] = DrawStruct(0, 8, 67);
+ _data[4] = DrawStruct(0, 38, 67);
+ _data[5] = DrawStruct(0, 84, 67);
+ _data[6] = DrawStruct(0, 134, 67);
+ _data[7] = DrawStruct(0, 117, 67);
+ _data[8] = DrawStruct(0, 117, 67);
+ _data[9] = DrawStruct(0, 103, 67);
+ _data[10] = DrawStruct(0, 8, 73);
+ _data[11] = DrawStruct(0, 8, 73);
+ _data[12] = DrawStruct(0, 30, 73);
+ _data[13] = DrawStruct(0, 181, 73);
+ _data[14] = DrawStruct(0, 154, 73);
+ _data[15] = DrawStruct(0, 129, 73);
+ _data[16] = DrawStruct(0, 87, 73);
+ _data[17] = DrawStruct(0, 8, 81);
+ _data[18] = DrawStruct(0, 8, 81);
+ _data[19] = DrawStruct(0, 202, 81);
+ _data[20] = DrawStruct(0, 145, 81);
+ _data[21] = DrawStruct(0, 63, 81);
+ _data[22] = DrawStruct(0, 8, 93);
+ _data[23] = DrawStruct(0, 169, 93);
+ _data[24] = DrawStruct(0, 31, 93);
+ _data[25] = DrawStruct(0, 8, 109);
+ _data[26] = DrawStruct(0, 201, 109);
+ _data[27] = DrawStruct(0, 8, 109);
+ _data[28] = DrawStruct(1, -64, 61, 14, SPRFLAG_SCENE_CLIPPED);
+ _data[29] = DrawStruct(1, -40, 61, 14, 0);
+ _data[30] = DrawStruct(1, -16, 61, 14, 0);
+ _data[31] = DrawStruct(1, 8, 61, 14, 0);
+ _data[32] = DrawStruct(1, 128, 61, 14, SPRFLAG_HORIZ_FLIPPED | SPRFLAG_SCENE_CLIPPED);
+ _data[33] = DrawStruct(1, 104, 61, 14, SPRFLAG_HORIZ_FLIPPED);
+ _data[34] = DrawStruct(1, 80, 61, 14, SPRFLAG_HORIZ_FLIPPED);
+ _data[35] = DrawStruct(1, 56, 61, 14, SPRFLAG_HORIZ_FLIPPED);
+ _data[36] = DrawStruct(1, 32, 61, 14, 0);
+ _data[37] = DrawStruct(0, -9, 61, 14, 0);
+ _data[38] = DrawStruct(0, -58, 61, 14, 0);
+ _data[39] = DrawStruct(0, 40, 61, 14, 0);
+ _data[40] = DrawStruct(0, -82, 61, 14, 0);
+ _data[41] = DrawStruct(0, 64, 61, 14, 0);
+ _data[42] = DrawStruct(0, -41, 61, 14, 0);
+ _data[43] = DrawStruct(0, -26, 61, 14, 0);
+ _data[44] = DrawStruct(0, -34, 61, 14, 0);
+ _data[45] = DrawStruct(0, -16, 61, 14, 0);
+ _data[46] = DrawStruct(0, 23, 61, 14, 0);
+ _data[47] = DrawStruct(0, 16, 61, 14, 0);
+ _data[48] = DrawStruct(0, -58, 61, 14, 0);
+ _data[49] = DrawStruct(0, 40, 61, 14, 0);
+ _data[50] = DrawStruct(0, -17, 61, 14, 0);
+ _data[51] = DrawStruct(0, -1, 58, 14, 0);
+ _data[52] = DrawStruct(0, -9, 58, 14, 0);
+ _data[53] = DrawStruct(0, 72, 58, 12, 0);
+ _data[54] = DrawStruct(0, 72, 58, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[55] = DrawStruct(0, 69, 63, 12, 0);
+ _data[56] = DrawStruct(0, 75, 63, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[57] = DrawStruct(0, 73, 53, 12, 0);
+ _data[58] = DrawStruct(0, 71, 53, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[59] = DrawStruct(0, 80, 57, 12, 0);
+ _data[60] = DrawStruct(0, 64, 57, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[61] = DrawStruct(2, -11, 54, 8, 0);
+ _data[62] = DrawStruct(1, -21, 54, 11, 0);
+ _data[63] = DrawStruct(2, 165, 54, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[64] = DrawStruct(1, 86, 54, 11, SPRFLAG_HORIZ_FLIPPED);
+ _data[65] = DrawStruct(1, 33, 54, 11, 0);
+ _data[66] = DrawStruct(0, -8, 54, 12, 0);
+ _data[67] = DrawStruct(0, -73, 54, 12, 0);
+ _data[68] = DrawStruct(0, 57, 54, 12, 0);
+ _data[69] = DrawStruct(0, -65, 54, 12, 0);
+ _data[70] = DrawStruct(0, -81, 54, 12, 0);
+ _data[71] = DrawStruct(0, 49, 54, 12, 0);
+ _data[72] = DrawStruct(0, 65, 54, 12, 0);
+ _data[73] = DrawStruct(0, -24, 54, 12, 0);
+ _data[74] = DrawStruct(0, 9, 50, 12, 0);
+ _data[75] = DrawStruct(0, -8, 50, 12, 0);
+ _data[76] = DrawStruct(0, 72, 53, 8, 0);
+ _data[77] = DrawStruct(0, 72, 53, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[78] = DrawStruct(0, 77, 58, 8, 0);
+ _data[79] = DrawStruct(0, 67, 58, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[80] = DrawStruct(0, 81, 47, 8, 0);
+ _data[81] = DrawStruct(0, 63, 47, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[82] = DrawStruct(0, 94, 52, 8, 0);
+ _data[83] = DrawStruct(0, 50, 52, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[84] = DrawStruct(2, 8, 40);
+ _data[85] = DrawStruct(2, 146, 40, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[86] = DrawStruct(1, 32, 40, 6, 0);
+ _data[87] = DrawStruct(0, -7, 30, 7, 0);
+ _data[88] = DrawStruct(0, -112, 30, 7, SPRFLAG_SCENE_CLIPPED);
+ _data[89] = DrawStruct(0, 98, 30, 7, SPRFLAG_SCENE_CLIPPED);
+ _data[90] = DrawStruct(0, -112, 30, 8, SPRFLAG_SCENE_CLIPPED);
+ _data[91] = DrawStruct(0, 98, 30, 8, SPRFLAG_SCENE_CLIPPED);
+ _data[92] = DrawStruct(0, -38, 30, 8, 0);
+ _data[93] = DrawStruct(0, 25, 30, 8, 0);
+ _data[94] = DrawStruct(0, -7, 30, 8, 0);
+ _data[95] = DrawStruct(0, 72, 48, 4, 0);
+ _data[96] = DrawStruct(0, 72, 48, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[97] = DrawStruct(0, 85, 53, 4, 0);
+ _data[98] = DrawStruct(0, 59, 53, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[99] = DrawStruct(0, 89, 41, 4, 0);
+ _data[100] = DrawStruct(0, 55, 41, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[101] = DrawStruct(0, 106, 47, 4, 0);
+ _data[102] = DrawStruct(0, 38, 47, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[103] = DrawStruct(0, 8, 24);
+ _data[104] = DrawStruct(0, 169, 24, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[105] = DrawStruct(1, 32, 24);
+ _data[106] = DrawStruct(0, -23, 40, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[107] = DrawStruct(0, 200, 40, 0, SPRFLAG_HORIZ_FLIPPED | SPRFLAG_SCENE_CLIPPED);
+ _data[108] = DrawStruct(0, 8, 47);
+ _data[109] = DrawStruct(0, 169, 47, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[110] = DrawStruct(1, -56, -4, 0x8000, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[111] = DrawStruct(0, -5, 2, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[112] = DrawStruct(0, -67, 2, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[113] = DrawStruct(0, 44, 73);
+ _data[114] = DrawStruct(0, 44, 73);
+ _data[115] = DrawStruct(0, 58, 14, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[116] = DrawStruct(0, 169, 73);
+ _data[117] = DrawStruct(0, 169, 73);
+ _data[118] = DrawStruct(0, -5, 14, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[119] = DrawStruct(0, 110, 73);
+ _data[120] = DrawStruct(0, 110, 73);
+ _data[121] = DrawStruct(0, -5, 14, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[122] = DrawStruct(0, 110, 73);
+ _data[123] = DrawStruct(0, 110, 73);
+ _data[124] = DrawStruct(0, 72, 43);
+ _data[125] = DrawStruct(0, 72, 43, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[126] = DrawStruct(0, 93, 48);
+ _data[127] = DrawStruct(0, 51, 48, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[128] = DrawStruct(0, 97, 36);
+ _data[129] = DrawStruct(0, 47, 36, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[130] = DrawStruct(0, 118, 42);
+ _data[131] = DrawStruct(0, 26, 42, 0, SPRFLAG_HORIZ_FLIPPED);
+}
+
+/*------------------------------------------------------------------------*/
+
+IndoorDrawList::IndoorDrawList() :
+ _sky1(_data[0]), _sky2(_data[1]), _ground(_data[2]), _horizon(_data[28]),
+ _swl_0F1R(_data[146]), _swl_0F1L(_data[144]), _swl_1F1R(_data[134]),
+ _swl_1F1L(_data[133]), _swl_2F2R(_data[110]), _swl_2F1R(_data[109]),
+ _swl_2F1L(_data[108]), _swl_2F2L(_data[107]), _swl_3F1R(_data[ 78]),
+ _swl_3F2R(_data[ 77]), _swl_3F3R(_data[ 76]), _swl_3F4R(_data[ 75]),
+ _swl_3F1L(_data[ 74]), _swl_3F2L(_data[ 73]), _swl_3F3L(_data[ 72]),
+ _swl_3F4L(_data[ 71]), _swl_4F4R(_data[ 33]), _swl_4F3R(_data[ 34]),
+ _swl_4F2R(_data[ 35]), _swl_4F1R(_data[ 36]), _swl_4F1L(_data[ 32]),
+ _swl_4F2L(_data[ 31]), _swl_4F3L(_data[ 30]), _swl_4F4L(_data[ 29]),
+ _fwl_4F4R(_data[ 45]), _fwl_4F3R(_data[ 44]), _fwl_4F2R(_data[ 43]),
+ _fwl_4F1R(_data[ 42]), _fwl_4F( _data[ 41]), _fwl_4F1L(_data[ 40]),
+ _fwl_4F2L(_data[ 39]), _fwl_4F3L(_data[ 38]), _fwl_4F4L(_data[ 37]),
+ _fwl_2F1R(_data[121]), _fwl_2F( _data[120]), _fwl_2F1L(_data[119]),
+ _fwl_3F2R(_data[ 91]), _fwl_3F1R(_data[ 90]), _fwl_3F( _data[ 89]),
+ _fwl_3F1L(_data[ 88]), _fwl_3F2L(_data[ 87]), _fwl_1F( _data[147]),
+ _fwl_1F1R(_data[145]), _fwl_1F1L(_data[143]),
+ _groundTiles(&_data[3]),
+ _objects0(_data[149]), _objects1(_data[125]), _objects2(_data[126]),
+ _objects3(_data[127]), _objects4(_data[97]), _objects5(_data[98]),
+ _objects6(_data[99]), _objects7(_data[55]), _objects8(_data[56]),
+ _objects9(_data[58]), _objects10(_data[57]), _objects11(_data[59]),
+ _attackImgs1(&_data[162]), _attackImgs2(&_data[135]),
+ _attackImgs3(&_data[111]), _attackImgs4(&_data[79]) {
+ // Setup draw structure positions
+ _data[0] = DrawStruct(0, 8, 8);
+ _data[1] = DrawStruct(1, 8, 25);
+ _data[2] = DrawStruct(0, 8, 67);
+ _data[3] = DrawStruct(0, 8, 67);
+ _data[4] = DrawStruct(0, 38, 67);
+ _data[5] = DrawStruct(0, 84, 67);
+ _data[6] = DrawStruct(0, 134, 67);
+ _data[7] = DrawStruct(0, 117, 67);
+ _data[8] = DrawStruct(0, 117, 67);
+ _data[9] = DrawStruct(0, 103, 67);
+ _data[10] = DrawStruct(0, 8, 73);
+ _data[11] = DrawStruct(0, 8, 73);
+ _data[12] = DrawStruct(0, 30, 73);
+ _data[13] = DrawStruct(0, 181, 73);
+ _data[14] = DrawStruct(0, 154, 73);
+ _data[15] = DrawStruct(0, 129, 73);
+ _data[16] = DrawStruct(0, 87, 73);
+ _data[17] = DrawStruct(0, 8, 81);
+ _data[18] = DrawStruct(0, 8, 81);
+ _data[19] = DrawStruct(0, 202, 81);
+ _data[20] = DrawStruct(0, 145, 81);
+ _data[21] = DrawStruct(0, 63, 81);
+ _data[22] = DrawStruct(0, 8, 93);
+ _data[23] = DrawStruct(0, 169, 93);
+ _data[24] = DrawStruct(0, 31, 93);
+ _data[25] = DrawStruct(0, 8, 109);
+ _data[26] = DrawStruct(0, 201, 109);
+ _data[27] = DrawStruct(0, 8, 109);
+ _data[28] = DrawStruct(7, 8, 64);
+ _data[29] = DrawStruct(22, 32, 60);
+ _data[30] = DrawStruct(20, 56, 60);
+ _data[31] = DrawStruct(18, 80, 60);
+ _data[32] = DrawStruct(16, 104, 60);
+ _data[33] = DrawStruct(23, 152, 60, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[34] = DrawStruct(21, 144, 60, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[35] = DrawStruct(19, 131, 60, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[36] = DrawStruct(17, 120, 60, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[37] = DrawStruct(14, 8, 60);
+ _data[38] = DrawStruct(12, 32, 60);
+ _data[39] = DrawStruct(10, 56, 60);
+ _data[40] = DrawStruct(14, 80, 60);
+ _data[41] = DrawStruct(14, 104, 60);
+ _data[42] = DrawStruct(14, 128, 60);
+ _data[43] = DrawStruct(14, 152, 60);
+ _data[44] = DrawStruct(8, 176, 60);
+ _data[45] = DrawStruct(8, 200, 60);
+ _data[46] = DrawStruct(0, -64, 61, 14, 0);
+ _data[47] = DrawStruct(0, -40, 61, 14, 0);
+ _data[48] = DrawStruct(0, -16, 61, 14, 0);
+ _data[49] = DrawStruct(0, 8, 61, 14, 0);
+ _data[50] = DrawStruct(0, 32, 61, 14, 0);
+ _data[51] = DrawStruct(0, 56, 61, 14, 0);
+ _data[52] = DrawStruct(0, 80, 61, 14, 0);
+ _data[53] = DrawStruct(0, 104, 61, 14, 0);
+ _data[54] = DrawStruct(0, 128, 61, 14, 0);
+ _data[55] = DrawStruct(0, -9, 58, 14, 0);
+ _data[56] = DrawStruct(0, -34, 58, 14, 0);
+ _data[57] = DrawStruct(0, 16, 58, 14, 0);
+ _data[58] = DrawStruct(0, -58, 58, 14, 0);
+ _data[59] = DrawStruct(0, 40, 58, 14, 0);
+ _data[60] = DrawStruct(0, -41, 58, 14, 0);
+ _data[61] = DrawStruct(0, -26, 58, 14, 0);
+ _data[62] = DrawStruct(0, -34, 58, 14, 0);
+ _data[63] = DrawStruct(0, -16, 58, 14, 0);
+ _data[64] = DrawStruct(0, 23, 58, 14, 0);
+ _data[65] = DrawStruct(0, 16, 58, 14, 0);
+ _data[66] = DrawStruct(0, -58, 58, 14, 0);
+ _data[67] = DrawStruct(0, 40, 58, 14, 0);
+ _data[68] = DrawStruct(0, -17, 58, 14, 0);
+ _data[69] = DrawStruct(0, -1, 58, 14, 0);
+ _data[70] = DrawStruct(0, -9, 58, 14, 0);
+ _data[71] = DrawStruct(14, 8, 58);
+ _data[72] = DrawStruct(12, 8, 55);
+ _data[73] = DrawStruct(10, 32, 52);
+ _data[74] = DrawStruct(14, 88, 52);
+ _data[75] = DrawStruct(14, 128, 52, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[76] = DrawStruct(14, 152, 52, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[77] = DrawStruct(0, 176, 55, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[78] = DrawStruct(0, 200, 58, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[79] = DrawStruct(0, 72, 58, 12, 0);
+ _data[80] = DrawStruct(0, 72, 58, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[81] = DrawStruct(0, 69, 63, 12, 0);
+ _data[82] = DrawStruct(0, 75, 63, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[83] = DrawStruct(0, 73, 53, 12, 0);
+ _data[84] = DrawStruct(0, 71, 53, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[85] = DrawStruct(0, 80, 57, 12, 0);
+ _data[86] = DrawStruct(0, 64, 57, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[87] = DrawStruct(7, -24, 52, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[88] = DrawStruct(7, 32, 52);
+ _data[89] = DrawStruct(7, 88, 52);
+ _data[90] = DrawStruct(0, 144, 52);
+ _data[91] = DrawStruct(0, 200, 52, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[92] = DrawStruct(0, -79, 52, 11, SPRFLAG_SCENE_CLIPPED);
+ _data[93] = DrawStruct(0, -27, 52, 11, 0);
+ _data[94] = DrawStruct(0, 32, 52, 11, 0);
+ _data[95] = DrawStruct(0, 89, 52, 11, 0);
+ _data[96] = DrawStruct(0, 145, 52, 11, SPRFLAG_SCENE_CLIPPED);
+ _data[97] = DrawStruct(0, -8, 50, 12, 0);
+ _data[98] = DrawStruct(0, -65, 50, 12, 0);
+ _data[99] = DrawStruct(0, 49, 50, 12, 0);
+ _data[100] = DrawStruct(0, -65, 50, 12, 0);
+ _data[101] = DrawStruct(0, -81, 50, 12, 0);
+ _data[102] = DrawStruct(0, 49, 50, 12, 0);
+ _data[103] = DrawStruct(0, 65, 50, 12, 0);
+ _data[104] = DrawStruct(0, -24, 50, 12, 0);
+ _data[105] = DrawStruct(0, 9, 50, 12, 0);
+ _data[106] = DrawStruct(0, -8, 50, 12, 0);
+ _data[107] = DrawStruct(7, 8, 48);
+ _data[108] = DrawStruct(7, 64, 40);
+ _data[109] = DrawStruct(6, 144, 40, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[110] = DrawStruct(6, 200, 48, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[111] = DrawStruct(0, 72, 53, 8, 0);
+ _data[112] = DrawStruct(0, 72, 53, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[113] = DrawStruct(0, 77, 58, 8, 0);
+ _data[114] = DrawStruct(0, 67, 58, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[115] = DrawStruct(0, 81, 47, 8, 0);
+ _data[116] = DrawStruct(0, 63, 47, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[117] = DrawStruct(0, 94, 52, 8, 0);
+ _data[118] = DrawStruct(0, 50, 52, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[119] = DrawStruct(6, -40, 40, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[120] = DrawStruct(6, 64, 40);
+ _data[121] = DrawStruct(0, 168, 40, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[122] = DrawStruct(0, -72, 40, 6, SPRFLAG_SCENE_CLIPPED);
+ _data[123] = DrawStruct(0, 32, 40, 6, 0);
+ _data[124] = DrawStruct(0, 137, 40, 6, SPRFLAG_SCENE_CLIPPED);
+ _data[125] = DrawStruct(0, -7, 25, 7, 0);
+ _data[126] = DrawStruct(0, -112, 25, 7, SPRFLAG_SCENE_CLIPPED);
+ _data[127] = DrawStruct(0, 98, 25, 7, SPRFLAG_SCENE_CLIPPED);
+ _data[128] = DrawStruct(0, -112, 29, 8, SPRFLAG_SCENE_CLIPPED);
+ _data[129] = DrawStruct(0, 98, 29, 8, SPRFLAG_SCENE_CLIPPED);
+ _data[130] = DrawStruct(0, -38, 29, 8, 0);
+ _data[131] = DrawStruct(0, 25, 29, 8, 0);
+ _data[132] = DrawStruct(0, -7, 29, 8, 0);
+ _data[133] = DrawStruct(6, 32, 24);
+ _data[134] = DrawStruct(0, 168, 24, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[135] = DrawStruct(0, 72, 48, 4, 0);
+ _data[136] = DrawStruct(0, 72, 48, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[137] = DrawStruct(0, 85, 53, 4, 0);
+ _data[138] = DrawStruct(0, 59, 53, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[139] = DrawStruct(0, 89, 41, 4, 0);
+ _data[140] = DrawStruct(0, 55, 41, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[141] = DrawStruct(0, 106, 47, 4, 0);
+ _data[142] = DrawStruct(0, 38, 47, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[143] = DrawStruct(0, -136, 24, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[144] = DrawStruct(0, 8, 12);
+ _data[145] = DrawStruct(0, 32, 24);
+ _data[146] = DrawStruct(0, 200, 12, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[147] = DrawStruct(0, 200, 24, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[148] = DrawStruct(0, 32, 24);
+ _data[149] = DrawStruct(0, -5, 2, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[150] = DrawStruct(0, -67, 10, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[151] = DrawStruct(0, 44, 73);
+ _data[152] = DrawStruct(0, 44, 73);
+ _data[153] = DrawStruct(0, 58, 14, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[154] = DrawStruct(0, 169, 73);
+ _data[155] = DrawStruct(0, 169, 73);
+ _data[156] = DrawStruct(0, -5, 14, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[157] = DrawStruct(0, 110, 73);
+ _data[158] = DrawStruct(0, 110, 73);
+ _data[159] = DrawStruct(0, -5, 14, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[160] = DrawStruct(0, 110, 73);
+ _data[161] = DrawStruct(0, 110, 73);
+ _data[162] = DrawStruct(0, 72, 43);
+ _data[163] = DrawStruct(0, 72, 43, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[164] = DrawStruct(0, 93, 48);
+ _data[165] = DrawStruct(0, 51, 48, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[166] = DrawStruct(0, 97, 36);
+ _data[167] = DrawStruct(0, 47, 36, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[168] = DrawStruct(0, 118, 42);
+ _data[169] = DrawStruct(0, 26, 42, 0, SPRFLAG_HORIZ_FLIPPED);
+}
+
+/*------------------------------------------------------------------------*/
+
+InterfaceMap::InterfaceMap(XeenEngine *vm): _vm(vm) {
+ Common::fill(&_wp[0], &_wp[20], 0);
+ Common::fill(&_wo[0], &_wo[308], 0);
+ _overallFrame = 0;
+ _flipWater = false;
+ _flipGround = false;
+ _flipSky = false;
+ _flipDefaultGround = false;
+ _isAttacking = false;
+ _charsShooting = false;
+ _objNumber = 0;
+ _combatFloatCounter = 0;
+ _thinWall = false;
+ _isAnimReset = false;
+ _overallFrame = 0;
+ _openDoor = false;
+}
+
+void InterfaceMap::drawMap() {
+ Combat &combat = *_vm->_combat;
+ Map &map = *_vm->_map;
+ Scripts &scripts = *_vm->_scripts;
+
+ const int COMBAT_POS_X[3][2] = { { 102, 134 }, { 36, 67 }, { 161, 161 } };
+ const int INDOOR_INDEXES[3] = { 157, 151, 154 };
+ const int OUTDOOR_INDEXES[3] = { 119, 113, 116 };
+ const int COMBAT_OFFSET_X[4] = { 8, 6, 4, 2 };
+
+ MazeObject &objObject = map._mobData._objects[_objNumber];
+ Direction partyDirection = _vm->_party->_mazeDirection;
+ int objNum = _objNumber - 1;
+
+ // Loop to update the frame numbers for each maze object, applying the animation frame
+ // limits as specified by the map's _animationInfo listing
+ for (uint idx = 0; idx < map._mobData._objects.size(); ++idx) {
+ MazeObject &mazeObject = map._mobData._objects[idx];
+ AnimationEntry &animEntry = map._animationInfo[mazeObject._spriteId];
+ int directionIndex = DIRECTION_ANIM_POSITIONS[mazeObject._direction][partyDirection];
+
+ if (_isAnimReset) {
+ mazeObject._frame = animEntry._frame1._frames[directionIndex];
+ } else {
+ ++mazeObject._frame;
+ if ((int)idx == objNum && scripts._animCounter > 0 && (
+ objObject._spriteId == (_vm->_files->_isDarkCc ? 15 : 16) ||
+ objObject._spriteId == 58 || objObject._spriteId == 73)) {
+ if (mazeObject._frame > 4 || mazeObject._spriteId == 58)
+ mazeObject._frame = 1;
+ }
+ else if (mazeObject._frame >= animEntry._frame2._frames[directionIndex]) {
+ mazeObject._frame = animEntry._frame1._frames[directionIndex];
+ }
+ }
+
+ mazeObject._flipped = animEntry._flipped._flags[directionIndex];
+ }
+
+ if (map._isOutdoors) {
+ // Outdoors drawing
+ for (int idx = 0; idx < 44; ++idx)
+ _outdoorList[OUTDOOR_DRAWSTRCT_INDEXES[idx]]._frame = -1;
+
+ if (combat._monstersAttacking) {
+ for (int idx = 0; idx < 8; ++idx) {
+ if (_outdoorList._attackImgs4[idx]._sprites)
+ _outdoorList._attackImgs4[idx]._frame = 0;
+ else if (_outdoorList._attackImgs3[idx]._sprites)
+ _outdoorList._attackImgs3[idx]._frame = 1;
+ else if (_outdoorList._attackImgs2[idx]._sprites)
+ _outdoorList._attackImgs2[idx]._frame = 2;
+ else if (_outdoorList._attackImgs1[idx]._sprites)
+ _outdoorList._attackImgs1[idx]._frame = 0;
+ }
+ } else if (_charsShooting) {
+ for (int idx = 0; idx < 8; ++idx) {
+ if (_outdoorList._attackImgs1[idx]._sprites)
+ _outdoorList._attackImgs1[idx]._frame = 0;
+ else if (_outdoorList._attackImgs2[idx]._sprites)
+ _outdoorList._attackImgs2[idx]._frame = 1;
+ else if (_outdoorList._attackImgs3[idx]._sprites)
+ _outdoorList._attackImgs3[idx]._frame = 2;
+ else if (_outdoorList._attackImgs4[idx]._sprites)
+ _outdoorList._attackImgs1[idx]._frame = 0;
+ }
+ }
+
+ _isAnimReset = false;
+ int attackMon2 = combat._attackMonsters[2];
+
+ for (int idx = 0; idx < 3; ++idx) {
+ DrawStruct &ds1 = _outdoorList[OUTDOOR_INDEXES[idx] + 1];
+ DrawStruct &ds2 = _outdoorList[OUTDOOR_INDEXES[idx]];
+ ds1._sprites = nullptr;
+ ds2._sprites = nullptr;
+
+ if (combat._charsArray1[idx]) {
+ int vIndex = combat._attackMonsters[1] && !attackMon2 ? 1 : 0;
+ combat._charsArray1[idx]--;
+
+ if (combat._monPow[idx]) {
+ ds2._x = COMBAT_POS_X[idx][vIndex];
+ ds2._frame = 0;
+ ds2._scale = combat._monsterScale[idx];
+
+ if (ds2._scale == 0x8000) {
+ ds2._x /= 3;
+ ds2._y = 60;
+ } else {
+ ds2._y = 73;
+ }
+
+ ds2._flags = SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED;
+ ds2._sprites = &_charPowSprites;
+ }
+
+ if (combat._elemPow[idx]) {
+ ds1._x = COMBAT_POS_X[idx][vIndex] + COMBAT_OFFSET_X[idx];
+ ds1._frame = combat._elemPow[idx];
+ ds1._scale = combat._elemScale[idx];
+
+ if (ds1._scale == 0x8000)
+ ds1._x /= 3;
+ ds1._flags = SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED;
+ ds1._sprites = &_charPowSprites;
+ }
+ }
+ }
+
+ setOutdoorsMonsters();
+ setOutdoorsObjects();
+
+ _outdoorList[123]._sprites = nullptr;
+ _outdoorList[122]._sprites = nullptr;
+ _outdoorList[121]._sprites = nullptr;
+
+ int monsterIndex;
+ if (combat._attackMonsters[0] != -1 && map._mobData._monsters[combat._attackMonsters[0]]._frame >= 8) {
+ _outdoorList[121] = _outdoorList[118];
+ _outdoorList[122] = _outdoorList[119];
+ _outdoorList[123] = _outdoorList[120];
+ _outdoorList[118]._sprites = nullptr;
+ _outdoorList[119]._sprites = nullptr;
+ _outdoorList[120]._sprites = nullptr;
+ monsterIndex = 1;
+ } else if (combat._attackMonsters[1] != -1 && map._mobData._monsters[combat._attackMonsters[1]]._frame >= 8) {
+ _outdoorList[121] = _outdoorList[112];
+ _outdoorList[122] = _outdoorList[113];
+ _outdoorList[123] = _outdoorList[114];
+ _outdoorList[112]._sprites = nullptr;
+ _outdoorList[113]._sprites = nullptr;
+ _outdoorList[124]._sprites = nullptr;
+ monsterIndex = 2;
+ } else if (combat._attackMonsters[2] != -1 && map._mobData._monsters[combat._attackMonsters[2]]._frame >= 8) {
+ _outdoorList[121] = _outdoorList[115];
+ _outdoorList[122] = _outdoorList[116];
+ _outdoorList[123] = _outdoorList[117];
+ _outdoorList[115]._sprites = nullptr;
+ _outdoorList[116]._sprites = nullptr;
+ _outdoorList[117]._sprites = nullptr;
+ monsterIndex = 3;
+ } else {
+ monsterIndex = 0;
+ }
+
+ drawOutdoors();
+
+ switch (monsterIndex) {
+ case 1:
+ _outdoorList[118] = _outdoorList[121];
+ _outdoorList[119] = _outdoorList[122];
+ _outdoorList[120] = _outdoorList[123];
+ break;
+ case 2:
+ _outdoorList[112] = _outdoorList[121];
+ _outdoorList[113] = _outdoorList[122];
+ _outdoorList[114] = _outdoorList[123];
+ break;
+ case 3:
+ _outdoorList[115] = _outdoorList[121];
+ _outdoorList[116] = _outdoorList[122];
+ _outdoorList[117] = _outdoorList[123];
+ break;
+ default:
+ break;
+ }
+ } else {
+ // Indoor drawing
+ // Default all the parts of draw struct not to be drawn by default
+ for (int idx = 3; idx < _indoorList.size(); ++idx)
+ _indoorList[idx]._frame = -1;
+
+ if (combat._monstersAttacking) {
+ for (int idx = 0; idx < 96; ++idx) {
+ if (_indoorList[79 + idx]._sprites != nullptr) {
+ _indoorList[79 + idx]._frame = 0;
+ }
+ else if (_indoorList[111 + idx]._sprites != nullptr) {
+ _indoorList[111 + idx]._frame = 1;
+ }
+ else if (_indoorList[135 + idx]._sprites != nullptr) {
+ _indoorList[135 + idx]._frame = 2;
+ }
+ else if (_indoorList[162 + idx]._sprites != nullptr) {
+ _indoorList[162 + idx]._frame = 0;
+ }
+ }
+ } else if (_charsShooting) {
+ for (int idx = 0; idx < 8; ++idx) {
+ if (_indoorList._attackImgs1[idx]._sprites != nullptr) {
+ _indoorList._attackImgs1[idx]._frame = 0;
+ } else if (_indoorList._attackImgs2[idx]._sprites != nullptr) {
+ _indoorList._attackImgs2[idx]._frame = 1;
+ } else if (_indoorList._attackImgs3[idx]._sprites != nullptr) {
+ _indoorList._attackImgs3[idx]._frame = 2;
+ } else if (_indoorList._attackImgs4[idx]._sprites != nullptr) {
+ _indoorList._attackImgs4[idx]._frame = 0;
+ }
+ }
+ }
+
+ setMazeBits();
+ _isAnimReset = false;
+
+ // Code in the original that's not being used
+ //MazeObject &objObject = map._mobData._objects[_objNumber - 1];
+
+ for (int idx = 0; idx < 3; ++idx) {
+ DrawStruct &ds1 = _indoorList[INDOOR_INDEXES[idx]];
+ DrawStruct &ds2 = _indoorList[INDOOR_INDEXES[idx] + 1];
+ ds1._sprites = nullptr;
+ ds2._sprites = nullptr;
+
+ if (combat._charsArray1[idx]) {
+ int posIndex = combat._attackMonsters[1] && !combat._attackMonsters[2] ? 1 : 0;
+ --combat._charsArray1[idx];
+
+ if (combat._monPow[idx]) {
+ ds1._x = COMBAT_POS_X[idx][posIndex];
+ ds1._frame = 0;
+ ds1._scale = combat._monsterScale[idx];
+ if (ds1._scale == 0x8000) {
+ ds1._x /= 3;
+ ds1._y = 60;
+ } else {
+ ds1._y = 73;
+ }
+
+ ds1._flags = SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED;
+ ds1._sprites = &_charPowSprites;
+ }
+
+ if (combat._elemPow[idx]) {
+ ds2._x = COMBAT_POS_X[idx][posIndex] + COMBAT_OFFSET_X[idx];
+ ds2._frame = combat._elemPow[idx];
+ ds2._scale = combat._elemScale[idx];
+ if (ds2._scale == 0x8000)
+ ds2._x /= 3;
+ ds2._flags = SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED;
+ ds2._sprites = &_charPowSprites;
+ }
+ }
+ }
+
+ setIndoorsMonsters();
+ setIndoorsObjects();
+ setIndoorsWallPics();
+
+ _indoorList[161]._sprites = nullptr;
+ _indoorList[160]._sprites = nullptr;
+ _indoorList[159]._sprites = nullptr;
+
+ // Handle attacking monsters
+ int monsterIndex = 0;
+ if (combat._attackMonsters[0] != -1 && map._mobData._monsters[combat._attackMonsters[0]]._frame >= 8) {
+ _indoorList[159] = _indoorList[156];
+ _indoorList[160] = _indoorList[157];
+ _indoorList[161] = _indoorList[158];
+ _indoorList[158]._sprites = nullptr;
+ _indoorList[156]._sprites = nullptr;
+ _indoorList[157]._sprites = nullptr;
+ monsterIndex = 1;
+ } else if (combat._attackMonsters[1] != -1 && map._mobData._monsters[combat._attackMonsters[1]]._frame >= 8) {
+ _indoorList[159] = _indoorList[150];
+ _indoorList[160] = _indoorList[151];
+ _indoorList[161] = _indoorList[152];
+ _indoorList[152]._sprites = nullptr;
+ _indoorList[151]._sprites = nullptr;
+ _indoorList[150]._sprites = nullptr;
+ monsterIndex = 2;
+ } else if (combat._attackMonsters[2] != -1 && map._mobData._monsters[combat._attackMonsters[2]]._frame >= 8) {
+ _indoorList[159] = _indoorList[153];
+ _indoorList[160] = _indoorList[154];
+ _indoorList[161] = _indoorList[155];
+ _indoorList[153]._sprites = nullptr;
+ _indoorList[154]._sprites = nullptr;
+ _indoorList[155]._sprites = nullptr;
+ monsterIndex = 3;
+ }
+
+ drawIndoors();
+
+ switch (monsterIndex) {
+ case 1:
+ _indoorList[156] = _indoorList[159];
+ _indoorList[157] = _indoorList[160];
+ _indoorList[158] = _indoorList[161];
+ break;
+ case 2:
+ _indoorList[150] = _indoorList[159];
+ _indoorList[151] = _indoorList[160];
+ _indoorList[152] = _indoorList[161];
+ break;
+ case 3:
+ _indoorList[153] = _indoorList[159];
+ _indoorList[154] = _indoorList[160];
+ _indoorList[155] = _indoorList[161];
+ break;
+ default:
+ break;
+ }
+ }
+
+ animate3d();
+}
+
+void InterfaceMap::animate3d() {
+ Combat &combat = *_vm->_combat;
+ Map &map = *_vm->_map;
+ _overallFrame = (_overallFrame + 1) % 5;
+ _combatFloatCounter = (_combatFloatCounter + 1) % 8;
+
+ for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
+ MazeMonster &monster = map._mobData._monsters[idx];
+ if (!monster._damageType) {
+ if (monster._frame < 8) {
+ MonsterStruct &monsterData = *monster._monsterData;
+ if (!monsterData._loopAnimation) {
+ // Monster isn't specially looped, so cycle through the 8 frames
+ monster._frame = (monster._frame + 1) % 8;
+ } else if (!monster._field9) {
+ monster._frame = (monster._frame + 1) % 8;
+ if (monster._frame == 0) {
+ monster._field9 ^= 1;
+ monster._frame = 6;
+ }
+ } else {
+ if (monster._frame)
+ --monster._frame;
+ if (monster._frame == 0)
+ monster._field9 = 0;
+ }
+ } else if (monster._frame == 11) {
+ --monster._fieldA;
+ if (monster._fieldA == 0)
+ monster._frame = 0;
+ } else {
+ ++monster._frame;
+ if (monster._frame == 11) {
+ --monster._frame;
+ monster._frame = monster._fieldA ? 10 : 0;
+ }
+ }
+ }
+
+ // Block 2
+ if (monster._effect2) {
+ if (monster._effect1) {
+ if (monster._effect1 & 0x80) {
+ if (monster._effect3)
+ --monster._effect3;
+ if (monster._effect3 == 0)
+ monster._effect1 ^= 0x80;
+ } else {
+ monster._effect3 = (monster._effect3 + 1) % 3;
+ if (monster._effect3 == 0) {
+ monster._effect1 ^= 0x80;
+ monster._effect3 = 2;
+ }
+ }
+ }
+ } else {
+ monster._effect3 = (monster._effect3 + 1) % 8;
+ if (monster._effect3 == 0) {
+ MonsterStruct &monsterData = *monster._monsterData;
+ monster._effect1 = monster._effect2 = monsterData._animationEffect;
+ }
+ }
+ }
+
+ DrawStruct *combatImgs1 = map._isOutdoors ? _outdoorList._attackImgs1 : _indoorList._attackImgs1;
+ DrawStruct *combatImgs2 = map._isOutdoors ? _outdoorList._attackImgs2 : _indoorList._attackImgs2;
+ DrawStruct *combatImgs3 = map._isOutdoors ? _outdoorList._attackImgs3 : _indoorList._attackImgs3;
+ DrawStruct *combatImgs4 = map._isOutdoors ? _outdoorList._attackImgs4 : _indoorList._attackImgs4;
+
+ if (combat._monstersAttacking) {
+ for (int idx = 0; idx < 8; ++idx) {
+ if (combatImgs1[idx]._sprites) {
+ combatImgs1[idx]._sprites = nullptr;
+ combat._shooting[idx] = false;
+ } else if (combatImgs2[idx]._sprites) {
+ combatImgs1[idx]._sprites = combatImgs2[idx]._sprites;
+ combatImgs2[idx]._sprites = nullptr;
+ } else if (combatImgs3[idx]._sprites) {
+ combatImgs2[idx]._sprites = combatImgs3[idx]._sprites;
+ combatImgs3[idx]._sprites = nullptr;
+ } else if (combatImgs4[idx]._sprites) {
+ combatImgs3[idx]._sprites = combatImgs4[idx]._sprites;
+ combatImgs4[idx]._sprites = nullptr;
+ }
+ }
+ } else if (_charsShooting) {
+ for (int idx = 0; idx < 8; ++idx) {
+ if (combatImgs4[idx]._sprites) {
+ combatImgs4[idx]._sprites = nullptr;
+ } else if (combatImgs3[idx]._sprites) {
+ combatImgs4[idx]._sprites = combatImgs3[idx]._sprites;
+ combatImgs3[idx]._sprites = nullptr;
+ } else if (combatImgs2[idx]._sprites) {
+ combatImgs3[idx]._sprites = combatImgs2[idx]._sprites;
+ combatImgs2[idx]._sprites = nullptr;
+ } else if (combatImgs1[idx]._sprites) {
+ combatImgs2[idx]._sprites = combatImgs1[idx]._sprites;
+ combatImgs1[idx]._sprites = nullptr;
+ }
+ }
+ }
+
+ for (uint idx = 0; idx < map._mobData._wallItems.size(); ++idx) {
+ MazeWallItem &wallItem = map._mobData._wallItems[idx];
+ wallItem._frame = (wallItem._frame + 1) % wallItem._sprites->size();
+ }
+}
+
+void InterfaceMap::setMazeBits() {
+ Common::fill(&_wo[0], &_wo[308], 0);
+
+ switch (_vm->_map->getCell(0) - 1) {
+ case 0:
+ ++_wo[125];
+ break;
+ case 1:
+ ++_wo[69];
+ break;
+ case 2:
+ ++_wo[185];
+ break;
+ case 3:
+ case 12:
+ ++_wo[105];
+ break;
+ case 4:
+ case 7:
+ ++_wo[25];
+ break;
+ case 5:
+ ++_wo[225];
+ break;
+ case 6:
+ ++_wo[205];
+ break;
+ case 8:
+ ++_wo[145];
+ break;
+ case 9:
+ ++_wo[305];
+ break;
+ case 10:
+ ++_wo[245];
+ break;
+ case 11:
+ ++_wo[165];
+ break;
+ case 13:
+ ++_wo[265];
+ break;
+ case 14:
+ ++_wo[285];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(1) - 1) {
+ case 1:
+ ++_wo[72];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[28];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(2) - 1) {
+ case 0:
+ ++_wo[127];
+ break;
+ case 1:
+ ++_wo[71];
+ break;
+ case 2:
+ ++_wo[187];
+ break;
+ case 3:
+ case 12:
+ ++_wo[107];
+ break;
+ case 4:
+ case 7:
+ ++_wo[27];
+ break;
+ case 5:
+ ++_wo[227];
+ break;
+ case 6:
+ ++_wo[207];
+ break;
+ case 8:
+ ++_wo[147];
+ break;
+ case 9:
+ ++_wo[307];
+ break;
+ case 10:
+ ++_wo[247];
+ break;
+ case 11:
+ ++_wo[167];
+ break;
+ case 13:
+ ++_wo[267];
+ break;
+ case 14:
+ ++_wo[287];
+ break;
+ default:
+ break;
+ }
+
+ _vm->_party->handleLight();
+
+ switch (_vm->_map->getCell(3) - 1) {
+ case 1:
+ ++_wo[73];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[29];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(4) - 1) {
+ case 0:
+ ++_wo[126];
+ break;
+ case 1:
+ ++_wo[70];
+ break;
+ case 2:
+ ++_wo[186];
+ break;
+ case 3:
+ case 12:
+ ++_wo[106];
+ break;
+ case 4:
+ case 7:
+ ++_wo[26];
+ break;
+ case 5:
+ ++_wo[226];
+ break;
+ case 6:
+ ++_wo[206];
+ case 8:
+ ++_wo[146];
+ break;
+ case 9:
+ ++_wo[306];
+ break;
+ case 10:
+ ++_wo[246];
+ break;
+ break;
+ case 11:
+ ++_wo[166];
+ break;
+ case 13:
+ ++_wo[266];
+ break;
+ case 14:
+ ++_wo[286];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(5) - 1) {
+ case 0:
+ ++_wo[122];
+ break;
+ case 1:
+ ++_wo[64];
+ break;
+ case 2:
+ ++_wo[182];
+ break;
+ case 3:
+ case 12:
+ ++_wo[102];
+ break;
+ case 4:
+ case 7:
+ ++_wo[20];
+ break;
+ case 5:
+ ++_wo[222];
+ break;
+ case 6:
+ ++_wo[202];
+ break;
+ case 8:
+ ++_wo[142];
+ break;
+ case 9:
+ ++_wo[302];
+ break;
+ case 10:
+ ++_wo[242];
+ break;
+ case 11:
+ ++_wo[162];
+ break;
+ case 13:
+ ++_wo[262];
+ break;
+ case 14:
+ ++_wo[282];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(6) - 1) {
+ case 1:
+ ++_wo[67];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[23];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(7) - 1) {
+ case 0:
+ ++_wo[124];
+ break;
+ case 1:
+ ++_wo[66];
+ break;
+ case 2:
+ ++_wo[184];
+ break;
+ case 3:
+ case 12:
+ ++_wo[104];
+ break;
+ case 4:
+ case 7:
+ ++_wo[22];
+ break;
+ case 5:
+ ++_wo[224];
+ break;
+ case 6:
+ ++_wo[204];
+ break;
+ case 8:
+ ++_wo[144];
+ break;
+ case 9:
+ ++_wo[304];
+ break;
+ case 10:
+ ++_wo[244];
+ break;
+ case 11:
+ ++_wo[164];
+ break;
+ case 13:
+ ++_wo[264];
+ break;
+ case 14:
+ ++_wo[284];
+ break;
+ default:
+ break;
+ }
+
+ _thinWall = (_vm->_map->_currentWall != INVALID_CELL) && _wo[27];
+
+ switch (_vm->_map->getCell(8) - 1) {
+ case 1:
+ ++_wo[68];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[24];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(9) - 1) {
+ case 0:
+ ++_wo[123];
+ break;
+ case 1:
+ ++_wo[65];
+ break;
+ case 2:
+ ++_wo[183];
+ break;
+ case 3:
+ case 12:
+ ++_wo[103];
+ break;
+ case 4:
+ case 7:
+ ++_wo[21];
+ break;
+ case 5:
+ ++_wo[223];
+ break;
+ case 6:
+ ++_wo[203];
+ break;
+ case 8:
+ ++_wo[143];
+ break;
+ case 9:
+ ++_wo[303];
+ break;
+ case 10:
+ ++_wo[243];
+ break;
+ case 11:
+ ++_wo[163];
+ break;
+ case 13:
+ ++_wo[263];
+ break;
+ case 14:
+ ++_wo[283];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(10) - 1) {
+ case 0:
+ ++_wo[117];
+ break;
+ case 1:
+ ++_wo[55];
+ break;
+ case 2:
+ ++_wo[177];
+ break;
+ case 3:
+ case 12:
+ ++_wo[97];
+ break;
+ case 4:
+ case 7:
+ ++_wo[11];
+ break;
+ case 5:
+ ++_wo[217];
+ break;
+ case 6:
+ ++_wo[197];
+ break;
+ case 8:
+ ++_wo[137];
+ break;
+ case 9:
+ ++_wo[297];
+ break;
+ case 10:
+ ++_wo[237];
+ case 11:
+ ++_wo[157];
+ break;
+ case 13:
+ ++_wo[257];
+ break;
+ case 14:
+ ++_wo[277];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(11) - 1) {
+ case 1:
+ ++_wo[60];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[16];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(12) - 1) {
+ case 0:
+ ++_wo[118];
+ break;
+ case 1:
+ ++_wo[56];
+ break;
+ case 2:
+ ++_wo[178];
+ break;
+ case 3:
+ case 12:
+ ++_wo[98];
+ break;
+ case 4:
+ case 7:
+ ++_wo[12];
+ break;
+ case 5:
+ ++_wo[218];
+ break;
+ case 6:
+ ++_wo[198];
+ break;
+ case 8:
+ ++_wo[138];
+ break;
+ case 9:
+ ++_wo[298];
+ break;
+ case 10:
+ ++_wo[238];
+ break;
+ case 11:
+ ++_wo[158];
+ break;
+ case 13:
+ ++_wo[258];
+ break;
+ case 14:
+ ++_wo[278];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(13) - 1) {
+ case 1:
+ ++_wo[61];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[17];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(14) - 1) {
+ case 0:
+ ++_wo[121];
+ break;
+ case 1:
+ ++_wo[59];
+ break;
+ case 2:
+ ++_wo[181];
+ break;
+ case 3:
+ case 12:
+ ++_wo[101];
+ break;
+ case 4:
+ case 7:
+ ++_wo[15];
+ break;
+ case 5:
+ ++_wo[221];
+ break;
+ case 6:
+ ++_wo[201];
+ break;
+ case 8:
+ ++_wo[141];
+ break;
+ case 9:
+ ++_wo[301];
+ break;
+ case 10:
+ ++_wo[241];
+ break;
+ case 11:
+ ++_wo[161];
+ break;
+ case 13:
+ ++_wo[261];
+ break;
+ case 14:
+ ++_wo[281];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(15) - 1) {
+ case 1:
+ ++_wo[63];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[19];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(16) - 1) {
+ case 0:
+ ++_wo[120];
+ break;
+ case 1:
+ ++_wo[58];
+ break;
+ case 2:
+ ++_wo[180];
+ break;
+ case 3:
+ case 12:
+ ++_wo[100];
+ break;
+ case 4:
+ case 7:
+ ++_wo[14];
+ break;
+ case 5:
+ ++_wo[220];
+ break;
+ case 6:
+ ++_wo[200];
+ break;
+ case 8:
+ ++_wo[140];
+ break;
+ case 9:
+ ++_wo[300];
+ break;
+ case 10:
+ ++_wo[240];
+ break;
+ case 11:
+ ++_wo[160];
+ break;
+ case 13:
+ ++_wo[260];
+ break;
+ case 14:
+ ++_wo[280];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(17) - 1) {
+ case 1:
+ ++_wo[62];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[18];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(18) - 1) {
+ case 0:
+ ++_wo[119];
+ break;
+ case 1:
+ ++_wo[57];
+ break;
+ case 2:
+ ++_wo[179];
+ break;
+ case 3:
+ case 12:
+ ++_wo[99];
+ break;
+ case 4:
+ case 7:
+ ++_wo[13];
+ break;
+ case 5:
+ ++_wo[219];
+ break;
+ case 6:
+ ++_wo[199];
+ break;
+ case 8:
+ ++_wo[139];
+ break;
+ case 9:
+ ++_wo[299];
+ break;
+ case 10:
+ ++_wo[239];
+ break;
+ case 11:
+ ++_wo[159];
+ break;
+ case 13:
+ ++_wo[259];
+ break;
+ case 14:
+ ++_wo[279];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(19) - 1) {
+ case 0:
+ ++_wo[108];
+ break;
+ case 1:
+ ++_wo[78];
+ break;
+ case 2:
+ ++_wo[168];
+ case 3:
+ case 12:
+ ++_wo[88];
+ break;
+ case 4:
+ case 7:
+ ++_wo[34];
+ break;
+ case 5:
+ ++_wo[208];
+ break;
+ case 6:
+ ++_wo[188];
+ break;
+ case 8:
+ ++_wo[128];
+ break;
+ case 9:
+ ++_wo[288];
+ break;
+ case 10:
+ ++_wo[228];
+ break;
+ case 11:
+ ++_wo[148];
+ break;
+ case 13:
+ ++_wo[248];
+ break;
+ case 14:
+ ++_wo[268];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(20) - 1) {
+ case 1:
+ ++_wo[76];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[32];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(21) - 1) {
+ case 0:
+ ++_wo[109];
+ break;
+ case 1:
+ ++_wo[44];
+ break;
+ case 2:
+ ++_wo[169];
+ break;
+ case 3:
+ case 12:
+ ++_wo[89];
+ break;
+ case 4:
+ case 7:
+ ++_wo[0];
+ break;
+ case 5:
+ ++_wo[209];
+ break;
+ case 6:
+ ++_wo[189];
+ break;
+ case 8:
+ ++_wo[129];
+ break;
+ case 9:
+ ++_wo[289];
+ break;
+ case 10:
+ ++_wo[229];
+ break;
+ case 11:
+ ++_wo[149];
+ break;
+ case 13:
+ ++_wo[249];
+ break;
+ case 14:
+ ++_wo[269];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(22) - 1) {
+ case 1:
+ ++_wo[74];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[30];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(23) - 1) {
+ case 0:
+ ++_wo[110];
+ break;
+ case 1:
+ ++_wo[45];
+ break;
+ case 2:
+ ++_wo[170];
+ break;
+ case 3:
+ case 12:
+ ++_wo[90];
+ break;
+ case 4:
+ case 7:
+ ++_wo[1];
+ break;
+ case 5:
+ ++_wo[210];
+ break;
+ case 6:
+ ++_wo[190];
+ break;
+ case 8:
+ ++_wo[130];
+ break;
+ case 9:
+ ++_wo[290];
+ break;
+ case 10:
+ ++_wo[230];
+ break;
+ case 11:
+ ++_wo[150];
+ break;
+ case 13:
+ ++_wo[250];
+ break;
+ case 14:
+ ++_wo[270];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(24) - 1) {
+ case 1:
+ ++_wo[52];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[8];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(25) - 1) {
+ case 0:
+ ++_wo[111];
+ break;
+ case 1:
+ ++_wo[46];
+ break;
+ case 2:
+ ++_wo[171];
+ break;
+ case 3:
+ case 12:
+ ++_wo[91];
+ break;
+ case 4:
+ case 7:
+ ++_wo[2];
+ break;
+ case 5:
+ ++_wo[211];
+ break;
+ case 6:
+ ++_wo[191];
+ break;
+ case 8:
+ ++_wo[131];
+ break;
+ case 9:
+ ++_wo[291];
+ break;
+ case 10:
+ ++_wo[231];
+ break;
+ case 11:
+ ++_wo[151];
+ break;
+ case 13:
+ ++_wo[251];
+ break;
+ case 14:
+ ++_wo[271];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(26) - 1) {
+ case 1:
+ ++_wo[51];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[7];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(27) - 1) {
+ case 0:
+ ++_wo[116];
+ break;
+ case 1:
+ ++_wo[50];
+ break;
+ case 2:
+ ++_wo[176];
+ break;
+ case 3:
+ case 12:
+ ++_wo[96];
+ break;
+ case 4:
+ case 7:
+ ++_wo[6];
+ break;
+ case 5:
+ ++_wo[216];
+ break;
+ case 6:
+ ++_wo[196];
+ break;
+ case 8:
+ ++_wo[136];
+ break;
+ case 9:
+ ++_wo[296];
+ break;
+ case 10:
+ ++_wo[236];
+ break;
+ case 11:
+ ++_wo[156];
+ break;
+ case 13:
+ ++_wo[256];
+ break;
+ case 14:
+ ++_wo[276];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(28) - 1) {
+ case 1:
+ ++_wo[53];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[9];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(29) - 1) {
+ case 0:
+ ++_wo[115];
+ break;
+ case 1:
+ ++_wo[49];
+ break;
+ case 2:
+ ++_wo[175];
+ break;
+ case 3:
+ case 12:
+ ++_wo[95];
+ break;
+ case 4:
+ case 7:
+ ++_wo[5];
+ break;
+ case 5:
+ ++_wo[215];
+ break;
+ case 6:
+ ++_wo[195];
+ break;
+ case 8:
+ ++_wo[135];
+ break;
+ case 9:
+ ++_wo[295];
+ break;
+ case 10:
+ ++_wo[235];
+ break;
+ case 11:
+ ++_wo[155];
+ break;
+ case 13:
+ ++_wo[255];
+ break;
+ case 14:
+ ++_wo[275];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(30) - 1) {
+ case 1:
+ ++_wo[54];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[10];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(31) - 1) {
+ case 0:
+ ++_wo[114];
+ break;
+ case 1:
+ ++_wo[48];
+ break;
+ case 2:
+ ++_wo[174];
+ break;
+ case 3:
+ case 12:
+ ++_wo[94];
+ break;
+ case 4:
+ case 7:
+ ++_wo[4];
+ break;
+ case 5:
+ ++_wo[214];
+ break;
+ case 6:
+ ++_wo[194];
+ break;
+ case 8:
+ ++_wo[134];
+ break;
+ case 9:
+ ++_wo[294];
+ break;
+ case 10:
+ ++_wo[234];
+ break;
+ case 11:
+ ++_wo[154];
+ break;
+ case 13:
+ ++_wo[254];
+ break;
+ case 14:
+ ++_wo[274];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(32) - 1) {
+ case 1:
+ ++_wo[75];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[31];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(33) - 1) {
+ case 0:
+ ++_wo[112];
+ break;
+ case 1:
+ ++_wo[47];
+ break;
+ case 2:
+ ++_wo[172];
+ break;
+ case 3:
+ case 12:
+ ++_wo[92];
+ break;
+ case 4:
+ case 7:
+ ++_wo[3];
+ break;
+ case 5:
+ ++_wo[212];
+ break;
+ case 6:
+ ++_wo[192];
+ break;
+ case 8:
+ ++_wo[132];
+ break;
+ case 9:
+ ++_wo[292];
+ break;
+ case 10:
+ ++_wo[232];
+ break;
+ case 11:
+ ++_wo[152];
+ break;
+ case 13:
+ ++_wo[252];
+ break;
+ case 14:
+ ++_wo[272];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(34) - 1) {
+ case 1:
+ ++_wo[77];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[33];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(35) - 1) {
+ case 0:
+ ++_wo[113];
+ break;
+ case 1:
+ ++_wo[79];
+ break;
+ case 2:
+ ++_wo[173];
+ break;
+ case 3:
+ case 12:
+ ++_wo[93];
+ break;
+ case 4:
+ case 7:
+ ++_wo[35];
+ break;
+ case 5:
+ ++_wo[213];
+ break;
+ case 6:
+ ++_wo[193];
+ break;
+ case 8:
+ ++_wo[133];
+ break;
+ case 9:
+ ++_wo[293];
+ break;
+ case 10:
+ ++_wo[233];
+ break;
+ case 11:
+ ++_wo[153];
+ break;
+ case 13:
+ ++_wo[253];
+ break;
+ case 14:
+ ++_wo[273];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(36) - 1) {
+ case 1:
+ ++_wo[83];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[39];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(37) - 1) {
+ case 1:
+ ++_wo[82];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[38];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(38) - 1) {
+ case 1:
+ ++_wo[81];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[37];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(39) - 1) {
+ case 1:
+ ++_wo[80];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[36];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(40) - 1) {
+ case 1:
+ ++_wo[84];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[40];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(41) - 1) {
+ case 1:
+ ++_wo[85];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[41];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(42) - 1) {
+ case 1:
+ ++_wo[86];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[42];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(43) - 1) {
+ case 1:
+ ++_wo[87];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[43];
+ break;
+ default:
+ break;
+ }
+}
+
+void InterfaceMap::setIndoorsMonsters() {
+ Combat &combat = *_vm->_combat;
+ Map &map = *_vm->_map;
+ Common::Point mazePos = _vm->_party->_mazePosition;
+ Direction dir = _vm->_party->_mazeDirection;
+
+ combat.clear();
+ for (uint monsterIdx = 0; monsterIdx < map._mobData._monsters.size(); ++monsterIdx) {
+ MazeMonster &monster = map._mobData._monsters[monsterIdx];
+ SpriteResource *sprites = monster._sprites;
+ int frame = monster._frame;
+
+ if (frame >= 8) {
+ sprites = monster._attackSprites;
+ frame -= 8;
+ }
+
+ // The following long sequence sets up monsters in the various positions
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][2]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][2])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[0] == -1) {
+ combat._attackMonsters[0] = monsterIdx;
+ setMonsterSprite(_indoorList[156], monster, sprites, frame, INDOOR_MONSTERS_Y[0]);
+ } else if (combat._attackMonsters[1] == -1) {
+ combat._attackMonsters[1] = monsterIdx;
+ setMonsterSprite(_indoorList[150], monster, sprites, frame, INDOOR_MONSTERS_Y[0]);
+ } else if (combat._attackMonsters[2] == -1) {
+ combat._attackMonsters[2] = monsterIdx;
+ setMonsterSprite(_indoorList[153], monster, sprites, frame, INDOOR_MONSTERS_Y[0]);
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][7]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][7])) {
+ monster._isAttacking = true;
+ if (!_wo[27]) {
+ if (combat._attackMonsters[3] == -1) {
+ combat._attackMonsters[3] = monsterIdx;
+ setMonsterSprite(_indoorList[132], monster, sprites, frame, INDOOR_MONSTERS_Y[1]);
+ } else if (combat._attackMonsters[4] == -1) {
+ combat._attackMonsters[4] = monsterIdx;
+ setMonsterSprite(_indoorList[130], monster, sprites, frame, INDOOR_MONSTERS_Y[1]);
+ } else if (combat._attackMonsters[2] == -1) {
+ combat._attackMonsters[5] = monsterIdx;
+ setMonsterSprite(_indoorList[131], monster, sprites, frame, INDOOR_MONSTERS_Y[1]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][5]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][5])) {
+ if (_wo[27] && _wo[25]) {
+ } else if (_wo[27] && _wo[28]) {
+ } else if (_wo[23] & _wo[25]) {
+ } else if (_wo[23] && _wo[28]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[12] == -1) {
+ combat._attackMonsters[12] = monsterIdx;
+ setMonsterSprite(_indoorList[128], monster, sprites, frame, INDOOR_MONSTERS_Y[1]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][9]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][9])) {
+ if (_wo[27] && _wo[26]) {
+ } else if (_wo[27] && _wo[29]) {
+ } else if (_wo[24] & _wo[26]) {
+ } else if (_wo[24] && _wo[29]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[13] == -1) {
+ combat._attackMonsters[13] = monsterIdx;
+ setMonsterSprite(_indoorList[129], monster, sprites, frame, INDOOR_MONSTERS_Y[1]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][14]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][14])) {
+ monster._isAttacking = true;
+
+ if (!_wo[22] && !_wo[27]) {
+ if (combat._attackMonsters[6] == -1) {
+ combat._attackMonsters[6] = monsterIdx;
+ setMonsterSprite(_indoorList[106], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ } else if (combat._attackMonsters[7] == -1) {
+ combat._attackMonsters[7] = monsterIdx;
+ setMonsterSprite(_indoorList[104], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ } else if (combat._attackMonsters[8] == -1) {
+ combat._attackMonsters[8] = monsterIdx;
+ setMonsterSprite(_indoorList[105], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][12]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][12])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[22] & _wo[20]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[14] == -1) {
+ combat._attackMonsters[14] = monsterIdx;
+ setMonsterSprite(_indoorList[100], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ } else if (combat._attackMonsters[20] == -1) {
+ combat._attackMonsters[20] = monsterIdx;
+ setMonsterSprite(_indoorList[101], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][16]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][16])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[22] & _wo[21]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[15] == -1) {
+ combat._attackMonsters[15] = monsterIdx;
+ setMonsterSprite(_indoorList[102], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ } else if (combat._attackMonsters[21] == -1) {
+ combat._attackMonsters[21] = monsterIdx;
+ setMonsterSprite(_indoorList[103], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][27]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][27])) {
+ if (!_wo[27] && !_wo[22] && !_wo[15]) {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[9] == -1) {
+ combat._attackMonsters[9] = monsterIdx;
+ setMonsterSprite(_indoorList[70], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[10] == -1) {
+ combat._attackMonsters[10] = monsterIdx;
+ setMonsterSprite(_indoorList[68], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[11] == -1) {
+ combat._attackMonsters[11] = monsterIdx;
+ setMonsterSprite(_indoorList[69], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][25]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][25])) {
+ if (_wo[27] || _wo[22]) {
+ } else if (_wo[15] && _wo[17]) {
+ } else if (_wo[15] && _wo[12]) {
+ } else if (_wo[12] && _wo[7]) {
+ } else if (_wo[17] && _wo[7]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[16] == -1) {
+ combat._attackMonsters[16] = monsterIdx;
+ setMonsterSprite(_indoorList[62], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[22] == -1) {
+ combat._attackMonsters[22] = monsterIdx;
+ setMonsterSprite(_indoorList[60], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[24] == -1) {
+ combat._attackMonsters[24] = monsterIdx;
+ setMonsterSprite(_indoorList[61], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][23]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][23])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (_wo[12] || _wo[8]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[18] == -1) {
+ combat._attackMonsters[18] = monsterIdx;
+ setMonsterSprite(_indoorList[66], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][29]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][29])) {
+ if (_wo[27] || _wo[22]) {
+ } else if (_wo[15] && _wo[19]) {
+ } else if (_wo[15] && _wo[14]) {
+ } else if (_wo[14] && _wo[9]) {
+ } else if (_wo[19] && _wo[9]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[17] == -1) {
+ combat._attackMonsters[17] = monsterIdx;
+ setMonsterSprite(_indoorList[65], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[23] == -1) {
+ combat._attackMonsters[23] = monsterIdx;
+ setMonsterSprite(_indoorList[63], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[25] == -1) {
+ combat._attackMonsters[25] = monsterIdx;
+ setMonsterSprite(_indoorList[64], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][31]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][31])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (_wo[14] || _wo[10]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[19] == -1) {
+ combat._attackMonsters[19] = monsterIdx;
+ setMonsterSprite(_indoorList[67], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[23] == -1) {
+ combat._attackMonsters[23] = monsterIdx;
+ setMonsterSprite(_indoorList[63], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[25] == -1) {
+ combat._attackMonsters[25] = monsterIdx;
+ setMonsterSprite(_indoorList[64], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ }
+ }
+ }
+ }
+
+ _indoorList[153]._x += 58;
+ _indoorList[131]._x += 25;
+ _indoorList[105]._x += 9;
+ _indoorList[69]._x--;
+ _indoorList[61]._x -= 26;
+ _indoorList[64]._x += 23;
+ _indoorList[66]._x -= 58;
+ _indoorList[67]._x += 40;
+ _indoorList[100]._x -= 65;
+ _indoorList[101]._x -= 85;
+ _indoorList[102]._x += 49;
+ _indoorList[103]._x += 65;
+ _indoorList[128]._x -= 112;
+ _indoorList[129]._x += 98;
+
+ if (combat._attackMonsters[1] != -1 && combat._attackMonsters[2] == -1) {
+ _indoorList[156]._x += 31;
+ _indoorList[150]._x -= 36;
+ } else {
+ _indoorList[156]._x -= 5;
+ _indoorList[150]._x -= 67;
+ }
+
+ if (combat._attackMonsters[4] != -1 && combat._attackMonsters[5] == -1) {
+ _indoorList[132]._x += 8;
+ _indoorList[130]._x -= 23;
+ } else {
+ _indoorList[132]._x -= 7;
+ _indoorList[130]._x -= 38;
+ }
+
+ if (combat._attackMonsters[7] != -1 && combat._attackMonsters[8] == -1) {
+ _indoorList[104]._x -= 16;
+ } else {
+ _indoorList[106]._x -= 8;
+ _indoorList[104]._x -= 24;
+ }
+
+ if (combat._attackMonsters[10] != -1 && combat._attackMonsters[11] == -1) {
+ _indoorList[70]._x -= 5;
+ _indoorList[68]._x -= 13;
+ } else {
+ _indoorList[70]._x -= 9;
+ _indoorList[68]._x -= 17;
+ }
+
+ if (combat._attackMonsters[22] == -1 && combat._attackMonsters[24] == -1) {
+ _indoorList[62]._x -= 27;
+ _indoorList[60]._x -= 37;
+ } else {
+ _indoorList[62]._x -= 34;
+ _indoorList[60]._x -= 41;
+ }
+
+ if (combat._attackMonsters[23] != -1 && combat._attackMonsters[25] == -1) {
+ _indoorList[65]._x += 20;
+ _indoorList[63]._x -= 12;
+ } else {
+ _indoorList[65]._x += 16;
+ _indoorList[63]._x -= 16;
+ }
+}
+
+void InterfaceMap::setMonsterSprite(DrawStruct &drawStruct, MazeMonster &monster, SpriteResource *sprites,
+ int frame, int defaultY) {
+ MonsterStruct &monsterData = *monster._monsterData;
+ bool flying = monsterData._flying;
+
+ drawStruct._frame = frame;
+ drawStruct._sprites = sprites;
+ drawStruct._y = defaultY;
+
+ if (flying) {
+ drawStruct._x = COMBAT_FLOAT_X[_combatFloatCounter];
+ drawStruct._y = COMBAT_FLOAT_Y[_combatFloatCounter];
+ } else {
+ drawStruct._x = 0;
+ }
+
+ drawStruct._flags &= ~0xFFF;
+ if (monster._effect2)
+ drawStruct._flags = MONSTER_EFFECT_FLAGS[monster._effect2][monster._effect3];
+}
+
+void InterfaceMap::setIndoorsObjects() {
+ Common::Point mazePos = _vm->_party->_mazePosition;
+ Direction dir = _vm->_party->_mazeDirection;
+ Common::Point pt;
+ _objNumber = 0;
+
+ Common::Array<MazeObject> &objects = _vm->_map->_mobData._objects;
+ for (uint idx = 0; idx < objects.size(); ++idx) {
+ MazeObject &mazeObject = objects[idx];
+
+ // Determine which half of the X/Y lists to use
+ int listOffset;
+ if (_vm->_files->_isDarkCc) {
+ listOffset = mazeObject._spriteId == 47 ? 1 : 0;
+ } else {
+ listOffset = mazeObject._spriteId == 113 ? 1 : 0;
+ }
+
+ // Position 1
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][2]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][2]) == mazeObject._position.y
+ && _indoorList._objects0._frame == -1) {
+ _indoorList._objects0._x = INDOOR_OBJECT_X[listOffset][0];
+ _indoorList._objects0._y = MAP_OBJECT_Y[listOffset][0];
+ _indoorList._objects0._frame = mazeObject._frame;
+ _indoorList._objects0._sprites = mazeObject._sprites;
+ _indoorList._objects0._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects0._flags |= SPRFLAG_HORIZ_FLIPPED;
+ _objNumber = idx;
+ }
+
+ // Position 2
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][7]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][7]) == mazeObject._position.y
+ && !_wo[27] && _indoorList._objects1._frame == -1) {
+ _indoorList._objects1._x = INDOOR_OBJECT_X[listOffset][1];
+ _indoorList._objects1._y = MAP_OBJECT_Y[listOffset][1];
+ _indoorList._objects1._frame = mazeObject._frame;
+ _indoorList._objects1._sprites = mazeObject._sprites;
+ _indoorList._objects1._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects1._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ // Position 3
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][5]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][5]) == mazeObject._position.y) {
+ if (_wo[27] && _wo[25]) {
+ } else if (_wo[27] && _wo[28]) {
+ } else if (_wo[23] && _wo[25]) {
+ } else if (_wo[23] && _wo[28]) {
+ } else if (_indoorList._objects2._frame == -1) {
+ _indoorList._objects2._x = INDOOR_OBJECT_X[listOffset][2];
+ _indoorList._objects2._y = MAP_OBJECT_Y[listOffset][2];
+ _indoorList._objects2._frame = mazeObject._frame;
+ _indoorList._objects2._sprites = mazeObject._sprites;
+ _indoorList._objects2._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects2._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 4
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][9]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][9]) == mazeObject._position.y) {
+ if (_wo[27] && _wo[26]) {
+ } else if (_wo[27] && _wo[29]) {
+ } else if (_wo[24] && _wo[26]) {
+ } else if (_wo[24] && _wo[29]) {
+ } else if (_indoorList._objects3._frame == -1) {
+ _indoorList._objects3._x = INDOOR_OBJECT_X[listOffset][3];
+ _indoorList._objects3._y = MAP_OBJECT_Y[listOffset][3];
+ _indoorList._objects3._frame = mazeObject._frame;
+ _indoorList._objects3._sprites = mazeObject._sprites;
+ _indoorList._objects3._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects3._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 5
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][14]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][14]) == mazeObject._position.y) {
+ if (!_wo[22] && !_wo[27] && _indoorList._objects4._frame == -1) {
+ _indoorList._objects4._x = INDOOR_OBJECT_X[listOffset][4];
+ _indoorList._objects4._y = MAP_OBJECT_Y[listOffset][4];
+ _indoorList._objects4._frame = mazeObject._frame;
+ _indoorList._objects4._sprites = mazeObject._sprites;
+ _indoorList._objects4._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects4._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 6
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][12]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][12]) == mazeObject._position.y) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_indoorList._objects5._frame == -1) {
+ _indoorList._objects5._x = INDOOR_OBJECT_X[listOffset][5];
+ _indoorList._objects5._y = MAP_OBJECT_Y[listOffset][5];
+ _indoorList._objects5._frame = mazeObject._frame;
+ _indoorList._objects5._sprites = mazeObject._sprites;
+ _indoorList._objects5._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects5._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 7
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][16]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][16]) == mazeObject._position.y) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_indoorList._objects6._frame == -1) {
+ _indoorList._objects6._x = INDOOR_OBJECT_X[listOffset][6];
+ _indoorList._objects6._y = MAP_OBJECT_Y[listOffset][6];
+ _indoorList._objects6._frame = mazeObject._frame;
+ _indoorList._objects6._sprites = mazeObject._sprites;
+ _indoorList._objects6._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects6._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 8
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][27]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][27]) == mazeObject._position.y) {
+ if (!_wo[27] && !_wo[22] && !_wo[15] && _indoorList._objects7._frame == -1) {
+ _indoorList._objects7._x = INDOOR_OBJECT_X[listOffset][7];
+ _indoorList._objects7._y = MAP_OBJECT_Y[listOffset][7];
+ _indoorList._objects7._frame = mazeObject._frame;
+ _indoorList._objects7._sprites = mazeObject._sprites;
+ _indoorList._objects7._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects7._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 9
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][25]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][25]) == mazeObject._position.y) {
+ if (_wo[27] || _wo[22]) {
+ } else if (_wo[15] && _wo[17]) {
+ } else if (_wo[15] && _wo[12]) {
+ } else if (_wo[12] && _wo[7]) {
+ } else if (_wo[17] && _wo[7]) {
+ } else if (_indoorList._objects8._frame == -1) {
+ _indoorList._objects8._x = INDOOR_OBJECT_X[listOffset][8];
+ _indoorList._objects8._y = MAP_OBJECT_Y[listOffset][8];
+ _indoorList._objects8._frame = mazeObject._frame;
+ _indoorList._objects8._sprites = mazeObject._sprites;
+ _indoorList._objects8._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects8._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 10
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][23]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][23]) == mazeObject._position.y) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (!_wo[12] && !_wo[8] && _indoorList._objects9._frame == -1) {
+ _indoorList._objects9._x = INDOOR_OBJECT_X[listOffset][10];
+ _indoorList._objects9._y = MAP_OBJECT_Y[listOffset][10];
+ _indoorList._objects9._frame = mazeObject._frame;
+ _indoorList._objects9._sprites = mazeObject._sprites;
+ _indoorList._objects9._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects9._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Block 11
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][29]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][29]) == mazeObject._position.y) {
+ if (_wo[27]) {
+ } else if (_wo[15] && _wo[19]) {
+ } else if (_wo[15] && _wo[14]) {
+ } else if (_wo[14] && _wo[9]) {
+ } else if (_wo[19] && _wo[9]) {
+ } else if (_indoorList._objects10._frame == -1) {
+ _indoorList._objects10._x = INDOOR_OBJECT_X[listOffset][9];
+ _indoorList._objects10._y = MAP_OBJECT_Y[listOffset][9];
+ _indoorList._objects10._frame = mazeObject._frame;
+ _indoorList._objects10._sprites = mazeObject._sprites;
+ _indoorList._objects10._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects10._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Block 12
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][31]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][31]) == mazeObject._position.y) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (!_wo[14] && !_wo[10] && _indoorList._objects11._frame == -1) {
+ _indoorList._objects11._x = INDOOR_OBJECT_X[listOffset][11];
+ _indoorList._objects11._y = MAP_OBJECT_Y[listOffset][11];
+ _indoorList._objects11._frame = mazeObject._frame;
+ _indoorList._objects11._sprites = mazeObject._sprites;
+ _indoorList._objects11._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects11._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+ }
+}
+
+void InterfaceMap::setIndoorsWallPics() {
+ Map &map = *_vm->_map;
+ const Common::Point &mazePos = _vm->_party->_mazePosition;
+ Direction dir = _vm->_party->_mazeDirection;
+
+ Common::fill(&_wp[0], &_wp[20], -1);
+
+ for (uint idx = 0; idx < map._mobData._wallItems.size(); ++idx) {
+ MazeWallItem &wallItem = map._mobData._wallItems[idx];
+ if (wallItem._direction != dir)
+ continue;
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][2]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][2])) {
+ if (_wp[1] == -1) {
+ _indoorList[148]._frame = wallItem._frame;
+ _indoorList[148]._sprites = wallItem._sprites;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][7]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][7])) {
+ if (!_wo[27] && _wp[1] == -1) {
+ _indoorList[123]._frame = wallItem._frame;
+ _indoorList[123]._sprites = wallItem._sprites;
+ _wp[4] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][5]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][5])) {
+ if (_wo[27] && _wo[25]) {
+ } else if (_wo[27] && _wo[28]) {
+ } else if (_wo[23] && _wo[25]) {
+ } else if (_wo[23] && _wo[28]) {
+ } else if (_wp[3] == -1) {
+ _indoorList[122]._frame = wallItem._frame;
+ _indoorList[122]._sprites = wallItem._sprites;
+ _wp[3] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][9]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][9])) {
+ if (_wo[27] && _wo[26]) {
+ } else if (_wo[27] && _wo[29]) {
+ } else if (_wo[24] && _wo[26]) {
+ } else if (_wo[24] && _wo[29]) {
+ } else if (_wp[5] == -1) {
+ _indoorList[124]._frame = wallItem._frame;
+ _indoorList[124]._sprites = wallItem._sprites;
+ _wp[5] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][14]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][14])) {
+ if (!_wo[22] && !_wo[27] && !_wp[8]) {
+ _indoorList[94]._frame = wallItem._frame;
+ _indoorList[94]._sprites = wallItem._sprites;
+ _wp[8] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][12]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][12])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_wp[7] == -1) {
+ _indoorList[93]._frame = wallItem._frame;
+ _indoorList[93]._sprites = wallItem._sprites;
+ _wp[7] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][16]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][16])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_wp[9] == -1) {
+ _indoorList[95]._frame = wallItem._frame;
+ _indoorList[95]._sprites = wallItem._sprites;
+ _wp[9] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][12]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][12])) {
+ if (_wo[27]) {
+ } else if (_wo[25] && _wo[28]) {
+ } else if (_wo[20] && _wo[16]) {
+ } else if (_wp[6] == -1) {
+ _indoorList[92]._frame = wallItem._frame;
+ _indoorList[92]._sprites = wallItem._sprites;
+ _wp[6] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][16]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][16])) {
+ if (!_wo[26] && !_wo[29] && !_wo[21] && !_wo[18] && _wp[10] == -1) {
+ _indoorList[96]._frame = wallItem._frame;
+ _indoorList[96]._sprites = wallItem._sprites;
+ _wp[10] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][27]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][27])) {
+ if (!_wo[27] && !_wo[22] && !_wo[15] && _wp[15] == -1) {
+ _indoorList[50]._frame = wallItem._frame;
+ _indoorList[50]._sprites = wallItem._sprites;
+ _wp[15] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][25]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][25])) {
+ if (_wo[27]) {
+ } else if (_wo[27] && _wo[22]) {
+ } else if (_wo[15] && _wo[17]) {
+ } else if (_wo[15] && _wo[12]) {
+ } else if (_wo[12] && _wo[7]) {
+ } else if (_wo[17] && _wo[7]) {
+ } else if (_wp[14] == -1) {
+ _indoorList[49]._frame = wallItem._frame;
+ _indoorList[49]._sprites = wallItem._sprites;
+ _wp[14] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][23]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][23])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (_wo[12] && _wo[8]) {
+ } else if (_wp[13] == -1) {
+ _indoorList[48]._frame = wallItem._frame;
+ _indoorList[48]._sprites = wallItem._sprites;
+ _wp[13] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][29]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][29])) {
+ if (_wo[27] || _wo[22]) {
+ } else if (_wo[15] && _wo[19]) {
+ } else if (_wo[15] && _wo[14]) {
+ } else if (_wo[14] && _wo[9]) {
+ } else if (_wo[19] && _wo[9]) {
+ } else if (_wp[16] == -1) {
+ _indoorList[51]._frame = wallItem._frame;
+ _indoorList[51]._sprites = wallItem._sprites;
+ _wp[16] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][31]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][31])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (!_wo[14] && !_wo[10] && _wp[17] == -1) {
+ _indoorList[52]._frame = wallItem._frame;
+ _indoorList[52]._sprites = wallItem._sprites;
+ _wp[17] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][23]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][23])) {
+ if (!_wo[27] && !_wo[20] && !_wo[12] && !_wo[23] && !_wo[8] && !_wo[30]) {
+ if (_wp[12] == -1) {
+ _indoorList[47]._frame = wallItem._frame;
+ _indoorList[47]._sprites = wallItem._sprites;
+ _wp[12] = idx;
+ }
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][31]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][31])) {
+ if (!_wo[27] && !_wo[21] && !_wo[14] && !_wo[24] && !_wo[10] && !_wo[31]) {
+ if (_wp[18] == -1) {
+ _indoorList[53]._frame = wallItem._frame;
+ _indoorList[53]._sprites = wallItem._sprites;
+ _wp[18] = idx;
+ }
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][23]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][23])) {
+ if (!_wo[25] && !_wo[28] && !_wo[20] && !_wo[11] && !_wo[16] && !_wo[30] && !_wo[32]) {
+ if (_wp[11] == -1) {
+ _indoorList[46]._frame = wallItem._frame;
+ _indoorList[46]._sprites = wallItem._sprites;
+ _wp[11] = idx;
+ }
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][31]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][31])) {
+ if (!_wo[26] && !_wo[20] && !_wo[21] && !_wo[13] && !_wo[18] && !_wo[31] && !_wo[33]) {
+ if (_wp[19] == -1) {
+ _indoorList[54]._frame = wallItem._frame;
+ _indoorList[54]._sprites = wallItem._sprites;
+ _wp[19] = idx;
+ }
+ }
+ }
+ }
+}
+
+void InterfaceMap::setOutdoorsMonsters() {
+ Combat &combat = *_vm->_combat;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Direction dir = party._mazeDirection;
+ Common::Point pt = party._mazePosition;
+
+ for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
+ MazeMonster &monster = map._mobData._monsters[idx];
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][2]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][2])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[0] == -1) {
+ _outdoorList[118]._frame = idx;
+ combat._attackMonsters[0] = idx;
+ } else if (combat._attackMonsters[1] == -1) {
+ _outdoorList[112]._frame = idx;
+ combat._attackMonsters[1] = idx;
+ } else if (combat._attackMonsters[2] == -1) {
+ _outdoorList[115]._frame = idx;
+ combat._attackMonsters[2] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][7]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][7])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[3] == -1) {
+ _outdoorList[94]._frame = idx;
+ combat._attackMonsters[3] = idx;
+ } else if (combat._attackMonsters[4] == -1) {
+ _outdoorList[92]._frame = idx;
+ combat._attackMonsters[4] = idx;
+ } else if (combat._attackMonsters[5] == -1) {
+ _outdoorList[93]._frame = idx;
+ combat._attackMonsters[5] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][5]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][5])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[12] == -1) {
+ _outdoorList[90]._frame = idx;
+ combat._attackMonsters[12] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][9]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][9])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[13] == -1) {
+ _outdoorList[91]._frame = idx;
+ combat._attackMonsters[13] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][14]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][14])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[6] == -1) {
+ _outdoorList[75]._frame = idx;
+ combat._attackMonsters[6] = idx;
+ } else if (combat._attackMonsters[7] == -1) {
+ _outdoorList[73]._frame = idx;
+ combat._attackMonsters[7] = idx;
+ } else if (combat._attackMonsters[8] == -1) {
+ _outdoorList[74]._frame = idx;
+ combat._attackMonsters[8] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][12]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][12])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[14] == -1) {
+ _outdoorList[69]._frame = idx;
+ combat._attackMonsters[14] = idx;
+ } else if (combat._attackMonsters[20] == -1) {
+ _outdoorList[70]._frame = idx;
+ combat._attackMonsters[20] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][16]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][16])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[15] == -1) {
+ _outdoorList[71]._frame = idx;
+ combat._attackMonsters[15] = idx;
+ } else if (combat._attackMonsters[21] == -1) {
+ _outdoorList[72]._frame = idx;
+ combat._attackMonsters[21] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][27]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][27])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[9] == -1) {
+ _outdoorList[52]._frame = idx;
+ combat._attackMonsters[9] = idx;
+ } else if (combat._attackMonsters[10] == -1) {
+ _outdoorList[50]._frame = idx;
+ combat._attackMonsters[10] = idx;
+ } else if (combat._attackMonsters[11] == -1) {
+ _outdoorList[51]._frame = idx;
+ combat._attackMonsters[11] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][25]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][25])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[16] == -1) {
+ _outdoorList[44]._frame = idx;
+ combat._attackMonsters[16] = idx;
+ } else if (combat._attackMonsters[22] == -1) {
+ _outdoorList[42]._frame = idx;
+ combat._attackMonsters[22] = idx;
+ } else if (combat._attackMonsters[24] == -1) {
+ _outdoorList[43]._frame = idx;
+ combat._attackMonsters[24] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][23]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][23])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[18] == -1) {
+ _outdoorList[48]._frame = idx;
+ combat._attackMonsters[18] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][29]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][29])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[17] == -1) {
+ _outdoorList[47]._frame = idx;
+ combat._attackMonsters[17] = idx;
+ } else if (combat._attackMonsters[23] == -1) {
+ _outdoorList[45]._frame = idx;
+ combat._attackMonsters[23] = idx;
+ } else if (combat._attackMonsters[25] == -1) {
+ _outdoorList[46]._frame = idx;
+ combat._attackMonsters[25] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][31]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][31])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[19] == -1) {
+ _outdoorList[49]._frame = idx;
+ combat._attackMonsters[19] = idx;
+ }
+ }
+ }
+
+ _outdoorList[115]._x = 58;
+ _outdoorList[93]._x = 25;
+ _outdoorList[74]._x = 9;
+ _outdoorList[51]._x = -1;
+ _outdoorList[43]._x = -26;
+ _outdoorList[46]._x = 23;
+ _outdoorList[48]._x = -58;
+ _outdoorList[49]._x = 40;
+ _outdoorList[69]._x = -65;
+ _outdoorList[70]._x = -85;
+ _outdoorList[71]._x = 49;
+ _outdoorList[72]._x = 65;
+ _outdoorList[90]._x = -112;
+ _outdoorList[91]._x = 98;
+
+ if (combat._attackMonsters[1] != -1 && combat._attackMonsters[2] == -1) {
+ _outdoorList[118]._x = 31;
+ _outdoorList[112]._x = -36;
+ } else {
+ _outdoorList[118]._x = -5;
+ _outdoorList[112]._x = -67;
+ }
+ if (combat._attackMonsters[4] != -1 && combat._attackMonsters[5] == -1) {
+ _outdoorList[94]._x = 8;
+ _outdoorList[92]._x = -23;
+ } else {
+ _outdoorList[94]._x = -7;
+ _outdoorList[92]._x = -38;
+ }
+ if (combat._attackMonsters[7] != -1 && combat._attackMonsters[8] == -1) {
+ _outdoorList[75]._x = 0;
+ _outdoorList[73]._x = -16;
+ } else {
+ _outdoorList[75]._x = -8;
+ _outdoorList[73]._x = -24;
+ }
+ if (combat._attackMonsters[10] != -1 && combat._attackMonsters[11] == -1) {
+ _outdoorList[52]._x = -5;
+ _outdoorList[50]._x = -13;
+ } else {
+ _outdoorList[52]._x = -9;
+ _outdoorList[50]._x = -17;
+ }
+ if (combat._attackMonsters[22] != -1 && combat._attackMonsters[24] == -1) {
+ _outdoorList[44]._x = -27;
+ _outdoorList[42]._x = -37;
+ } else {
+ _outdoorList[44]._x = -34;
+ _outdoorList[42]._x = -41;
+ }
+ if (combat._attackMonsters[23] != -1 && combat._attackMonsters[25] == -1) {
+ _outdoorList[47]._x = 20;
+ _outdoorList[45]._x = -12;
+ } else {
+ _outdoorList[47]._x = 16;
+ _outdoorList[45]._x = -16;
+ }
+
+ for (int idx = 0; idx < 26; ++idx) {
+ DrawStruct &ds = _outdoorList[OUTDOOR_MONSTER_INDEXES[idx]];
+
+ if (ds._frame != -1) {
+ ds._flags &= ~0xfff;
+
+ // TODO: Double-check.. this section looks *weird*
+ MazeMonster &monster = map._mobData._monsters[ds._frame];
+ MonsterStruct &monsterData = *monster._monsterData;
+
+ ds._frame = monster._frame;
+
+ if (monster._effect2) {
+ ds._flags = MONSTER_EFFECT_FLAGS[monster._effect2 - 1][monster._effect3];
+ }
+
+ if (monster._frame > 7) {
+ monster._frame -= 8;
+ ds._sprites = monster._attackSprites;
+ } else {
+ ds._sprites = monster._sprites;
+ }
+
+ ds._y = OUTDOOR_MONSTERS_Y[idx];
+
+ if (monsterData._flying) {
+ ds._x += COMBAT_FLOAT_X[_combatFloatCounter];
+ ds._y += COMBAT_FLOAT_Y[_combatFloatCounter];
+ }
+ }
+ }
+ // TODO
+}
+
+void InterfaceMap::setOutdoorsObjects() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ const Common::Point &pt = party._mazePosition;
+ Direction dir = party._mazeDirection;
+ int posIndex;
+
+ for (uint idx = 0; idx < map._mobData._objects.size(); ++idx) {
+ MazeObject &obj = map._mobData._objects[idx];
+
+ if (_vm->_files->_isDarkCc) {
+ posIndex = obj._spriteId == 47 ? 1 : 0;
+ } else {
+ posIndex = obj._spriteId == 113 ? 1 : 0;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][2]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][2]) &&
+ _outdoorList[111]._frame == -1) {
+ DrawStruct &ds = _outdoorList[111];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][0];
+ ds._y = MAP_OBJECT_Y[posIndex][0];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ _objNumber = idx;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][7]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][7]) &&
+ _outdoorList[87]._frame == -1) {
+ DrawStruct &ds = _outdoorList[87];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][1];
+ ds._y = MAP_OBJECT_Y[posIndex][1];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][5]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][5]) &&
+ _outdoorList[88]._frame == -1) {
+ DrawStruct &ds = _outdoorList[88];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][2];
+ ds._y = MAP_OBJECT_Y[posIndex][2];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][9]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][9]) &&
+ _outdoorList[89]._frame == -1) {
+ DrawStruct &ds = _outdoorList[89];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][3];
+ ds._y = MAP_OBJECT_Y[posIndex][3];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][14]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][14]) &&
+ _outdoorList[66]._frame == -1) {
+ DrawStruct &ds = _outdoorList[66];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][4];
+ ds._y = MAP_OBJECT_Y[posIndex][4];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][12]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][12]) &&
+ _outdoorList[67]._frame == -1) {
+ DrawStruct &ds = _outdoorList[67];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][5];
+ ds._y = MAP_OBJECT_Y[posIndex][5];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][16]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][16]) &&
+ _outdoorList[68]._frame == -1) {
+ DrawStruct &ds = _outdoorList[68];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][6];
+ ds._y = MAP_OBJECT_Y[posIndex][6];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][27]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][27]) &&
+ _outdoorList[37]._frame == -1) {
+ DrawStruct &ds = _outdoorList[37];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][7];
+ ds._y = MAP_OBJECT_Y[posIndex][7];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][25]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][25]) &&
+ _outdoorList[38]._frame == -1) {
+ DrawStruct &ds = _outdoorList[38];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][8];
+ ds._y = MAP_OBJECT_Y[posIndex][8];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][23]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][23]) &&
+ _outdoorList[40]._frame == -1) {
+ DrawStruct &ds = _outdoorList[40];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][10];
+ ds._y = MAP_OBJECT_Y[posIndex][10];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][29]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][29]) &&
+ _outdoorList[39]._frame == -1) {
+ DrawStruct &ds = _outdoorList[39];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][9];
+ ds._y = MAP_OBJECT_Y[posIndex][9];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][31]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][31]) &&
+ _outdoorList[41]._frame == -1) {
+ DrawStruct &ds = _outdoorList[41];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][11];
+ ds._y = MAP_OBJECT_Y[posIndex][11];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+}
+
+void InterfaceMap::drawIndoors() {
+ Map &map = *_vm->_map;
+ int surfaceId;
+
+ // Draw any surface tiles on top of the default ground
+ for (int cellIndex = 0; cellIndex < 25; ++cellIndex) {
+ map.getCell(DRAW_NUMBERS[cellIndex]);
+
+ DrawStruct &drawStruct = _indoorList._groundTiles[cellIndex];
+ SpriteResource &sprites = map._surfaceSprites[map._currentSurfaceId];
+ drawStruct._sprites = sprites.empty() ? (SpriteResource *)nullptr : &sprites;
+
+ surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];
+ if (surfaceId == SURFTYPE_WATER || surfaceId == SURFTYPE_LAVA ||
+ surfaceId == SURFTYPE_SEWER) {
+ drawStruct._frame = DRAW_FRAMES[cellIndex][_flipWater ? 1 : 0];
+ drawStruct._flags = _flipWater ? SPRFLAG_HORIZ_FLIPPED : 0;
+ } else {
+ drawStruct._frame = DRAW_FRAMES[cellIndex][_flipGround ? 1 : 0];
+ drawStruct._flags = _flipGround ? SPRFLAG_HORIZ_FLIPPED : 0;
+ }
+ }
+
+ if (!_wo[27] && !_wo[20] && !_wo[23] && !_wo[12] && !_wo[8] && !_wo[30]) {
+ if (_wo[39])
+ _indoorList._swl_4F4L._frame = 22;
+ else if (_wo[83])
+ _indoorList._swl_4F4L._frame = 46;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[17] && !_wo[12] && !_wo[8]) {
+ if (_wo[38])
+ _indoorList._swl_4F3L._frame = 20;
+ else if (_wo[82])
+ _indoorList._swl_4F3L._frame = 44;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[15] && !_wo[2] && !_wo[7]) {
+ if (_wo[37])
+ _indoorList._swl_4F2L._frame = 18;
+ else if (_wo[81])
+ _indoorList._swl_4F2L._frame = 42;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[15] && !_wo[6]) {
+ if (_wo[36])
+ _indoorList._swl_4F1L._frame = 16;
+ else if (_wo[80])
+ _indoorList._swl_4F1L._frame = 40;
+ }
+
+ if (!_wo[27] && !_wo[21] && !_wo[24] && !_wo[14] && !_wo[10] && !_wo[31]) {
+ if (_wo[43])
+ _indoorList._swl_4F4R._frame = 23;
+ else if (_wo[87])
+ _indoorList._swl_4F4R._frame = 47;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[19] && !_wo[14] && !_wo[10]) {
+ if (_wo[42])
+ _indoorList._swl_4F3R._frame = 21;
+ else if (_wo[86])
+ _indoorList._swl_4F3R._frame = 45;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[15] && !_wo[5] && !_wo[9]) {
+ if (_wo[41])
+ _indoorList._swl_4F2R._frame = 19;
+ else if (_wo[85])
+ _indoorList._swl_4F2R._frame = 43;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[15] && !_wo[6]) {
+ if (_wo[40])
+ _indoorList._swl_4F1R._frame = 17;
+ else if (_wo[84])
+ _indoorList._swl_4F1R._frame = 41;
+ }
+
+ if (!_wo[25] && !_wo[28] && !_wo[20] && !_wo[11] &&
+ !_wo[16] && !_wo[30] && !_wo[32]) {
+ if (_wo[88])
+ _indoorList._fwl_4F4L._frame = 7;
+ else if (_wo[78])
+ _indoorList._fwl_4F4L._frame = 16;
+ else if (_wo[148])
+ _indoorList._fwl_4F4L._frame = _overallFrame + 1;
+ else if (_wo[108])
+ _indoorList._fwl_4F4L._frame = 8;
+ else if (_wo[168])
+ _indoorList._fwl_4F4L._frame = 10;
+ else if (_wo[128])
+ _indoorList._fwl_4F4L._frame = 9;
+ else if (_wo[34])
+ _indoorList._fwl_4F4L._frame = 0;
+ else if (_wo[188])
+ _indoorList._fwl_4F4L._frame = 15;
+ else if (_wo[208])
+ _indoorList._fwl_4F4L._frame = 14;
+ else if (_wo[228])
+ _indoorList._fwl_4F4L._frame = 6;
+ else if (_wo[248])
+ _indoorList._fwl_4F4L._frame = 11;
+ else if (_wo[268])
+ _indoorList._fwl_4F4L._frame = 12;
+ else if (_wo[288])
+ _indoorList._fwl_4F4L._frame = 13;
+ }
+
+ if (!_wo[26] && !_wo[29] && !_wo[21] && !_wo[13] && !_wo[18] && !_wo[31] && !_wo[33]) {
+ if (_wo[93])
+ _indoorList._fwl_4F4R._frame = 7;
+ else if (_wo[79])
+ _indoorList._fwl_4F4R._frame = 16;
+ else if (_wo[153])
+ _indoorList._fwl_4F4R._frame = _overallFrame + 1;
+ else if (_wo[113])
+ _indoorList._fwl_4F4R._frame = 8;
+ else if (_wo[173])
+ _indoorList._fwl_4F4R._frame = 10;
+ else if (_wo[133])
+ _indoorList._fwl_4F4R._frame = 9;
+ else if (_wo[35])
+ _indoorList._fwl_4F4R._frame = 0;
+ else if (_wo[79])
+ _indoorList._fwl_4F4R._frame = 15;
+ else if (_wo[213])
+ _indoorList._fwl_4F4R._frame = 14;
+ else if (_wo[233])
+ _indoorList._fwl_4F4R._frame = 6;
+ else if (_wo[253])
+ _indoorList._fwl_4F4R._frame = 11;
+ else if (_wo[273])
+ _indoorList._fwl_4F4R._frame = 12;
+ else if (_wo[293])
+ _indoorList._fwl_4F4R._frame = 13;
+ }
+
+ if (!_wo[25] && !_wo[28] && !_wo[20] && !_wo[11] && !_wo[16] && !_wo[30]) {
+ if (_wo[32])
+ _indoorList._swl_3F4L._frame = 14;
+ else if (_wo[76])
+ _indoorList._swl_3F4L._frame = 38;
+ }
+
+ if (!_wo[26] && !_wo[29] && !_wo[21] && !_wo[13] && !_wo[18] && !_wo[31]) {
+ if (_wo[33])
+ _indoorList._swl_3F1R._frame = 15;
+ else if (_wo[77])
+ _indoorList._swl_3F1R._frame = 39;
+ }
+
+ if (_wo[28] && _wo[27]) {
+ } else if (_wo[28] && _wo[12]) {
+ } else if (_wo[28] && _wo[23]) {
+ } else if (_wo[28] && _wo[8]) {
+ } else if (_wo[25] && _wo[27]) {
+ } else if (_wo[25] && _wo[12]) {
+ } else if (_wo[25] && _wo[23]) {
+ } else if (_wo[25] && _wo[8]) {
+ } else if (_wo[11] && _wo[27]) {
+ } else if (_wo[11] && _wo[12]) {
+ } else if (_wo[11] && _wo[23]) {
+ } else if (_wo[11] && _wo[8]) {
+ } else if (_wo[17] && _wo[27]) {
+ } else if (_wo[17] && _wo[12]) {
+ } else if (_wo[17] && _wo[23]) {
+ } else if (_wo[17] && _wo[8]) {
+ } else if (_wo[20]) {
+ } else if (_wo[30]) {
+ _indoorList._swl_3F3L._frame = 12;
+ } else if (_wo[74]) {
+ _indoorList._swl_3F3L._frame = 36;
+ }
+
+ if (_wo[29] && _wo[27]) {
+ } else if (_wo[29] && _wo[14]) {
+ } else if (_wo[29] && _wo[24]) {
+ } else if (_wo[29] && _wo[10]) {
+ } else if (_wo[26] && _wo[27]) {
+ } else if (_wo[26] && _wo[14]) {
+ } else if (_wo[26] && _wo[24]) {
+ } else if (_wo[26] && _wo[10]) {
+ } else if (_wo[13] && _wo[27]) {
+ } else if (_wo[13] && _wo[14]) {
+ } else if (_wo[13] && _wo[24]) {
+ } else if (_wo[13] && _wo[10]) {
+ } else if (_wo[19] && _wo[27]) {
+ } else if (_wo[19] && _wo[24]) {
+ } else if (_wo[19] && _wo[10]) {
+ } else if (_wo[21]) {
+ } else if (_wo[31]) {
+ _indoorList._swl_3F2R._frame = 13;
+ } else if (_wo[75]) {
+ _indoorList._swl_3F2R._frame = 37;
+ }
+
+ if (!_wo[27] && !_wo[20] && !_wo[12] && !_wo[23] && !_wo[8] && !_wo[30]) {
+ if (_wo[89])
+ _indoorList._fwl_4F3L._frame = 7;
+ else if (_wo[44])
+ _indoorList._fwl_4F3L._frame = 16;
+ else if (_wo[149])
+ _indoorList._fwl_4F3L._frame = _overallFrame + 1;
+ else if (_wo[109])
+ _indoorList._fwl_4F3L._frame = 8;
+ else if (_wo[169])
+ _indoorList._fwl_4F3L._frame = 10;
+ else if (_wo[129])
+ _indoorList._fwl_4F3L._frame = 9;
+ else if (_wo[0])
+ _indoorList._fwl_4F3L._frame = 0;
+ else if (_wo[189])
+ _indoorList._fwl_4F3L._frame = 15;
+ else if (_wo[209])
+ _indoorList._fwl_4F3L._frame = 14;
+ else if (_wo[229])
+ _indoorList._fwl_4F3L._frame = 6;
+ else if (_wo[249])
+ _indoorList._fwl_4F3L._frame = 11;
+ else if (_wo[269])
+ _indoorList._fwl_4F3L._frame = 12;
+ else if (_wo[289])
+ _indoorList._fwl_4F3L._frame = 13;
+ }
+
+ if (_wo[22] && _wo[20]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (_wo[12]) {
+ } else if (_wo[8]) {
+ } else if (_wo[90]) {
+ _indoorList._fwl_4F2L._frame = 7;
+ } else if (_wo[45]) {
+ _indoorList._fwl_4F2L._frame = 16;
+ } else if (_wo[150]) {
+ _indoorList._fwl_4F2L._frame = _overallFrame + 1;
+ } else if (_wo[110]) {
+ _indoorList._fwl_4F2L._frame = 8;
+ } else if (_wo[170]) {
+ _indoorList._fwl_4F2L._frame = 10;
+ } else if (_wo[130]) {
+ _indoorList._fwl_4F2L._frame = 9;
+ } else if (_wo[1]) {
+ _indoorList._fwl_4F2L._frame = 0;
+ } else if (_wo[190]) {
+ _indoorList._fwl_4F2L._frame = 15;
+ } else if (_wo[210]) {
+ _indoorList._fwl_4F2L._frame = 14;
+ } else if (_wo[230]) {
+ _indoorList._fwl_4F2L._frame = 6;
+ } else if (_wo[250]) {
+ _indoorList._fwl_4F2L._frame = 11;
+ } else if (_wo[270]) {
+ _indoorList._fwl_4F2L._frame = 12;
+ } else if (_wo[290]) {
+ _indoorList._fwl_4F2L._frame = 13;
+ }
+
+ if (_wo[15] && _wo[17]) {
+ } else if (_wo[15] && _wo[12]) {
+ } else if (_wo[12] && _wo[7]) {
+ } else if (_wo[17] && _wo[7]) {
+ } else if (_wo[91]) {
+ _indoorList._fwl_4F1L._frame = 7;
+ } else if (_wo[46]) {
+ _indoorList._fwl_4F1L._frame = 16;
+ } else if (_wo[151]) {
+ _indoorList._fwl_4F1L._frame = _overallFrame + 1;
+ } else if (_wo[111]) {
+ _indoorList._fwl_4F1L._frame = 8;
+ } else if (_wo[171]) {
+ _indoorList._fwl_4F1L._frame = 10;
+ } else if (_wo[131]) {
+ _indoorList._fwl_4F1L._frame = 9;
+ } else if (_wo[2]) {
+ _indoorList._fwl_4F1L._frame = 0;
+ } else if (_wo[191]) {
+ _indoorList._fwl_4F1L._frame = 15;
+ } else if (_wo[211]) {
+ _indoorList._fwl_4F1L._frame = 14;
+ } else if (_wo[231]) {
+ _indoorList._fwl_4F1L._frame = 6;
+ } else if (_wo[251]) {
+ _indoorList._fwl_4F1L._frame = 11;
+ } else if (_wo[271]) {
+ _indoorList._fwl_4F1L._frame = 12;
+ } else if (_wo[291]) {
+ _indoorList._fwl_4F1L._frame = 13;
+ }
+
+ if (!_wo[27] && !_wo[21] && !_wo[14] && !_wo[24] && !_wo[10] && !_wo[31]) {
+ if (_wo[92]) {
+ _indoorList._fwl_4F3R._frame = 7;
+ } else if (_wo[47]) {
+ _indoorList._fwl_4F3R._frame = 16;
+ } else if (_wo[152]) {
+ _indoorList._fwl_4F3R._frame = _overallFrame + 1;
+ } else if (_wo[112]) {
+ _indoorList._fwl_4F3R._frame = 8;
+ } else if (_wo[172]) {
+ _indoorList._fwl_4F3R._frame = 10;
+ } else if (_wo[132]) {
+ _indoorList._fwl_4F3R._frame = 9;
+ } else if (_wo[3]) {
+ _indoorList._fwl_4F3R._frame = 0;
+ } else if (_wo[192]) {
+ _indoorList._fwl_4F3R._frame = 15;
+ } else if (_wo[212]) {
+ _indoorList._fwl_4F3R._frame = 14;
+ } else if (_wo[232]) {
+ _indoorList._fwl_4F3R._frame = 6;
+ } else if (_wo[252]) {
+ _indoorList._fwl_4F3R._frame = 11;
+ } else if (_wo[272]) {
+ _indoorList._fwl_4F3R._frame = 12;
+ } else if (_wo[292]) {
+ _indoorList._fwl_4F3R._frame = 13;
+ }
+ }
+
+ if (_wo[22] && _wo[21]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (_wo[14] || _wo[10]) {
+ } else if (_wo[94]) {
+ _indoorList._fwl_4F2R._frame = 7;
+ } else if (_wo[48]) {
+ _indoorList._fwl_4F2R._frame = 16;
+ } else if (_wo[154]) {
+ _indoorList._fwl_4F2R._frame = _overallFrame + 1;
+ } else if (_wo[114]) {
+ _indoorList._fwl_4F2R._frame = 8;
+ } else if (_wo[174]) {
+ _indoorList._fwl_4F2R._frame = 10;
+ } else if (_wo[134]) {
+ _indoorList._fwl_4F2R._frame = 9;
+ } else if (_wo[4]) {
+ _indoorList._fwl_4F2R._frame = 0;
+ } else if (_wo[194]) {
+ _indoorList._fwl_4F2R._frame = 15;
+ } else if (_wo[214]) {
+ _indoorList._fwl_4F2R._frame = 14;
+ } else if (_wo[234]) {
+ _indoorList._fwl_4F2R._frame = 6;
+ } else if (_wo[254]) {
+ _indoorList._fwl_4F2R._frame = 11;
+ } else if (_wo[274]) {
+ _indoorList._fwl_4F2R._frame = 12;
+ } else if (_wo[294]) {
+ _indoorList._fwl_4F2R._frame = 13;
+ }
+
+ if (_wo[15] && _wo[19]) {
+ } else if (_wo[15] && _wo[14]) {
+ } else if (_wo[14] && _wo[9]) {
+ } else if (_wo[19] && _wo[9]) {
+ } else if (_wo[95]) {
+ _indoorList._fwl_4F1R._frame = 7;
+ } else if (_wo[49]) {
+ _indoorList._fwl_4F1R._frame = 16;
+ } else if (_wo[155]) {
+ _indoorList._fwl_4F1R._frame = _overallFrame + 1;
+ } else if (_wo[115]) {
+ _indoorList._fwl_4F1R._frame = 8;
+ } else if (_wo[175]) {
+ _indoorList._fwl_4F1R._frame = 10;
+ } else if (_wo[135]) {
+ _indoorList._fwl_4F1R._frame = 9;
+ } else if (_wo[5]) {
+ _indoorList._fwl_4F1R._frame = 0;
+ } else if (_wo[195]) {
+ _indoorList._fwl_4F1R._frame = 15;
+ } else if (_wo[215]) {
+ _indoorList._fwl_4F1R._frame = 14;
+ } else if (_wo[235]) {
+ _indoorList._fwl_4F1R._frame = 6;
+ } else if (_wo[255]) {
+ _indoorList._fwl_4F1R._frame = 11;
+ } else if (_wo[275]) {
+ _indoorList._fwl_4F1R._frame = 12;
+ } else if (_wo[295]) {
+ _indoorList._fwl_4F1R._frame = 13;
+ }
+
+ if (_wo[27] || _wo[22] || _wo[15] || _wo[96]) {
+ } else if (_wo[50]) {
+ _indoorList._fwl_4F._frame = 16;
+ } else if (_wo[156]) {
+ _indoorList._fwl_4F._frame = _overallFrame + 1;
+ } else if (_wo[116]) {
+ _indoorList._fwl_4F._frame = 8;
+ } else if (_wo[176]) {
+ _indoorList._fwl_4F._frame = 10;
+ } else if (_wo[136]) {
+ _indoorList._fwl_4F._frame = 9;
+ } else if (_wo[6]) {
+ _indoorList._fwl_4F._frame = 0;
+ } else if (_wo[196]) {
+ _indoorList._fwl_4F._frame = 15;
+ } else if (_wo[216]) {
+ _indoorList._fwl_4F._frame = 14;
+ } else if (_wo[236]) {
+ _indoorList._fwl_4F._frame = 6;
+ } else if (_wo[256]) {
+ _indoorList._fwl_4F._frame = 11;
+ } else if (_wo[276]) {
+ _indoorList._fwl_4F._frame = 12;
+ } else if (_wo[296]) {
+ _indoorList._fwl_4F._frame = 13;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[15]) {
+ if (_wo[7])
+ _indoorList._swl_3F1L._frame = 8;
+ else if (_wo[51])
+ _indoorList._swl_3F1L._frame = 32;
+ }
+
+ if (_wo[22] && _wo[23]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[17] && _wo[23]) {
+ } else if (_wo[17] && _wo[20]) {
+ } else if (_wo[8]) {
+ _indoorList._swl_3F2L._frame = 10;
+ } else if (_wo[52]) {
+ _indoorList._swl_3F2L._frame = 34;
+ }
+
+ if (_wo[27] || _wo[22] || _wo[15]) {
+ } else if (_wo[9]) {
+ _indoorList._swl_3F4R._frame = 9;
+ } else if (_wo[53]) {
+ _indoorList._swl_3F4R._frame = 33;
+ }
+
+ if (_wo[22] && _wo[24]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[19] && _wo[24]) {
+ } else if (_wo[19] && _wo[21]) {
+ } else if (_wo[14]) {
+ } else if (_wo[10]) {
+ _indoorList._swl_3F3R._frame = 11;
+ } else if (_wo[54]) {
+ _indoorList._swl_3F3R._frame = 35;
+ }
+
+ if (_wo[25] || _wo[28] || _wo[20] || _wo[16]) {
+ } else if (_wo[97]) {
+ _indoorList._fwl_3F2L._frame = 24;
+ } else if (_wo[55]) {
+ _indoorList._fwl_3F2L._frame = 33;
+ } else if (_wo[137]) {
+ _indoorList._fwl_3F2L._frame = 26;
+ } else if (_wo[157]) {
+ _indoorList._fwl_3F2L._frame = _overallFrame + 18;
+ } else if (_wo[117]) {
+ _indoorList._fwl_3F2L._frame = 25;
+ } else if (_wo[177]) {
+ _indoorList._fwl_3F2L._frame = 27;
+ } else if (_wo[11]) {
+ _indoorList._fwl_3F2L._frame = 17;
+ } else if (_wo[197]) {
+ _indoorList._fwl_3F2L._frame = 32;
+ } else if (_wo[217]) {
+ _indoorList._fwl_3F2L._frame = 31;
+ } else if (_wo[237]) {
+ _indoorList._fwl_3F2L._frame = 23;
+ } else if (_wo[257]) {
+ _indoorList._fwl_3F2L._frame = 28;
+ } else if (_wo[277]) {
+ _indoorList._fwl_3F2L._frame = 29;
+ } else if (_wo[297]) {
+ _indoorList._fwl_3F2L._frame = 30;
+ }
+
+ if (_wo[22] && _wo[23]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_wo[98]) {
+ _indoorList._fwl_3F1L._frame = 24;
+ } else if (_wo[56]) {
+ _indoorList._fwl_3F1L._frame = 33;
+ } else if (_wo[178]) {
+ _indoorList._fwl_3F1L._frame = 27;
+ } else if (_wo[118]) {
+ _indoorList._fwl_3F1L._frame = 25;
+ } else if (_wo[158]) {
+ _indoorList._fwl_3F1L._frame = _overallFrame + 18;
+ } else if (_wo[138]) {
+ _indoorList._fwl_3F1L._frame = 26;
+ } else if (_wo[12]) {
+ _indoorList._fwl_3F1L._frame = 17;
+ } else if (_wo[198]) {
+ _indoorList._fwl_3F1L._frame = 32;
+ } else if (_wo[218]) {
+ _indoorList._fwl_3F1L._frame = 31;
+ } else if (_wo[238]) {
+ _indoorList._fwl_3F1L._frame = 23;
+ } else if (_wo[258]) {
+ _indoorList._fwl_3F1L._frame = 28;
+ } else if (_wo[278]) {
+ _indoorList._fwl_3F1L._frame = 29;
+ } else if (_wo[298]) {
+ _indoorList._fwl_3F1L._frame = 30;
+ }
+
+ if (_wo[26] || _wo[29] || _wo[21] || _wo[18]) {
+ } else if (_wo[99]) {
+ _indoorList._fwl_3F2R._frame = 24;
+ } else if (_wo[57]) {
+ _indoorList._fwl_3F2R._frame = 33;
+ } else if (_wo[139]) {
+ _indoorList._fwl_3F2R._frame = 26;
+ } else if (_wo[159]) {
+ _indoorList._fwl_3F2R._frame = _overallFrame + 18;
+ } else if (_wo[119]) {
+ _indoorList._fwl_3F2R._frame = 25;
+ } else if (_wo[179]) {
+ _indoorList._fwl_3F2R._frame = 27;
+ } else if (_wo[13]) {
+ _indoorList._fwl_3F2R._frame = 17;
+ } else if (_wo[199]) {
+ _indoorList._fwl_3F2R._frame = 32;
+ } else if (_wo[219]) {
+ _indoorList._fwl_3F2R._frame = 31;
+ } else if (_wo[239]) {
+ _indoorList._fwl_3F2R._frame = 23;
+ } else if (_wo[259]) {
+ _indoorList._fwl_3F2R._frame = 28;
+ } else if (_wo[279]) {
+ _indoorList._fwl_3F2R._frame = 29;
+ } else if (_wo[299]) {
+ _indoorList._fwl_3F2R._frame = 30;
+ }
+
+ if (_wo[22] && _wo[24]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_wo[100]) {
+ _indoorList._fwl_3F1R._frame = 24;
+ } else if (_wo[58]) {
+ _indoorList._fwl_3F1R._frame = 33;
+ } else if (_wo[140]) {
+ _indoorList._fwl_3F1R._frame = 26;
+ } else if (_wo[160]) {
+ _indoorList._fwl_3F1R._frame = _overallFrame + 18;
+ } else if (_wo[120]) {
+ _indoorList._fwl_3F1R._frame = 25;
+ } else if (_wo[180]) {
+ _indoorList._fwl_3F1R._frame = 27;
+ } else if (_wo[14]) {
+ _indoorList._fwl_3F1R._frame = 17;
+ } else if (_wo[200]) {
+ _indoorList._fwl_3F1R._frame = 32;
+ } else if (_wo[220]) {
+ _indoorList._fwl_3F1R._frame = 31;
+ } else if (_wo[240]) {
+ _indoorList._fwl_3F1R._frame = 23;
+ } else if (_wo[260]) {
+ _indoorList._fwl_3F1R._frame = 28;
+ } else if (_wo[280]) {
+ _indoorList._fwl_3F1R._frame = 29;
+ } else if (_wo[300]) {
+ _indoorList._fwl_3F1R._frame = 30;
+ }
+
+ if (_wo[22] || _wo[27]) {
+ } else if (_wo[101]) {
+ _indoorList._fwl_3F._frame = 24;
+ } else if (_wo[59]) {
+ _indoorList._fwl_3F._frame = 33;
+ } else if (_wo[141]) {
+ _indoorList._fwl_3F._frame = 26;
+ } else if (_wo[161]) {
+ _indoorList._fwl_3F._frame = _overallFrame + 18;
+ } else if (_wo[121]) {
+ _indoorList._fwl_3F._frame = 25;
+ } else if (_wo[181]) {
+ _indoorList._fwl_3F._frame = 27;
+ } else if (_wo[15]) {
+ _indoorList._fwl_3F._frame = 17;
+ } else if (_wo[201]) {
+ _indoorList._fwl_3F._frame = 32;
+ } else if (_wo[221]) {
+ _indoorList._fwl_3F._frame = 31;
+ } else if (_wo[241]) {
+ _indoorList._fwl_3F._frame = 23;
+ } else if (_wo[261]) {
+ _indoorList._fwl_3F._frame = 28;
+ } else if (_wo[281]) {
+ _indoorList._fwl_3F._frame = 29;
+ } else if (_wo[301]) {
+ _indoorList._fwl_3F._frame = 30;
+ }
+
+ if (_wo[25] || _wo[28] || _wo[20]) {
+ } else if (_wo[16]) {
+ _indoorList._swl_2F2L._frame = 6;
+ } else if (_wo[60]) {
+ _indoorList._swl_2F2L._frame = 30;
+ }
+
+ if (_wo[27] || _wo[22]) {
+ } else if (_wo[17]) {
+ _indoorList._swl_2F1L._frame = 4;
+ } else if (_wo[61]) {
+ _indoorList._swl_2F1L._frame = 28;
+ }
+
+ if (_wo[26] || _wo[29] || _wo[21]) {
+ } else if (_wo[18]) {
+ _indoorList._swl_2F2R._frame = 7;
+ } else if (_wo[62]) {
+ _indoorList._swl_2F2R._frame = 31;
+ }
+
+ if (_wo[27] || _wo[22]) {
+ } else if (_wo[19]) {
+ _indoorList._swl_2F1R._frame = 5;
+ } else if (_wo[63]) {
+ _indoorList._swl_2F1R._frame = 29;
+ }
+
+ if (_wo[27] && _wo[25]) {
+ } else if (_wo[27] && _wo[28]) {
+ } else if (_wo[23] & _wo[25]) {
+ } else if (_wo[23] && _wo[28]) {
+ } else if (_wo[102]) {
+ _indoorList._fwl_2F1L._frame = 7;
+ } else if (_wo[64]) {
+ _indoorList._fwl_2F1L._frame = 16;
+ } else if (_wo[182]) {
+ _indoorList._fwl_2F1L._frame = 10;
+ } else if (_wo[122]) {
+ _indoorList._fwl_2F1L._frame = 8;
+ } else if (_wo[142]) {
+ _indoorList._fwl_2F1L._frame = 9;
+ } else if (_wo[162]) {
+ _indoorList._fwl_2F1L._frame = _overallFrame + 1;
+ } else if (_wo[20]) {
+ _indoorList._fwl_2F1L._frame = 0;
+ } else if (_wo[202]) {
+ _indoorList._fwl_2F1L._frame = 15;
+ } else if (_wo[222]) {
+ _indoorList._fwl_2F1L._frame = 14;
+ } else if (_wo[242]) {
+ _indoorList._fwl_2F1L._frame = 6;
+ } else if (_wo[262]) {
+ _indoorList._fwl_2F1L._frame = 11;
+ } else if (_wo[282]) {
+ _indoorList._fwl_2F1L._frame = 12;
+ } else if (_wo[302]) {
+ _indoorList._fwl_2F1L._frame = 13;
+ }
+
+ if (_wo[27] && _wo[26]) {
+ } else if (_wo[27] && _wo[29]) {
+ } else if (_wo[24] && _wo[26]) {
+ } else if (_wo[24] && _wo[29]) {
+ } else if (_wo[103]) {
+ _indoorList._fwl_2F1R._frame = 7;
+ } else if (_wo[65]) {
+ _indoorList._fwl_2F1R._frame = 16;
+ } else if (_wo[183]) {
+ _indoorList._fwl_2F1R._frame = 10;
+ } else if (_wo[123]) {
+ _indoorList._fwl_2F1R._frame = 8;
+ } else if (_wo[143]) {
+ _indoorList._fwl_2F1R._frame = 9;
+ } else if (_wo[163]) {
+ _indoorList._fwl_2F1R._frame = _overallFrame + 1;
+ } else if (_wo[21]) {
+ _indoorList._fwl_2F1R._frame = 0;
+ } else if (_wo[203]) {
+ _indoorList._fwl_2F1R._frame = 15;
+ } else if (_wo[223]) {
+ _indoorList._fwl_2F1R._frame = 14;
+ } else if (_wo[243]) {
+ _indoorList._fwl_2F1R._frame = 6;
+ } else if (_wo[263]) {
+ _indoorList._fwl_2F1R._frame = 11;
+ } else if (_wo[283]) {
+ _indoorList._fwl_2F1R._frame = 12;
+ } else if (_wo[303]) {
+ _indoorList._fwl_2F1R._frame = 13;
+ }
+
+ if (_wo[27]) {
+ } else if (_wo[104]) {
+ _indoorList._fwl_2F._frame = 7;
+ } else if (_wo[66]) {
+ _indoorList._fwl_2F._frame = 16;
+ } else if (_wo[184]) {
+ _indoorList._fwl_2F._frame = 10;
+ } else if (_wo[124]) {
+ _indoorList._fwl_2F._frame = 8;
+ } else if (_wo[144]) {
+ _indoorList._fwl_2F._frame = 9;
+ } else if (_wo[164]) {
+ _indoorList._fwl_2F._frame = _overallFrame + 1;
+ } else if (_wo[22]) {
+ _indoorList._fwl_2F._frame = 0;
+ } else if (_wo[204]) {
+ _indoorList._fwl_2F._frame = 15;
+ } else if (_wo[224]) {
+ _indoorList._fwl_2F._frame = 14;
+ } else if (_wo[244]) {
+ _indoorList._fwl_2F._frame = 6;
+ } else if (_wo[264]) {
+ _indoorList._fwl_2F._frame = 11;
+ } else if (_wo[284]) {
+ _indoorList._fwl_2F._frame = 12;
+ } else if (_wo[304]) {
+ _indoorList._fwl_2F._frame = 13;
+ }
+
+ if (_wo[27]) {
+ } else if (_wo[23]) {
+ _indoorList._swl_1F1L._frame = 2;
+ } else if (_wo[67]) {
+ _indoorList._swl_1F1L._frame = 26;
+ }
+
+ if (_wo[27]) {
+ } else if (_wo[24]) {
+ _indoorList._swl_1F1R._frame = 3;
+ } else if (_wo[68]) {
+ _indoorList._swl_1F1R._frame = 27;
+ }
+
+ if (_wo[28]) {
+ } else if (_wo[105] || _wo[25] || _wo[165] || _wo[125] || _wo[185] || _wo[145]) {
+ _indoorList._fwl_1F1L._frame = 0;
+ _indoorList._fwl_1F1L._sprites = &map._wallSprites._fwl1;
+ } else if (_wo[69]) {
+ _indoorList._fwl_1F1L._frame = 9;
+ _indoorList._fwl_1F1L._sprites = &map._wallSprites._fwl2;
+ }
+
+ if (_wo[29]) {
+ } else if (_wo[106] || _wo[26] || _wo[166] || _wo[126] || _wo[186] || _wo[146]) {
+ _indoorList._fwl_1F._frame = 0;
+ _indoorList._fwl_1F._sprites = &map._wallSprites._fwl1;
+ } else if (_wo[70]) {
+ _indoorList._fwl_1F._frame = 9;
+ _indoorList._fwl_1F._sprites = &map._wallSprites._fwl2;
+ }
+
+ if (_wo[107]) {
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ if (!_openDoor)
+ _indoorList._fwl_1F1R._frame = 0;
+ else
+ _indoorList._fwl_1F1R._frame = map.mazeData()._wallKind ? 1 : 10;
+ } else if (_wo[71]) {
+ _indoorList._fwl_1F1R._frame = 9;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[167]) {
+ _indoorList._fwl_1F1R._frame = _overallFrame + 1;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[127]) {
+ _indoorList._fwl_1F1R._frame = 1;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[147]) {
+ _indoorList._fwl_1F1R._frame = 2;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[187]) {
+ _indoorList._fwl_1F1R._frame = 3;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[27]) {
+ _indoorList._fwl_1F1R._frame = 0;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl1;
+ } else if (_wo[207]) {
+ _indoorList._fwl_1F1R._frame = 8;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[227]) {
+ _indoorList._fwl_1F1R._frame = 7;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[247]) {
+ _indoorList._fwl_1F1R._frame = 6;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl1;
+ } else if (_wo[267]) {
+ _indoorList._fwl_1F1R._frame = 4;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[287]) {
+ _indoorList._fwl_1F1R._frame = 5;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[307]) {
+ _indoorList._fwl_1F1R._frame = 6;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ }
+
+ if (_wo[28]) {
+ _indoorList._swl_0F1L._frame = 0;
+ } else if (_wo[72]) {
+ _indoorList._swl_0F1L._frame = 24;
+ }
+
+ if (_wo[29]) {
+ _indoorList._swl_0F1R._frame = 1;
+ } else if (_wo[73]) {
+ _indoorList._swl_0F1R._frame = 25;
+ }
+
+ map.cellFlagLookup(_vm->_party->_mazePosition);
+
+ assert(map._currentSky < 2);
+ _indoorList[0]._sprites = &map._skySprites[map._currentSky];
+ _indoorList[0]._flags = _flipSky ? SPRFLAG_HORIZ_FLIPPED : 0;
+
+ if (_openDoor) {
+ Common::Point pt(
+ _vm->_party->_mazePosition.x + SCREEN_POSITIONING_X[
+ _vm->_party->_mazeDirection][_vm->_party->_mazePosition.x],
+ _vm->_party->_mazePosition.y + SCREEN_POSITIONING_Y[
+ _vm->_party->_mazeDirection][_vm->_party->_mazePosition.y]
+ );
+ map.cellFlagLookup(pt);
+
+ _indoorList._sky2._sprites = &map._skySprites[0];
+ } else {
+ _indoorList._sky2._sprites = _indoorList[0]._sprites;
+ }
+
+ _indoorList._sky2._flags = _flipSky ? SPRFLAG_HORIZ_FLIPPED : 0;
+ _indoorList._ground._flags = _flipDefaultGround ? SPRFLAG_HORIZ_FLIPPED : 0;
+ _indoorList._horizon._frame = 7;
+
+ // Finally draw the darn indoor scene
+ _vm->_screen->_windows[3].drawList(&_indoorList[0], _indoorList.size());
+
+ // Check for any character shooting
+ _isAttacking = false;
+ for (uint idx = 0; idx < _vm->_party->_activeParty.size(); ++idx) {
+ if (_vm->_combat->_shooting[idx])
+ _isAttacking = true;
+ }
+
+ _charsShooting = _isAttacking;
+}
+
+void InterfaceMap::drawOutdoors() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ int surfaceId;
+
+ // Draw any surface tiles on top of the default ground
+ for (int cellIndex = 0; cellIndex < 25; ++cellIndex) {
+ map.getCell(cellIndex == 24 ? 2 : DRAW_NUMBERS[cellIndex]);
+
+ DrawStruct &drawStruct = _outdoorList._groundTiles[cellIndex];
+ SpriteResource &sprites = map._surfaceSprites[map._currentSurfaceId];
+ drawStruct._sprites = sprites.empty() ? (SpriteResource *)nullptr : &sprites;
+
+ surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];
+ if (surfaceId == SURFTYPE_DWATER || surfaceId == SURFTYPE_LAVA) {
+ drawStruct._frame = DRAW_FRAMES[cellIndex][_flipWater ? 1 : 0];
+ drawStruct._flags = _flipWater ? SPRFLAG_HORIZ_FLIPPED : 0;
+ } else {
+ drawStruct._frame = DRAW_FRAMES[cellIndex][_flipGround ? 1 : 0];
+ drawStruct._flags = _flipGround ? SPRFLAG_HORIZ_FLIPPED : 0;
+ }
+ }
+
+ party.handleLight();
+
+ // Set up terrain draw entries
+ const int TERRAIN_INDEXES1[9] = { 44, 36, 37, 38, 45, 43, 42, 41, 39 };
+ const int TERRAIN_INDEXES2[5] = { 22, 24, 31, 29, 26 };
+ const int TERRAIN_INDEXES3[3] = { 11, 16, 13 };
+ const int TERRAIN_INDEXES4[5] = { 5, 9, 7, 0, 4 };
+
+ // Loops to set draw entries for the terrain
+ for (int idx = 0; idx < 9; ++idx) {
+ map.getCell(TERRAIN_INDEXES1[idx]);
+ SpriteResource &spr = map._wallSprites._surfaces[map._currentWall];
+ _outdoorList[28 + idx]._sprites = spr.size() == 0 ? (SpriteResource *)nullptr : &spr;
+ }
+ for (int idx = 0; idx < 5; ++idx) {
+ map.getCell(TERRAIN_INDEXES2[idx]);
+ SpriteResource &spr = map._wallSprites._surfaces[map._currentWall];
+ _outdoorList[61 + idx]._sprites = spr.size() == 0 ? (SpriteResource *)nullptr : &spr;
+ }
+ for (int idx = 0; idx < 3; ++idx) {
+ map.getCell(TERRAIN_INDEXES3[idx]);
+ SpriteResource &spr = map._wallSprites._surfaces[map._currentWall];
+ _outdoorList[84 + idx]._sprites = spr.size() == 0 ? (SpriteResource *)nullptr : &spr;
+ }
+ for (int idx = 0; idx < 5; ++idx) {
+ map.getCell(TERRAIN_INDEXES4[idx]);
+ SpriteResource &spr = map._wallSprites._surfaces[map._currentWall];
+ _outdoorList[103 + idx]._sprites = spr.size() == 0 ? (SpriteResource *)nullptr : &spr;
+ }
+
+ map.getCell(1);
+ SpriteResource &surface = map._wallSprites._surfaces[map._currentWall];
+ _outdoorList[108]._sprites = surface.size() == 0 ? (SpriteResource *)nullptr : &surface;
+ _outdoorList[109]._sprites = _outdoorList[108]._sprites;
+ _outdoorList[110]._sprites = _outdoorList[108]._sprites;
+ _outdoorList._sky1._flags = _outdoorList._sky2._flags = _flipSky ? SPRFLAG_HORIZ_FLIPPED : 0;
+ _outdoorList._groundSprite._flags = _flipWater ? SPRFLAG_HORIZ_FLIPPED : 0;
+
+ // Finally render the outdoor scene
+ screen._windows[3].drawList(&_outdoorList[0], _outdoorList.size());
+
+ // Check for any character shooting
+ _isAttacking = false;
+ for (uint idx = 0; idx < _vm->_party->_activeParty.size(); ++idx) {
+ if (_vm->_combat->_shooting[idx])
+ _isAttacking = true;
+ }
+
+ _charsShooting = _isAttacking;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/interface_map.h b/engines/xeen/interface_map.h
new file mode 100644
index 0000000000..2189aacec1
--- /dev/null
+++ b/engines/xeen/interface_map.h
@@ -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.
+ *
+ */
+
+#ifndef XEEN_INTERFACE_MAP_H
+#define XEEN_INTERFACE_MAP_H
+
+#include "common/scummsys.h"
+#include "xeen/map.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+class XeenEngine;
+
+class OutdoorDrawList {
+public:
+ DrawStruct _data[132];
+ DrawStruct &_sky1, &_sky2;
+ DrawStruct &_groundSprite;
+ DrawStruct * const _groundTiles;
+ DrawStruct * const _attackImgs1;
+ DrawStruct * const _attackImgs2;
+ DrawStruct * const _attackImgs3;
+ DrawStruct * const _attackImgs4;
+public:
+ OutdoorDrawList();
+
+ DrawStruct &operator[](int idx) {
+ assert(idx < size());
+ return _data[idx];
+ }
+
+ int size() const { return 132; }
+};
+
+class IndoorDrawList {
+public:
+ DrawStruct _data[170];
+ DrawStruct &_sky1, &_sky2;
+ DrawStruct &_ground;
+ DrawStruct &_horizon;
+ DrawStruct * const _groundTiles;
+ DrawStruct &_swl_0F1R, &_swl_0F1L, &_swl_1F1R, &_swl_1F1L,
+ &_swl_2F2R, &_swl_2F1R, &_swl_2F1L, &_swl_2F2L,
+ &_swl_3F1R, &_swl_3F2R, &_swl_3F3R, &_swl_3F4R,
+ &_swl_3F1L, &_swl_3F2L, &_swl_3F3L, &_swl_3F4L,
+ &_swl_4F4R, &_swl_4F3R, &_swl_4F2R, &_swl_4F1R,
+ &_swl_4F1L, &_swl_4F2L, &_swl_4F3L, &_swl_4F4L;
+ DrawStruct &_fwl_4F4R, &_fwl_4F3R, &_fwl_4F2R, &_fwl_4F1R,
+ &_fwl_4F, &_fwl_4F1L, &_fwl_4F2L, &_fwl_4F3L, &_fwl_4F4L;
+ DrawStruct &_fwl_2F1R, &_fwl_2F, &_fwl_2F1L, &_fwl_3F2R,
+ &_fwl_3F1R, &_fwl_3F, &_fwl_3F1L, &_fwl_3F2L;
+ DrawStruct &_fwl_1F, &_fwl_1F1R, &_fwl_1F1L;
+ DrawStruct &_objects0, &_objects1, &_objects2, &_objects3;
+ DrawStruct &_objects4, &_objects5, &_objects6, &_objects7;
+ DrawStruct &_objects8, &_objects9, &_objects10, &_objects11;
+ DrawStruct * const _attackImgs1;
+ DrawStruct * const _attackImgs2;
+ DrawStruct * const _attackImgs3;
+ DrawStruct * const _attackImgs4;
+public:
+ IndoorDrawList();
+
+ DrawStruct &operator[](int idx) {
+ assert(idx < size());
+ return _data[idx];
+ }
+
+ int size() const { return 170; }
+};
+
+class InterfaceMap {
+private:
+ XeenEngine *_vm;
+ int _combatFloatCounter;
+
+ void initDrawStructs();
+
+ /**
+ * Helper method for setIndoorsMonsters to set a draw structure
+ * with the deatils for a given monster
+ */
+ void setMonsterSprite(DrawStruct &drawStruct, MazeMonster &monster,
+ SpriteResource *sprites, int frame, int defaultY);
+protected:
+ int8 _wp[20];
+ byte _wo[308];
+ bool _flipWater;
+ bool _flipGround;
+ bool _flipSky;
+ bool _flipDefaultGround;
+ bool _thinWall;
+ bool _isAnimReset;
+
+ void setMazeBits();
+
+ /**
+ * Handles animation of monsters, wall items, and combat within the 3d
+ * view by cycling the appropriate frame numbers
+ */
+ void animate3d();
+
+ void drawMap();
+public:
+ OutdoorDrawList _outdoorList;
+ IndoorDrawList _indoorList;
+ SpriteResource _charPowSprites;
+ int _objNumber;
+ int _overallFrame;
+ bool _charsShooting;
+ bool _openDoor;
+ bool _isAttacking;
+public:
+ InterfaceMap(XeenEngine *vm);
+
+ virtual ~InterfaceMap() {}
+
+ /**
+ * Set up draw structures for displaying on-screen monsters
+ */
+ void setIndoorsMonsters();
+
+ /**
+ * Set up draw structures for displaying on-screen objects
+ */
+ void setIndoorsObjects();
+
+ /**
+ * Set up draw structures for displaying on-screen wall items
+ */
+ void setIndoorsWallPics();
+
+ /**
+ * Draw the contents of the current 3d view of an indoor map
+ */
+ void drawIndoors();
+
+ /**
+ * Set up the draw structures for displaying monsters on outdoor maps
+ */
+ void setOutdoorsMonsters();
+
+ /**
+ * Set up the draw structures for displaying objects on outdoor maps
+ */
+ void setOutdoorsObjects();
+
+ /**
+ * Draw the contents of the current 3d view of an outdoor map
+ */
+ void drawOutdoors();
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_INTERFACE_MAP_H */
diff --git a/engines/xeen/items.cpp b/engines/xeen/items.cpp
new file mode 100644
index 0000000000..e8c0249803
--- /dev/null
+++ b/engines/xeen/items.cpp
@@ -0,0 +1,29 @@
+/* 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.
+ *
+ */
+
+#include "xeen/items.h"
+#include "xeen/resources.h"
+
+namespace Xeen {
+
+
+} // End of namespace Xeen
diff --git a/engines/xeen/items.h b/engines/xeen/items.h
new file mode 100644
index 0000000000..bfbd9e4481
--- /dev/null
+++ b/engines/xeen/items.h
@@ -0,0 +1,34 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_ITEMS_H
+#define XEEN_ITEMS_H
+
+#include "xeen/character.h"
+
+namespace Xeen {
+
+
+
+} // End of namespace Xeen
+
+#endif /* XEEN_ITEMS_H */
diff --git a/engines/xeen/map.cpp b/engines/xeen/map.cpp
new file mode 100644
index 0000000000..6816423a19
--- /dev/null
+++ b/engines/xeen/map.cpp
@@ -0,0 +1,1581 @@
+/* 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.
+ *
+ */
+
+#include "common/serializer.h"
+#include "xeen/map.h"
+#include "xeen/interface.h"
+#include "xeen/resources.h"
+#include "xeen/saves.h"
+#include "xeen/screen.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+const int MAP_GRID_PRIOR_INDEX[] = { 0, 0, 0, 0, 1, 2, 3, 4, 0 };
+
+const int MAP_GRID_PRIOR_DIRECTION[] = { 0, 1, 2, 3, 1, 2, 3, 0, 0 };
+
+const int MAP_GRID_PRIOR_INDEX2[] = { 0, 0, 0, 0, 2, 3, 4, 1, 0 };
+
+const int MAP_GRID_PRIOR_DIRECTION2[] = { 0, 1, 2, 3, 0, 1, 2, 3, 0 };
+
+MonsterStruct::MonsterStruct() {
+ _experience = 0;
+ _hp = 0;
+ _accuracy = 0;
+ _speed = 0;
+ _numberOfAttacks = 0;
+ _hatesClass = CLASS_KNIGHT;
+ _strikes = 0;
+ _dmgPerStrike = 0;
+ _attackType = DT_PHYSICAL;
+ _specialAttack = SA_NONE;
+ _hitChance = 0;
+ _rangeAttack = 0;
+ _monsterType = MONSTER_0;
+ _fireResistence = 0;
+ _electricityResistence = 0;
+ _coldResistence = 0;
+ _poisonResistence = 0;
+ _energyResistence = 0;
+ _magicResistence = 0;
+ _phsyicalResistence = 0;
+ _field29 = 0;
+ _gold = 0;
+ _gems = 0;
+ _itemDrop = 0;
+ _flying = 0;
+ _imageNumber = 0;
+ _loopAnimation = 0;
+ _animationEffect = 0;
+ _fx = 0;
+}
+
+MonsterStruct::MonsterStruct(Common::String name, int experience, int hp, int accuracy,
+ int speed, int numberOfAttacks, CharacterClass hatesClass, int strikes,
+ int dmgPerStrike, DamageType attackType, SpecialAttack specialAttack,
+ int hitChance, int rangeAttack, MonsterType monsterType,
+ int fireResistence, int electricityResistence, int coldResistence,
+ int poisonResistence, int energyResistence, int magicResistence,
+ int phsyicalResistence, int field29, int gold, int gems, int itemDrop,
+ bool flying, int imageNumber, int loopAnimation, int animationEffect,
+ int fx, Common::String attackVoc):
+ _name(name), _experience(experience), _hp(hp), _accuracy(accuracy),
+ _speed(speed), _numberOfAttacks(numberOfAttacks), _hatesClass(hatesClass),
+ _strikes(strikes), _dmgPerStrike(dmgPerStrike), _attackType(attackType),
+ _specialAttack(specialAttack), _hitChance(hitChance), _rangeAttack(rangeAttack),
+ _monsterType(monsterType), _fireResistence(fireResistence),
+ _electricityResistence(electricityResistence), _coldResistence(coldResistence),
+ _poisonResistence(poisonResistence), _energyResistence(energyResistence),
+ _magicResistence(magicResistence), _phsyicalResistence(phsyicalResistence),
+ _field29(field29), _gold(gold), _gems(gems), _itemDrop(itemDrop),
+ _flying(flying), _imageNumber(imageNumber), _loopAnimation(loopAnimation),
+ _animationEffect(animationEffect), _fx(fx), _attackVoc(attackVoc) {
+}
+
+void MonsterStruct::synchronize(Common::SeekableReadStream &s) {
+ char name[16];
+ s.read(name, 16);
+ name[15] = '\0';
+ _name = Common::String(name);
+
+ _experience = s.readUint32LE();
+ _hp = s.readUint16LE();
+ _accuracy = s.readByte();
+ _speed = s.readByte();
+ _numberOfAttacks = s.readByte();
+ _hatesClass = (CharacterClass)s.readByte();
+ _strikes = s.readUint16LE();
+ _dmgPerStrike = s.readByte();
+ _attackType = (DamageType)s.readByte();
+ _specialAttack = (SpecialAttack)s.readByte();
+ _hitChance = s.readByte();
+ _rangeAttack = s.readByte();
+ _monsterType = (MonsterType)s.readByte();
+ _fireResistence = s.readByte();
+ _electricityResistence = s.readByte();
+ _coldResistence = s.readByte();
+ _poisonResistence = s.readByte();
+ _energyResistence = s.readByte();
+ _magicResistence = s.readByte();
+ _phsyicalResistence = s.readByte();
+ _field29 = s.readByte();
+ _gold = s.readUint16LE();
+ _gems = s.readByte();
+ _itemDrop = s.readByte();
+ _flying = s.readByte() != 0;
+ _imageNumber = s.readByte();
+ _loopAnimation = s.readByte();
+ _animationEffect = s.readByte();
+ _fx = s.readByte();
+
+ char attackVoc[10];
+ s.read(attackVoc, 9);
+ attackVoc[9] = '\0';
+ _attackVoc = Common::String(attackVoc);
+}
+
+MonsterData::MonsterData() {
+ push_back(MonsterStruct("", 0, 0, 0, 0, 0, CLASS_KNIGHT, 1, 1, DT_PHYSICAL,
+ SA_NONE, 1, 0, MONSTER_0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, false, 0, 0, 0, 100, "Slime"));
+ push_back(MonsterStruct("Whirlwind", 250000, 1000, 10, 250, 1, CLASS_15, 5,
+ 100, DT_PHYSICAL, SA_CONFUSE, 250, 0, MONSTER_0, 100,
+ 100, 100, 100, 0, 0, 100, 0, 0, 0, 0, false, 1, 0, 0, 176,
+ "airmon"));
+ push_back(MonsterStruct("Annihilator", 1000000, 1500, 40, 200, 12, CLASS_16, 5,
+ 50, DT_ENERGY, SA_NONE, 1, 1, MONSTER_0, 80, 80, 100,
+ 100, 0, 0, 80, 0, 0, 0, 0, false, 2, 0, 0, 102, "alien1"));
+ push_back(MonsterStruct("Autobot", 1000000, 2500, 100, 200, 2, CLASS_16, 5,
+ 100, DT_ENERGY, SA_NONE, 1, 0, MONSTER_0, 50, 50, 100,
+ 100, 0, 0, 50, 0, 0, 0, 0, true, 3, 0, 0, 101, "alien2"));
+ push_back(MonsterStruct("Sewer Stalker", 50000, 250, 30, 25, 1, CLASS_16, 3,
+ 100, DT_PHYSICAL, SA_NONE, 50, 0, MONSTER_ANIMAL, 0,
+ 0, 50, 50, 0, 0, 0, 0, 0, 0, 0, false, 4, 0, 0, 113,
+ "iguana"));
+ push_back(MonsterStruct("Armadillo", 60000, 800, 50, 15, 1, CLASS_16, 100, 6,
+ DT_PHYSICAL, SA_BREAKWEAPON, 60, 0, MONSTER_ANIMAL,
+ 50, 0, 80, 80, 50, 0, 50, 0, 0, 0, 0, false, 5, 1, 0, 113,
+ "unnh"));
+ push_back(MonsterStruct("Barbarian", 5000, 50, 5, 40, 3, CLASS_SORCERER, 1, 20,
+ DT_PHYSICAL, SA_NONE, 20, 1, MONSTER_HUMANOID, 0, 0,
+ 0, 0, 0, 0, 0, 0, 100, 0, 3, false, 6, 0, 0, 100,
+ "barbarch"));
+ push_back(MonsterStruct("Electrapede", 10000, 200, 10, 50, 1, CLASS_PALADIN,
+ 50, 1, DT_ELECTRICAL, SA_PARALYZE, 1, 0,
+ MONSTER_INSECT, 50, 100, 50, 50, 50, 0, 0, 0, 0, 0, 0,
+ false, 7, 1, 0, 107, "centi"));
+ push_back(MonsterStruct("Cleric of Mok", 30000, 125, 10, 40, 1, CLASS_CLERIC,
+ 250, 1, DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_HUMANOID,
+ 10, 100, 10, 10, 10, 10, 0, 0, 0, 10, 0, false, 8, 0, 0,
+ 117, "cleric"));
+ push_back(MonsterStruct("Mok Heretic", 50000, 150, 12, 50, 1, CLASS_CLERIC,
+ 500, 1, DT_MAGICAL, SA_NONE, 1, 1, MONSTER_HUMANOID, 20, 50,
+ 20, 20, 20, 30, 0, 0, 0, 25, 4, false, 8, 0, 0, 117,
+ "cleric"));
+ push_back(MonsterStruct("Mantis Ant", 40000, 300, 30, 40, 2, CLASS_16, 2, 100,
+ DT_PHYSICAL, SA_POISON, 30, 0, MONSTER_INSECT, 0, 0,
+ 0, 100, 0, 0, 30, 0, 0, 0, 0, false, 10, 0, 0, 104,
+ "spell001"));
+ push_back(MonsterStruct("Cloud Dragon", 500000, 2000, 40, 150, 1, CLASS_15,
+ 600, 1, DT_COLD, SA_NONE, 1, 1, MONSTER_DRAGON, 0, 50,
+ 100, 100, 50, 25, 50, 0, 0, 10, 0, false, 11, 0, 0, 140,
+ "tiger1"));
+ push_back(MonsterStruct("Phase Dragon", 2000000, 4000, 80, 200, 1, CLASS_15,
+ 750, 1, DT_COLD, SA_NONE, 1, 1, MONSTER_DRAGON, 0, 50,
+ 100, 100, 80, 50, 50, 0, 0, 20, 0, false, 11, 0, 10, 140,
+ "Begger"));
+ push_back(MonsterStruct("Green Dragon", 500000, 2500, 50, 150, 1, CLASS_15,
+ 500, 1, DT_FIRE, SA_NONE, 1, 1, MONSTER_DRAGON, 100,
+ 50, 0, 100, 50, 25, 50, 0, 0, 10, 0, false, 13, 0, 0, 140,
+ "tiger1"));
+ push_back(MonsterStruct("Energy Dragon", 2000000, 5000, 100, 250, 1, CLASS_15,
+ 1000, 1, DT_ENERGY, SA_NONE, 1, 1, MONSTER_DRAGON, 80,
+ 80, 60, 100, 100, 30, 50, 0, 0, 20, 0, false, 13, 0, 7,
+ 140, "begger"));
+ push_back(MonsterStruct("Dragon Mummy", 2000000, 3000, 30, 100, 1,
+ CLASS_CLERIC, 2000, 2, DT_PHYSICAL, SA_DISEASE, 200,
+ 0, MONSTER_DRAGON, 0, 80, 100, 100, 0, 10, 90, 0, 0, 0,
+ 0, false, 15, 0, 0, 140, "dragmum"));
+ push_back(MonsterStruct("Scraps", 2000000, 3000, 30, 100, 1, CLASS_16, 2000, 2,
+ DT_PHYSICAL, SA_NONE, 200, 0, MONSTER_DRAGON, 0, 80,
+ 100, 100, 0, 10, 90, 0, 0, 0, 0, false, 15, 0, 0, 140,
+ "dragmum"));
+ push_back(MonsterStruct("Earth Blaster", 250000, 1000, 10, 100, 1, CLASS_15, 5,
+ 100, DT_PHYSICAL, SA_NONE, 200, 0, MONSTER_0, 100, 90,
+ 90, 100, 0, 0, 90, 0, 0, 0, 0, false, 17, 0, 0, 100,
+ "earthmon"));
+ push_back(MonsterStruct("Beholder Bat", 10000, 75, 15, 80, 1, CLASS_15, 5, 5,
+ DT_FIRE, SA_NONE, 1, 0, MONSTER_0, 100, 50, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, true, 18, 0, 0, 120, "eyeball"));
+ push_back(MonsterStruct("Fire Blower", 250000, 1000, 20, 60, 1, CLASS_15, 5,
+ 100, DT_FIRE, SA_NONE, 1, 0, MONSTER_0, 100, 50, 0,
+ 100, 50, 0, 50, 0, 0, 0, 0, false, 19, 0, 0, 110, "fire"));
+ push_back(MonsterStruct("Hell Hornet", 50000, 250, 30, 50, 2, CLASS_DRUID, 2,
+ 250, DT_POISON, SA_WEAKEN, 1, 0, MONSTER_INSECT, 50,
+ 50, 50, 100, 50, 0, 50, 0, 0, 0, 0, true, 20, 0, 0, 123,
+ "insect"));
+ push_back(MonsterStruct("Gargoyle", 30000, 150, 35, 30, 2, CLASS_16, 5, 50,
+ DT_PHYSICAL, SA_NONE, 60, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 20, 0, 0, 0, 0, 0, false, 21, 0, 10, 100, "gargrwl"));
+ push_back(MonsterStruct("Giant", 100000, 500, 25, 45, 2, CLASS_16, 100, 5,
+ DT_PHYSICAL, SA_UNCONSCIOUS, 100, 0, MONSTER_0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1000, 0, 5, false, 22, 0, 0, 100,
+ "giant"));
+ push_back(MonsterStruct("Goblin", 1000, 10, 5, 30, 2, CLASS_16, 2, 6,
+ DT_PHYSICAL, SA_NONE, 1, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, false, 25, 0, 0, 131, "gremlin"));
+ push_back(MonsterStruct("Onyx Golem", 1000000, 10000, 50, 100, 1, CLASS_15, 2,
+ 250, DT_MAGICAL, SA_DRAINSP, 1, 0, MONSTER_GOLEM, 100, 100,
+ 100, 100, 100, 100, 50, 0, 0, 100, 0, true, 24, 0, 10,
+ 100, "golem"));
+ push_back(MonsterStruct("Gremlin", 2000, 20, 7, 35, 2, CLASS_16, 2, 10,
+ DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, false, 26, 0, 0, 101, "gremlink"));
+ push_back(MonsterStruct("Gremlin Guard", 3000, 50, 10, 35, 2, CLASS_16, 6, 5,
+ DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, false, 26, 0, 0, 101, "gremlink"));
+ push_back(MonsterStruct("Griffin", 60000, 800, 35, 150, 2, CLASS_KNIGHT, 50, 6,
+ DT_PHYSICAL, SA_NONE, 150, 0, MONSTER_ANIMAL, 0, 0, 0,
+ 0, 0, 80, 0, 0, 0, 0, 0, false, 27, 0, 0, 120, "screech"));
+ push_back(MonsterStruct("Gamma Gazer", 1000000, 5000, 60, 200, 7, CLASS_16, 10,
+ 20, DT_ENERGY, SA_NONE, 1, 0, MONSTER_0, 100, 100, 0,
+ 100, 100, 0, 60, 0, 0, 0, 0, false, 28, 0, 0, 140, "hydra"));
+ push_back(MonsterStruct("Iguanasaurus", 100000, 2500, 20, 30, 1, CLASS_16, 10,
+ 50, DT_PHYSICAL, SA_INSANE, 150, 0, MONSTER_ANIMAL, 50,
+ 50, 50, 50, 50, 0, 20, 0, 0, 0, 0, false, 29, 0, 0, 113,
+ "iguana"));
+ push_back(MonsterStruct("Slayer Knight", 50000, 500, 30, 50, 1, CLASS_PALADIN,
+ 2, 250, DT_PHYSICAL, SA_NONE, 100, 0, MONSTER_HUMANOID,
+ 50, 50, 50, 50, 50, 0, 0, 0, 50, 0, 5, false, 30, 0, 0,
+ 141, "knight"));
+ push_back(MonsterStruct("Death Knight", 100000, 750, 50, 80, 2, CLASS_PALADIN,
+ 2, 250, DT_PHYSICAL, SA_NONE, 150, 0, MONSTER_HUMANOID,
+ 50, 50, 50, 50, 50, 10, 0, 0, 100, 0, 6, false, 30, 0, 0,
+ 141, "knight"));
+ push_back(MonsterStruct("Lava Dweller", 500000, 1500, 30, 40, 1, CLASS_15, 5,
+ 100, DT_FIRE, SA_NONE, 1, 0, MONSTER_0, 100, 100, 0,
+ 100, 50, 0, 50, 0, 0, 0, 0, false, 19, 0, 0, 110, "fire"));
+ push_back(MonsterStruct("Lava Roach", 50000, 500, 20, 70, 1, CLASS_16, 5, 50,
+ DT_FIRE, SA_NONE, 1, 0, MONSTER_INSECT, 100, 100, 0,
+ 100, 0, 0, 0, 0, 0, 0, 0, false, 33, 0, 0, 131, "Phantom"));
+ push_back(MonsterStruct("Power Lich", 200000, 500, 20, 60, 1, CLASS_15, 10, 10,
+ DT_MAGICAL, SA_UNCONSCIOUS, 1, 1, MONSTER_UNDEAD, 0, 0, 0, 0,
+ 0, 80, 70, 0, 0, 0, 0, true, 34, 0, 0, 141, "lich"));
+ push_back(MonsterStruct("Mystic Mage", 100000, 200, 20, 70, 1, CLASS_15, 10,
+ 20, DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_0, 50, 100,
+ 50, 50, 50, 30, 0, 0, 0, 50, 0, true, 35, 0, 0, 163,
+ "monsterb"));
+ push_back(MonsterStruct("Magic Mage", 200000, 300, 25, 80, 1, CLASS_15, 10, 30,
+ DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_0, 50, 100, 50,
+ 50, 50, 50, 0, 0, 0, 75, 0, true, 35, 0, 0, 163,
+ "monsterb"));
+ push_back(MonsterStruct("Minotaur", 250000, 3000, 80, 120, 1, CLASS_16, 100, 4,
+ DT_PHYSICAL, SA_AGING, 150, 0, MONSTER_0, 0, 0, 10, 0,
+ 0, 50, 60, 0, 0, 0, 0, false, 37, 0, 0, 141, "stonegol"));
+ push_back(MonsterStruct("Gorgon", 250000, 4000, 90, 100, 1, CLASS_16, 100, 3,
+ DT_PHYSICAL, SA_STONE, 100, 0, MONSTER_0, 0, 0, 0, 0,
+ 0, 60, 70, 0, 0, 0, 0, false, 37, 0, 0, 141, "stonegol"));
+ push_back(MonsterStruct("Higher Mummy", 100000, 400, 20, 60, 1, CLASS_CLERIC,
+ 10, 40, DT_PHYSICAL, SA_CURSEITEM, 100, 0,
+ MONSTER_UNDEAD, 0, 50, 50, 100, 50, 20, 75, 0, 0, 0, 0,
+ false, 39, 0, 0, 141, "mummy"));
+ push_back(MonsterStruct("Orc Guard", 5000, 60, 10, 20, 1, CLASS_12, 3, 10,
+ DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_HUMANOID, 0, 0,
+ 0, 0, 0, 0, 0, 0, 50, 0, 2, false, 40, 0, 0, 125, "orc"));
+ push_back(MonsterStruct("Octopod", 250000, 2500, 40, 80, 1, CLASS_15, 2, 100,
+ DT_POISON, SA_POISON, 1, 0, MONSTER_ANIMAL, 0, 0, 50,
+ 100, 0, 0, 0, 0, 0, 0, 0, true, 41, 0, 0, 101, "photon"));
+ push_back(MonsterStruct("Ogre", 10000, 100, 15, 30, 1, CLASS_16, 4, 10,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 100, 0, 0, false, 42, 0, 0, 136, "ogre"));
+ push_back(MonsterStruct("Orc Shaman", 10000, 50, 15, 30, 1, CLASS_15, 5, 5,
+ DT_COLD, SA_SLEEP, 1, 1, MONSTER_HUMANOID, 0, 0, 0, 0,
+ 0, 10, 0, 0, 75, 10, 2, false, 43, 0, 0, 125, "fx7"));
+ push_back(MonsterStruct("Sabertooth", 10000, 100, 20, 60, 3, CLASS_16, 5, 10,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_ANIMAL, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, false, 44, 1, 0, 101, "saber"));
+ push_back(MonsterStruct("Sand Flower", 10000, 100, 10, 50, 5, CLASS_16, 5, 5,
+ DT_PHYSICAL, SA_INLOVE, 50, 0, MONSTER_0, 0, 0, 0, 0,
+ 0, 50, 50, 0, 0, 0, 0, false, 45, 0, 0, 106, "sand"));
+ push_back(MonsterStruct("Killer Cobra", 25000, 1000, 25, 100, 1, CLASS_16, 2,
+ 100, DT_PHYSICAL, SA_AGING, 30, 0, MONSTER_ANIMAL, 0,
+ 0, 0, 100, 0, 50, 0, 0, 0, 0, 0, false, 46, 0, 0, 100,
+ "hiss"));
+ push_back(MonsterStruct("Sewer Rat", 2000, 40, 5, 35, 1, CLASS_16, 3, 10,
+ DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_ANIMAL, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, false, 47, 0, 0, 136, "rat"));
+ push_back(MonsterStruct("Sewer Slug", 1000, 25, 2, 25, 1, CLASS_16, 2, 10,
+ DT_PHYSICAL, SA_NONE, 5, 0, MONSTER_INSECT, 0, 0, 0,
+ 100, 0, 0, 0, 0, 0, 0, 0, false, 48, 0, 0, 111, "zombie"));
+ push_back(MonsterStruct("Skeletal Lich", 500000, 2000, 30, 200, 1,
+ CLASS_SORCERER, 1000, 1, DT_ENERGY, SA_ERADICATE, 1, 1,
+ MONSTER_UNDEAD, 80, 70, 80, 100, 100, 50, 50, 0, 0, 0,
+ 0, false, 49, 0, 0, 140, "elecbolt"));
+ push_back(MonsterStruct("Enchantress", 40000, 100, 25, 60, 1, CLASS_CLERIC, 3,
+ 150, DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_HUMANOID,
+ 10, 100, 10, 10, 10, 20, 0, 0, 0, 20, 0, false, 50, 0, 0,
+ 163, "disint"));
+ push_back(MonsterStruct("Sorceress", 80000, 200, 30, 80, 1, CLASS_15, 2, 50,
+ DT_MAGICAL, SA_NONE, 1, 1, MONSTER_HUMANOID, 10, 20, 10, 10,
+ 10, 80, 0, 0, 0, 50, 5, false, 50, 0, 0, 163, "disint"));
+ push_back(MonsterStruct("Arachnoid", 4000, 50, 10, 40, 1, CLASS_16, 3, 5,
+ DT_POISON, SA_POISON, 1, 0, MONSTER_INSECT, 0, 0, 0,
+ 100, 0, 0, 0, 0, 0, 0, 0, false, 52, 0, 0, 104, "web"));
+ push_back(MonsterStruct("Medusa Sprite", 5000, 30, 5, 30, 1, CLASS_RANGER, 3,
+ 3, DT_PHYSICAL, SA_STONE, 10, 0, MONSTER_0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, true, 53, 0, 0, 42, "hiss"));
+ push_back(MonsterStruct("Rogue", 5000, 50, 10, 30, 1, CLASS_ROBBER, 1, 60,
+ DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_HUMANOID, 0, 0,
+ 0, 0, 0, 0, 0, 0, 70, 0, 0, false, 54, 0, 0, 100, "thief"));
+ push_back(MonsterStruct("Thief", 10000, 100, 15, 40, 1, CLASS_ROBBER, 1, 100,
+ DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_HUMANOID, 0, 0,
+ 0, 0, 0, 0, 0, 0, 200, 0, 0, false, 54, 0, 0, 100,
+ "thief"));
+ push_back(MonsterStruct("Troll Grunt", 10000, 100, 5, 50, 1, CLASS_16, 2, 25,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 50, 50, 50,
+ 50, 0, 0, 0, 0, 0, 0, 0, false, 56, 0, 0, 136, "troll"));
+ push_back(MonsterStruct("Vampire", 200000, 400, 30, 80, 1, CLASS_CLERIC, 10,
+ 10, DT_PHYSICAL, SA_WEAKEN, 100, 0, MONSTER_UNDEAD, 50,
+ 50, 50, 50, 50, 50, 50, 0, 0, 0, 0, false, 57, 0, 0, 42,
+ "vamp"));
+ push_back(MonsterStruct("Vampire Lord", 300000, 500, 35, 100, 1, CLASS_CLERIC,
+ 10, 30, DT_PHYSICAL, SA_SLEEP, 120, 0, MONSTER_UNDEAD,
+ 50, 50, 50, 50, 50, 50, 70, 0, 0, 0, 0, false, 58, 0, 0,
+ 42, "vamp"));
+ push_back(MonsterStruct("Vulture Roc", 200000, 2500, 50, 150, 1, CLASS_16, 5,
+ 60, DT_PHYSICAL, SA_NONE, 100, 0, MONSTER_ANIMAL, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, true, 59, 0, 0, 120, "vulture"));
+ push_back(MonsterStruct("Sewer Hag", 50000, 75, 10, 40, 1, CLASS_PALADIN, 10,
+ 25, DT_ELECTRICAL, SA_INSANE, 1, 1, MONSTER_HUMANOID,
+ 0, 100, 0, 100, 0, 20, 0, 0, 0, 10, 0, false, 62, 0, 0,
+ 108, "elecspel"));
+ push_back(MonsterStruct("Tidal Terror", 500000, 1000, 10, 200, 1, CLASS_15, 5,
+ 100, DT_COLD, SA_NONE, 1, 0, MONSTER_0, 100, 50, 50,
+ 100, 50, 0, 100, 0, 0, 0, 0, true, 61, 0, 0, 101,
+ "splash3"));
+ push_back(MonsterStruct("Witch", 80000, 150, 15, 70, 1, CLASS_15, 10, 10,
+ DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_HUMANOID, 0, 100,
+ 0, 20, 0, 20, 0, 0, 0, 10, 0, false, 63, 0, 0, 114,
+ "elecspel"));
+ push_back(MonsterStruct("Coven Leader", 120000, 250, 20, 100, 1, CLASS_15, 10,
+ 15, DT_ENERGY, SA_DRAINSP, 1, 1, MONSTER_HUMANOID, 10,
+ 100, 0, 50, 100, 50, 0, 0, 0, 20, 6, false, 63, 0, 10, 114,
+ "elecspel"));
+ push_back(MonsterStruct("Master Wizard", 120000, 500, 25, 150, 2, CLASS_KNIGHT,
+ 10, 40, DT_FIRE, SA_NONE, 1, 1, MONSTER_HUMANOID, 100,
+ 50, 50, 50, 50, 50, 0, 0, 0, 50, 0, false, 64, 0, 0, 163,
+ "boltelec"));
+ push_back(MonsterStruct("Wizard", 60000, 250, 20, 125, 1, CLASS_PALADIN, 10,
+ 25, DT_MAGICAL, SA_NONE, 1, 1, MONSTER_HUMANOID, 50, 30, 30,
+ 30, 30, 30, 0, 0, 0, 20, 0, false, 65, 0, 0, 163, "wizard"));
+ push_back(MonsterStruct("Dark Wolf", 10000, 70, 10, 70, 3, CLASS_16, 3, 8,
+ DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_ANIMAL, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, false, 66, 1, 0, 100, "wolf"));
+ push_back(MonsterStruct("Screamer", 500000, 3000, 50, 200, 1, CLASS_15, 10, 20,
+ DT_POISON, SA_POISON, 1, 0, MONSTER_0, 0, 0, 0, 100, 0,
+ 0, 60, 0, 0, 0, 0, false, 67, 0, 0, 110, "dragon"));
+ push_back(MonsterStruct("Cult Leader", 100000, 100, 20, 60, 1, CLASS_15, 10,
+ 10, DT_ENERGY, SA_NONE, 1, 1, MONSTER_HUMANOID, 50, 50,
+ 50, 50, 100, 50, 0, 0, 0, 100, 6, false, 8, 0, 0, 100,
+ "cleric"));
+ push_back(MonsterStruct("Mega Dragon", 100000000, 64000, 100, 200, 1, CLASS_15,
+ 10, 200, DT_ENERGY, SA_ERADICATE, 1, 1, MONSTER_DRAGON,
+ 100, 100, 100, 100, 100, 100, 90, 0, 0, 232, 0, false, 11,
+ 0, 7, 100, "tiger1"));
+ push_back(MonsterStruct("Gettlewaithe", 5000, 100, 15, 35, 2, CLASS_16, 5, 5,
+ DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 2000, 0, 5, false, 25, 0, 0, 100, "gremlin"));
+ push_back(MonsterStruct("Doom Knight", 500000, 1000, 50, 100, 4, CLASS_PALADIN,
+ 2, 250, DT_PHYSICAL, SA_DEATH, 150, 0,
+ MONSTER_HUMANOID, 80, 80, 80, 80, 80, 20, 0, 0, 200,
+ 0, 7, false, 30, 0, 10, 100, "knight"));
+ push_back(MonsterStruct("Sandro", 200000, 1000, 20, 75, 1, CLASS_15, 10, 10,
+ DT_MAGICAL, SA_DEATH, 1, 1, MONSTER_UNDEAD, 0, 0, 0, 0, 0,
+ 90, 80, 0, 0, 100, 7, true, 34, 0, 10, 100, "lich"));
+ push_back(MonsterStruct("Mega Mage", 500000, 500, 35, 100, 1, CLASS_15, 10, 40,
+ DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_0, 80, 100, 80,
+ 80, 80, 80, 0, 0, 0, 100, 6, true, 35, 0, 11, 100,
+ "monsterb"));
+ push_back(MonsterStruct("Orc Elite", 15000, 200, 15, 40, 2, CLASS_12, 5, 10,
+ DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_HUMANOID, 0, 0,
+ 0, 0, 0, 0, 0, 0, 100, 0, 3, false, 40, 0, 0, 100, "orc"));
+ push_back(MonsterStruct("Shaalth", 20000, 300, 15, 50, 1, CLASS_15, 5, 10,
+ DT_COLD, SA_SLEEP, 1, 0, MONSTER_HUMANOID, 0, 0, 0, 0,
+ 0, 20, 0, 0, 1000, 50, 5, false, 43, 0, 10, 100, "fx7"));
+ push_back(MonsterStruct("Rooka", 5000, 60, 5, 40, 1, CLASS_16, 3, 10,
+ DT_PHYSICAL, SA_DISEASE, 15, 0, MONSTER_ANIMAL, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 10, 4, false, 47, 0, 0, 100, "rat"));
+ push_back(MonsterStruct("Morgana", 200000, 300, 35, 100, 1, CLASS_15, 2, 60,
+ DT_ENERGY, SA_PARALYZE, 1, 1, MONSTER_HUMANOID, 50, 50,
+ 50, 50, 100, 80, 0, 0, 0, 100, 6, false, 50, 0, 10, 100,
+ "disint"));
+ push_back(MonsterStruct("Master Thief", 20000, 100, 20, 50, 1, CLASS_ROBBER, 1,
+ 250, DT_PHYSICAL, SA_NONE, 40, 0, MONSTER_HUMANOID, 0,
+ 0, 0, 0, 0, 0, 0, 0, 250, 20, 4, false, 54, 0, 14, 100,
+ "thief"));
+ push_back(MonsterStruct("Royal Vampire", 400000, 750, 40, 125, 1, CLASS_CLERIC,
+ 10, 50, DT_PHYSICAL, SA_CURSEITEM, 120, 0,
+ MONSTER_UNDEAD, 50, 50, 50, 50, 50, 50, 65, 0, 0, 0, 0,
+ false, 57, 0, 0, 100, "vamp"));
+ push_back(MonsterStruct("Ct. Blackfang", 2000000, 1500, 50, 150, 1,
+ CLASS_CLERIC, 10, 100, DT_PHYSICAL, SA_DEATH, 120, 0,
+ MONSTER_UNDEAD, 75, 75, 75, 75, 75, 75, 75, 0, 0, 0, 0,
+ false, 58, 0, 10, 100, "vamp"));
+ push_back(MonsterStruct("Troll Guard", 15000, 200, 10, 60, 1, CLASS_16, 2, 35,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 50, 50, 50,
+ 50, 0, 0, 0, 0, 0, 0, 0, false, 56, 0, 0, 100, "troll"));
+ push_back(MonsterStruct("Troll Chief", 20000, 300, 15, 65, 1, CLASS_16, 2, 50,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 50, 50, 50,
+ 50, 0, 0, 0, 0, 0, 0, 0, false, 56, 0, 0, 100, "troll"));
+ push_back(MonsterStruct("Hobstadt", 25000, 400, 20, 70, 1, CLASS_16, 2, 50,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 50, 50, 50,
+ 50, 0, 0, 0, 0, 1000, 0, 4, false, 56, 0, 0, 100, "troll"));
+ push_back(MonsterStruct("Graalg", 20000, 200, 15, 50, 1, CLASS_16, 5, 10,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1000, 0, 5, false, 42, 0, 0, 100, "ogre"));
+ push_back(MonsterStruct("Vampire King", 3000000, 10000, 60, 200, 1,
+ CLASS_CLERIC, 10, 250, DT_PHYSICAL, SA_ERADICATE, 150,
+ 0, MONSTER_UNDEAD, 80, 80, 80, 80, 80, 80, 90, 0, 0, 0,
+ 0, false, 58, 0, 0, 100, "vamp"));
+ push_back(MonsterStruct("Valio", 60000, 150, 15, 60, 1, CLASS_PALADIN, 10, 25,
+ DT_MAGICAL, SA_NONE, 1, 0, MONSTER_HUMANOID, 50, 30, 30, 30,
+ 40, 30, 0, 0, 0, 0, 0, false, 65, 0, 0, 100, "wizard"));
+ push_back(MonsterStruct("Sky Golem", 200000, 1000, 50, 100, 1, CLASS_15, 2,
+ 100, DT_COLD, SA_NONE, 1, 1, MONSTER_GOLEM, 50, 50,
+ 100, 50, 50, 50, 50, 0, 0, 0, 0, true, 24, 0, 0, 100,
+ "golem"));
+ push_back(MonsterStruct("Gurodel", 100000, 750, 30, 60, 2, CLASS_16, 100, 6,
+ DT_PHYSICAL, SA_UNCONSCIOUS, 110, 0, MONSTER_0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 5000, 0, 6, false, 22, 0, 0, 100,
+ "giant"));
+ push_back(MonsterStruct("Yog", 25000, 100, 5, 60, 1, CLASS_SORCERER, 1, 30,
+ DT_PHYSICAL, SA_NONE, 25, 0, MONSTER_HUMANOID, 0, 0,
+ 0, 0, 0, 0, 0, 0, 200, 0, 4, false, 6, 0, 10, 100,
+ "barbarch"));
+ push_back(MonsterStruct("Sharla", 10000, 50, 5, 50, 1, CLASS_RANGER, 3, 4,
+ DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, true, 53, 0, 0, 100, "hiss"));
+ push_back(MonsterStruct("Ghost Mummy", 500000, 500, 35, 175, 1, CLASS_CLERIC,
+ 200, 5, DT_PHYSICAL, SA_AGING, 150, 0, MONSTER_UNDEAD,
+ 0, 60, 80, 80, 80, 50, 80, 0, 0, 0, 0, false, 40, 0, 6,
+ 100, "orc"));
+ push_back(MonsterStruct("Phase Mummy", 500000, 500, 35, 175, 1, CLASS_CLERIC,
+ 200, 6, DT_PHYSICAL, SA_DRAINSP, 150, 0,
+ MONSTER_UNDEAD, 0, 70, 80, 80, 80, 60, 85, 0, 0, 0, 0,
+ false, 39, 0, 7, 100, "mummy"));
+ push_back(MonsterStruct("Xenoc", 250000, 700, 35, 175, 1, CLASS_15, 10, 50,
+ DT_ENERGY, SA_NONE, 1, 0, MONSTER_HUMANOID, 50, 50, 50,
+ 50, 100, 50, 0, 0, 0, 100, 6, false, 64, 0, 0, 100,
+ "boltelec"));
+ push_back(MonsterStruct("Barkman", 4000000, 40000, 25, 100, 3, CLASS_16, 250,
+ 1, DT_FIRE, SA_NONE, 1, 0, MONSTER_0, 100, 50, 0, 100,
+ 0, 0, 0, 0, 0, 0, 6, false, 19, 0, 11, 100, "fire"));
+}
+
+void MonsterData::load(const Common::String &name) {
+ File f(name);
+ synchronize(f);
+}
+
+void MonsterData::synchronize(Common::SeekableReadStream &s) {
+ clear();
+
+ MonsterStruct spr;
+ while (!s.eos()) {
+ spr.synchronize(s);
+ push_back(spr);
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+SurroundingMazes::SurroundingMazes() {
+ clear();
+}
+
+void SurroundingMazes::clear() {
+ _north = 0;
+ _east = 0;
+ _south = 0;
+ _west = 0;
+}
+
+void SurroundingMazes::synchronize(Common::SeekableReadStream &s) {
+ _north = s.readUint16LE();
+ _east = s.readUint16LE();
+ _south = s.readUint16LE();
+ _west = s.readUint16LE();
+}
+
+int &SurroundingMazes::operator[](int idx) {
+ switch (idx) {
+ case DIR_NORTH:
+ return _north;
+ case DIR_EAST:
+ return _east;
+ case DIR_SOUTH:
+ return _south;
+ default:
+ return _west;
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+MazeDifficulties::MazeDifficulties() {
+ _unlockDoor = 0;
+ _unlockBox = 0;
+ _bashDoor = 0;
+ _bashGrate = 0;
+ _bashWall = 0;
+}
+
+void MazeDifficulties::synchronize(Common::SeekableReadStream &s) {
+ _wallNoPass = s.readByte();
+ _surfaceNoPass = s.readByte();
+ _unlockDoor = s.readByte();
+ _unlockBox = s.readByte();
+ _bashDoor = s.readByte();
+ _bashGrate = s.readByte();
+ _bashWall = s.readByte();
+ _chance2Run = s.readByte();
+}
+
+/*------------------------------------------------------------------------*/
+
+MazeData::MazeData() {
+ clear();
+}
+
+void MazeData::clear() {
+ for (int y = 0; y < MAP_HEIGHT; ++y) {
+ for (int x = 0; x < MAP_WIDTH; ++x)
+ _wallData[y][x]._data = 0;
+ Common::fill(&_seenTiles[y][0], &_seenTiles[y][MAP_WIDTH], false);
+ Common::fill(&_steppedOnTiles[y][0], &_steppedOnTiles[y][MAP_WIDTH], false);
+ _wallTypes[y] = 0;
+ _surfaceTypes[y] = 0;
+ }
+ _mazeNumber = 0;
+ _surroundingMazes.clear();
+ _mazeFlags = _mazeFlags2 = 0;
+ _floorType = 0;
+ _trapDamage = 0;
+ _wallKind = 0;
+ _tavernTips = 0;
+}
+
+void MazeData::synchronize(Common::SeekableReadStream &s) {
+ for (int y = 0; y < MAP_HEIGHT; ++y) {
+ for (int x = 0; x < MAP_WIDTH; ++x)
+ _wallData[y][x]._data = s.readUint16LE();
+ }
+ for (int y = 0; y < MAP_HEIGHT; ++y) {
+ for (int x = 0; x < MAP_WIDTH; ++x) {
+ byte b = s.readByte();
+ _cells[y][x]._surfaceId = b & 7;
+ _cells[y][x]._flags = b & 0xF8;
+ }
+ }
+
+ _mazeNumber = s.readUint16LE();
+ _surroundingMazes.synchronize(s);
+ _mazeFlags = s.readUint16LE();
+ _mazeFlags2 = s.readUint16LE();
+
+ for (int i = 0; i < 16; ++i)
+ _wallTypes[i] = s.readByte();
+ for (int i = 0; i < 16; ++i)
+ _surfaceTypes[i] = s.readByte();
+
+ _floorType = s.readByte();
+ _runPosition.x = s.readByte();
+ _difficulties.synchronize(s);
+ _runPosition.y = s.readByte();
+ _trapDamage = s.readByte();
+ _wallKind = s.readByte();
+ _tavernTips = s.readByte();
+
+ Common::Serializer ser(&s, nullptr);
+ for (int y = 0; y < MAP_HEIGHT; ++y)
+ SavesManager::syncBitFlags(ser, &_seenTiles[y][0], &_seenTiles[y][MAP_WIDTH]);
+ for (int y = 0; y < MAP_HEIGHT; ++y)
+ SavesManager::syncBitFlags(ser, &_steppedOnTiles[y][0], &_steppedOnTiles[y][MAP_WIDTH]);
+}
+
+void MazeData::setAllTilesStepped() {
+ for (int y = 0; y < MAP_HEIGHT; ++y)
+ Common::fill(&_steppedOnTiles[y][0], &_steppedOnTiles[y][MAP_WIDTH], true);
+}
+
+void MazeData::clearCellSurfaces() {
+ for (int y = 0; y < MAP_HEIGHT; ++y) {
+ for (int x = 0; x < MAP_WIDTH; ++x)
+ _cells[y][x]._surfaceId = 0;
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+MobStruct::MobStruct() {
+ _id = 0;
+ _direction = DIR_NORTH;
+}
+
+bool MobStruct::synchronize(XeenSerializer &s) {
+ s.syncAsSint8(_pos.x);
+ s.syncAsSint8(_pos.y);
+ s.syncAsByte(_id);
+ s.syncAsByte(_direction);
+
+ return _id != 0xff || _pos.x != -1 || _pos.y != -1;
+}
+
+/*------------------------------------------------------------------------*/
+
+MazeObject::MazeObject() {
+ _id = 0;
+ _frame = 0;
+ _spriteId = 0;
+ _direction = DIR_NORTH;
+ _flipped = false;
+ _sprites = nullptr;
+}
+
+/*------------------------------------------------------------------------*/
+
+MazeMonster::MazeMonster() {
+ _frame = 0;
+ _id = 0;
+ _spriteId = 0;
+ _isAttacking = false;
+ _damageType = DT_PHYSICAL;
+ _field9 = 0;
+ _fieldA = 0;
+ _hp = 0;
+ _effect1 = _effect2 = 0;
+ _effect3 = 0;
+ _sprites = nullptr;
+ _attackSprites = nullptr;
+ _monsterData = nullptr;
+}
+
+int MazeMonster::getTextColor() const {
+ if (_hp == _monsterData->_hp)
+ return 15;
+ else if (_hp >= (_monsterData->_hp / 2))
+ return 9;
+ else
+ return 32;
+}
+
+/*------------------------------------------------------------------------*/
+
+MazeWallItem::MazeWallItem() {
+ _id = 0;
+ _frame = 0;
+ _spriteId = 0;
+ _direction = DIR_NORTH;
+ _sprites = nullptr;
+}
+
+/*------------------------------------------------------------------------*/
+
+MonsterObjectData::MonsterObjectData(XeenEngine *vm): _vm(vm) {
+}
+
+void MonsterObjectData::synchronize(XeenSerializer &s, MonsterData &monsterData) {
+ Common::Array<MobStruct> mobStructs;
+ MobStruct mobStruct;
+ byte b;
+
+ if (s.isLoading()) {
+ _objectSprites.clear();
+ _monsterSprites.clear();
+ _monsterAttackSprites.clear();
+ _wallItemSprites.clear();
+ _objects.clear();
+ _monsters.clear();
+ _wallItems.clear();
+ }
+
+ for (uint i = 0; i < 16; ++i) {
+ b = (i >= _objectSprites.size()) ? 0xff : _objectSprites[i]._spriteId;
+ s.syncAsByte(b);
+ if (b != 0xff)
+ _objectSprites.push_back(SpriteResourceEntry(b));
+ }
+ for (uint i = 0; i < 16; ++i) {
+ b = (i >= _monsterSprites.size()) ? 0xff : _monsterSprites[i]._spriteId;
+ s.syncAsByte(b);
+ if (b != 0xff)
+ _monsterSprites.push_back(SpriteResourceEntry(b));
+ }
+ for (uint i = 0; i < 16; ++i) {
+ b = (i >= _wallItemSprites.size()) ? 0xff : _wallItemSprites[i]._spriteId;
+ s.syncAsByte(b);
+ if (b != 0xff)
+ _wallItemSprites.push_back(SpriteResourceEntry(b));
+ }
+
+ if (s.isSaving()) {
+ // Save objects
+ for (uint i = 0; i < _objects.size(); ++i) {
+ mobStruct._pos = _objects[i]._position;
+ mobStruct._id = _objects[i]._id;
+ mobStruct._direction = _objects[i]._direction;
+ mobStruct.synchronize(s);
+ }
+ mobStruct._pos.x = mobStruct._pos.y = -1;
+ mobStruct._id = 0xff;
+ mobStruct.synchronize(s);
+
+ // Save monsters
+ for (uint i = 0; i < _monsters.size(); ++i) {
+ mobStruct._pos = _monsters[i]._position;
+ mobStruct._id = _monsters[i]._id;
+ mobStruct._direction = DIR_NORTH;
+ mobStruct.synchronize(s);
+ }
+ mobStruct._pos.x = mobStruct._pos.y = -1;
+ mobStruct._id = 0xff;
+ mobStruct.synchronize(s);
+
+ // Save wall items
+ if (_wallItems.size() == 0) {
+ MobStruct nullStruct;
+ nullStruct.synchronize(s);
+ } else {
+ for (uint i = 0; i < _wallItems.size(); ++i) {
+ mobStruct._pos = _wallItems[i]._position;
+ mobStruct._id = _wallItems[i]._id;
+ mobStruct._direction = _wallItems[i]._direction;
+ mobStruct.synchronize(s);
+ }
+ }
+ mobStruct._pos.x = mobStruct._pos.y = -1;
+ mobStruct._id = 0xff;
+ mobStruct.synchronize(s);
+
+ } else {
+ // Load monster/obbject data and merge together with sprite Ids
+ // Merge together object data
+ mobStruct.synchronize(s);
+ do {
+ MazeObject obj;
+ obj._position = mobStruct._pos;
+ obj._id = mobStruct._id;
+ obj._direction = mobStruct._direction;
+ obj._frame = 100;
+ obj._spriteId = _objectSprites[obj._id]._spriteId;
+ obj._sprites = &_objectSprites[obj._id]._sprites;
+
+ _objects.push_back(obj);
+ mobStruct.synchronize(s);
+ } while (mobStruct._id != 255 || mobStruct._pos.x != -1);
+
+ // Merge together monster data
+ mobStruct.synchronize(s);
+ do {
+ MazeMonster mon;
+ mon._position = mobStruct._pos;
+ mon._id = mobStruct._id;
+ mon._spriteId = _monsterSprites[mon._id]._spriteId;
+ mon._sprites = &_monsterSprites[mon._id]._sprites;
+ mon._attackSprites = &_monsterSprites[mon._id]._attackSprites;
+ mon._monsterData = &monsterData[mon._spriteId];
+ mon._frame = _vm->getRandomNumber(7);
+
+ MonsterStruct &md = *mon._monsterData;
+ mon._hp = md._hp;
+ mon._effect1 = mon._effect2 = md._animationEffect;
+ if (md._animationEffect)
+ mon._effect3 = _vm->getRandomNumber(7);
+
+ _monsters.push_back(mon);
+ mobStruct.synchronize(s);
+ } while (mobStruct._id != 255 || mobStruct._pos.x != -1);
+
+ // Merge together wall item data
+ mobStruct.synchronize(s);
+ do {
+ if (mobStruct._id < (int)_wallItemSprites.size()) {
+ MazeWallItem wi;
+ wi._position = mobStruct._pos;
+ wi._id = mobStruct._id;
+ wi._direction = mobStruct._direction;
+ wi._spriteId = _wallItemSprites[wi._id]._spriteId;
+ wi._sprites = &_wallItemSprites[wi._id]._sprites;
+
+ _wallItems.push_back(wi);
+ }
+
+ mobStruct.synchronize(s);
+ } while (mobStruct._id != 255 || mobStruct._pos.x != -1);
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+HeadData::HeadData() {
+ for (int y = 0; y < MAP_HEIGHT; ++y) {
+ for (int x = 0; x < MAP_WIDTH; ++x) {
+ _data[y][x]._left = _data[y][x]._right = 0;
+ }
+ }
+}
+
+void HeadData::synchronize(Common::SeekableReadStream &s) {
+ for (int y = 0; y < MAP_HEIGHT; ++y) {
+ for (int x = 0; x < MAP_WIDTH; ++x) {
+ _data[y][x]._left = s.readByte();
+ _data[y][x]._right = s.readByte();
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+void AnimationEntry::synchronize(Common::SeekableReadStream &s) {
+ for (int i = 0; i < 4; ++i)
+ _frame1._frames[i] = s.readByte();
+ for (int i = 0; i < 4; ++i)
+ _flipped._flags[i] = s.readByte() != 0;
+ for (int i = 0; i < 4; ++i)
+ _frame2._frames[i] = s.readByte();
+}
+
+void AnimationInfo::synchronize(Common::SeekableReadStream &s) {
+ AnimationEntry entry;
+
+ clear();
+ while (s.pos() < s.size()) {
+ entry.synchronize(s);
+ push_back(entry);
+ }
+}
+
+void AnimationInfo::load(const Common::String &name) {
+ File f(name);
+ synchronize(f);
+ f.close();
+}
+
+/*------------------------------------------------------------------------*/
+
+Map::Map(XeenEngine *vm) : _vm(vm), _mobData(vm) {
+ _loadDarkSide = false;
+ _sideTownPortal = 0;
+ _sideObjects = 0;
+ _sideMonsters = 0;
+ _sidePictures = 0;
+ _isOutdoors = false;
+ _mazeDataIndex = 0;
+ _currentSteppedOn = false;
+ _currentSurfaceId = 0;
+ _currentWall = 0;
+ _currentTile = 0;
+ _currentGrateUnlocked = false;
+ _currentCantRest = false;
+ _currentIsDrain = false;
+ _currentIsEvent = false;
+ _currentSky = 0;
+ _currentMonsterFlags = 0;
+}
+
+void Map::load(int mapId) {
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ IndoorDrawList &indoorList = _vm->_interface->_indoorList;
+ OutdoorDrawList &outdoorList = _vm->_interface->_outdoorList;
+
+ if (intf._falling) {
+ Window &w = screen._windows[9];
+ w.open();
+ w.writeString(OOPS);
+ } else {
+ PleaseWait::show(_vm);
+ }
+
+ _vm->_party->_stepped = true;
+ _vm->_party->_mazeId = mapId;
+ _vm->_events->clearEvents();
+
+ _sideObjects = 1;
+ _sideMonsters = 1;
+ _sidePictures = 1;
+ if (mapId >= 113 && mapId <= 127) {
+ _sideTownPortal = 0;
+ } else {
+ _sideTownPortal = _loadDarkSide ? 1 : 0;
+ }
+
+ if (_vm->getGameID() == GType_WorldOfXeen) {
+ if (!_loadDarkSide) {
+ _animationInfo.load("clouds.dat");
+ _monsterData.load("xeen.mon");
+ _wallPicSprites.load("xeenpic.dat");
+ _sidePictures = 0;
+ _sideMonsters = 0;
+ _sideObjects = 0;
+ } else {
+ switch (mapId) {
+ case 113:
+ case 114:
+ case 115:
+ case 116:
+ case 128:
+ _animationInfo.load("clouds.dat");
+ _monsterData.load("dark.mon");
+ _wallPicSprites.load("darkpic.dat");
+ _sideObjects = 0;
+ break;
+ case 117:
+ case 118:
+ case 119:
+ case 120:
+ case 124:
+ _animationInfo.load("clouds.dat");
+ _monsterData.load("xeen.mon");
+ _wallPicSprites.load("darkpic.dat");
+ _sideObjects = 0;
+ _sideMonsters = 0;
+ break;
+ case 125:
+ case 126:
+ case 127:
+ _animationInfo.load("clouds.dat");
+ _monsterData.load("dark.mon");
+ _wallPicSprites.load("xeenpic.dat");
+ _sideObjects = 0;
+ _sidePictures = 0;
+ break;
+ default:
+ _animationInfo.load("dark.dat");
+ _monsterData.load("ddark.mon");
+ _wallPicSprites.load("darkpic.dat");
+ break;
+ }
+ }
+ }
+
+ // Load any events for the new map
+ loadEvents(mapId);
+
+ // Iterate through loading the given maze as well as the two successive
+ // mazes in each of the four cardinal directions
+ bool isDarkCc = _vm->getGameID() == GType_DarkSide;
+ MazeData *mazeDataP = &_mazeData[0];
+ bool textLoaded = false;
+
+ for (int idx = 0; idx < 9; ++idx, ++mazeDataP) {
+ mazeDataP->_mazeId = mapId;
+
+ if (mapId == 0) {
+ mazeDataP->clear();
+ } else {
+ // Load in the maze's data file
+ Common::String datName = Common::String::format("maze%c%03d.dat",
+ (mapId >= 100) ? 'x' : '0', mapId);
+ File datFile(datName);
+ mazeDataP->synchronize(datFile);
+ datFile.close();
+
+ if (isDarkCc && mapId == 50)
+ mazeDataP->setAllTilesStepped();
+ if (!isDarkCc && _vm->_party->_gameFlags[25] &&
+ (mapId == 42 || mapId == 43 || mapId == 4)) {
+ mazeDataP->clearCellSurfaces();
+ }
+
+ _isOutdoors = (mazeDataP->_mazeFlags2 & FLAG_IS_OUTDOORS) != 0;
+
+ // Handle loading text data
+ if (!textLoaded) {
+ textLoaded = true;
+ Common::String txtName = Common::String::format("%s%c%03d.txt",
+ isDarkCc ? "dark" : "xeen", mapId >= 100 ? 'x' : '0', mapId);
+ File fText(txtName);
+ char mazeName[33];
+ fText.read(mazeName, 33);
+ mazeName[32] = '\0';
+
+ _mazeName = Common::String(mazeName);
+ fText.close();
+
+ // Load the monster/object data
+ Common::String mobName = Common::String::format("maze%c%03d.mob",
+ (mapId >= 100) ? 'x' : '0', mapId);
+ File mobFile(mobName);
+ XeenSerializer sMob(&mobFile, nullptr);
+ _mobData.synchronize(sMob, _monsterData);
+ mobFile.close();
+
+ Common::String headName = Common::String::format("aaze%c%03d.hed",
+ (mapId >= 100) ? 'x' : '0', mapId);
+ File headFile(headName);
+ _headData.synchronize(headFile);
+ headFile.close();
+
+ if (!isDarkCc && _vm->_party->_mazeId)
+ _mobData._monsters.clear();
+
+ if (!isDarkCc && mapId == 15) {
+ if ((_mobData._monsters[0]._position.x > 31 || _mobData._monsters[0]._position.y > 31) &&
+ (_mobData._monsters[1]._position.x > 31 || _mobData._monsters[1]._position.y > 31) &&
+ (_mobData._monsters[2]._position.x > 31 || _mobData._monsters[2]._position.y > 31)) {
+ _vm->_party->_gameFlags[56] = true;
+ }
+ }
+ }
+ }
+
+ // Move to next surrounding maze
+ MazeData *baseMaze = &_mazeData[MAP_GRID_PRIOR_INDEX[idx]];
+ mapId = baseMaze->_surroundingMazes[MAP_GRID_PRIOR_DIRECTION[idx]];
+ if (!mapId) {
+ baseMaze = &_mazeData[MAP_GRID_PRIOR_INDEX2[idx]];
+ mapId = baseMaze->_surroundingMazes[MAP_GRID_PRIOR_DIRECTION2[idx]];
+ }
+ }
+
+ // TODO: Switch setting flags that don't seem to ever be used
+
+ // Reload the monster data for the main maze that we're loading
+ mapId = _vm->_party->_mazeId;
+ Common::String filename = Common::String::format("maze%c%03d.mob",
+ (mapId >= 100) ? 'x' : '0', mapId);
+ File mobFile(filename, *_vm->_saves);
+ XeenSerializer sMob(&mobFile, nullptr);
+ _mobData.synchronize(sMob, _monsterData);
+ mobFile.close();
+
+ // Load sprites for the objects
+ for (uint i = 0; i < _mobData._objectSprites.size(); ++i) {
+ if (_vm->_party->_cloudsEnd && _mobData._objectSprites[i]._spriteId == 85 &&
+ mapId == 27 && isDarkCc) {
+ _mobData._objects[29]._spriteId = 0;
+ _mobData._objects[29]._id = 8;
+ _mobData._objectSprites[i]._sprites.clear();
+ } else if (mapId == 12 && _vm->_party->_gameFlags[43] &&
+ _mobData._objectSprites[i]._spriteId == 118 && !isDarkCc) {
+ filename = "085.obj";
+ _mobData._objectSprites[0]._spriteId = 85;
+ } else {
+ filename = Common::String::format("%03d.%cbj",
+ _mobData._objectSprites[i]._spriteId,
+ _mobData._objectSprites[i]._spriteId >= 100 ? '0' : 'o');
+ }
+
+ // Read in the object sprites
+ _mobData._objectSprites[i]._sprites.load(filename,
+ *_vm->_files->_sideArchives[_sideObjects]);
+ }
+
+ // Load sprites for the monsters
+ for (uint i = 0; i < _mobData._monsterSprites.size(); ++i) {
+ CCArchive *archive = _vm->_files->_sideArchives[
+ _mobData._monsterSprites[i]._spriteId == 91 && _vm->getGameID() == GType_WorldOfXeen ?
+ 0 : _sideMonsters];
+
+ filename = Common::String::format("%03d.mon", _mobData._monsterSprites[i]._spriteId);
+ _mobData._monsterSprites[i]._sprites.load(filename, *archive);
+
+ filename = Common::String::format("%03d.att", _mobData._monsterSprites[i]._spriteId);
+ _mobData._monsterSprites[i]._attackSprites.load(filename, *archive);
+ }
+
+ // Load wall picture sprite resources
+ for (uint i = 0; i < _mobData._wallItemSprites.size(); ++i) {
+ filename = Common::String::format("%03d.pic", _mobData._wallItems[i]._spriteId);
+ _mobData._wallItemSprites[i]._sprites.load(filename,
+ *_vm->_files->_sideArchives[_sidePictures]);
+ }
+
+ // Handle loading miscellaneous sprites for the map
+ if (_isOutdoors) {
+ warning("TODO"); // Sound loading
+
+ _groundSprites.load("water.out");
+ _tileSprites.load("outdoor.til");
+ outdoorList._sky1._sprites = &_skySprites[0];
+ outdoorList._sky2._sprites = &_skySprites[0];
+ outdoorList._groundSprite._sprites = &_groundSprites;
+
+ for (int i = 0; i < TOTAL_SURFACES; ++i) {
+ _wallSprites._surfaces[i].clear();
+
+ if (_mazeData[0]._wallTypes[i] != 0) {
+ _wallSprites._surfaces[i].load(Common::String::format("%s.wal",
+ SURFACE_TYPE_NAMES[_mazeData[0]._wallTypes[i]]));
+ }
+
+ _surfaceSprites[i].clear();
+ if (i != 0 && _mazeData[0]._surfaceTypes[i] != 0)
+ _surfaceSprites[i].load(SURFACE_NAMES[_mazeData[0]._surfaceTypes[i]]);
+ }
+ } else {
+ warning("TODO"); // Sound loading
+
+ _skySprites[1].load(Common::String::format("%s.sky",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+ _groundSprites.load(Common::String::format("%s.gnd",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+ _tileSprites.load(Common::String::format("%s.til",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+
+ for (int i = 0; i < TOTAL_SURFACES; ++i) {
+ _surfaceSprites[i].clear();
+
+ if (_mazeData[0]._surfaceTypes[i] != 0 || i == 4)
+ _surfaceSprites[i].load(SURFACE_NAMES[_mazeData[0]._surfaceTypes[i]]);
+ }
+
+ for (int i = 0; i < TOTAL_SURFACES; ++i)
+ _wallSprites._surfaces[i].clear();
+
+ _wallSprites._fwl1.load(Common::String::format("f%s1.fwl",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+ _wallSprites._fwl2.load(Common::String::format("f%s2.fwl",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+ _wallSprites._fwl3.load(Common::String::format("f%s3.fwl",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+ _wallSprites._fwl4.load(Common::String::format("f%s4.fwl",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+ _wallSprites._swl.load(Common::String::format("s%s.swl",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+
+ // Set entries in the indoor draw list to the correct sprites
+ // for drawing various parts of the background
+ indoorList._swl_0F1R._sprites = &_wallSprites._swl;
+ indoorList._swl_0F1L._sprites = &_wallSprites._swl;
+ indoorList._swl_1F1R._sprites = &_wallSprites._swl;
+ indoorList._swl_1F1L._sprites = &_wallSprites._swl;
+ indoorList._swl_2F2R._sprites = &_wallSprites._swl;
+ indoorList._swl_2F1R._sprites = &_wallSprites._swl;
+ indoorList._swl_2F1L._sprites = &_wallSprites._swl;
+ indoorList._swl_2F2L._sprites = &_wallSprites._swl;
+
+ indoorList._swl_3F1R._sprites = &_wallSprites._swl;
+ indoorList._swl_3F2R._sprites = &_wallSprites._swl;
+ indoorList._swl_3F3R._sprites = &_wallSprites._swl;
+ indoorList._swl_3F4R._sprites = &_wallSprites._swl;
+ indoorList._swl_3F1L._sprites = &_wallSprites._swl;
+ indoorList._swl_3F2L._sprites = &_wallSprites._swl;
+ indoorList._swl_3F3L._sprites = &_wallSprites._swl;
+ indoorList._swl_3F4L._sprites = &_wallSprites._swl;
+
+ indoorList._swl_4F4R._sprites = &_wallSprites._swl;
+ indoorList._swl_4F3R._sprites = &_wallSprites._swl;
+ indoorList._swl_4F2R._sprites = &_wallSprites._swl;
+ indoorList._swl_4F1R._sprites = &_wallSprites._swl;
+ indoorList._swl_4F1L._sprites = &_wallSprites._swl;
+ indoorList._swl_4F2L._sprites = &_wallSprites._swl;
+ indoorList._swl_4F3L._sprites = &_wallSprites._swl;
+ indoorList._swl_4F4L._sprites = &_wallSprites._swl;
+
+ indoorList._fwl_4F4R._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F3R._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F2R._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F1R._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F1L._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F2L._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F3L._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F4L._sprites = &_wallSprites._fwl4;
+
+ indoorList._fwl_2F1R._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_2F._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_2F1L._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_3F2R._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_3F1R._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_3F._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_3F1L._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_3F2L._sprites = &_wallSprites._fwl3;
+
+ indoorList._fwl_1F._sprites = &_wallSprites._fwl1;
+ indoorList._fwl_1F1R._sprites = &_wallSprites._fwl1;
+ indoorList._fwl_1F1L._sprites = &_wallSprites._fwl1;
+ indoorList._horizon._sprites = &_wallSprites._fwl1;
+
+ indoorList._ground._sprites = &_groundSprites;
+
+ // Don't show horizon for certain maps
+ if (_vm->_files->_isDarkCc) {
+ if ((mapId >= 89 && mapId <= 112) || mapId == 128 || mapId == 129)
+ indoorList._horizon._sprites = nullptr;
+ } else {
+ if (mapId >= 25 && mapId <= 27)
+ indoorList._horizon._sprites = nullptr;
+ }
+ }
+
+ loadSky();
+}
+
+int Map::mazeLookup(const Common::Point &pt, int layerShift, int wallMask) {
+ Common::Point pos = pt;
+ int mapId = _vm->_party->_mazeId;
+
+ if (pt.x < -16 || pt.y < -16 || pt.x >= 32 || pt.y >= 32)
+ error("Invalid coordinate");
+
+ // Find the correct maze data out of the set to use
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != _vm->_party->_mazeId)
+ ++_mazeDataIndex;
+
+ // Handle map changing to the north or south as necessary
+ if (pos.y & 16) {
+ if (pos.y >= 0) {
+ pos.y -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
+ } else {
+ pos.y += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
+ }
+
+ if (mapId) {
+ // Move to the correct map to north/south
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+ } else {
+ // No map, so reached outside indoor area or outer space outdoors
+ _currentSteppedOn = true;
+ return _isOutdoors ? SURFTYPE_SPACE : INVALID_CELL;
+ }
+ }
+
+ // Handle map changing to the east or west as necessary
+ if (pos.x & 16) {
+ if (pos.x >= 0) {
+ pos.x -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
+ } else {
+ pos.x += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._west;
+ }
+
+ if (mapId) {
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+ }
+ }
+
+ if (mapId) {
+ if (_isOutdoors) {
+ _currentSurfaceId = _mazeData[_mazeDataIndex]._wallData[pos.y][pos.x]._outdoors._surfaceId;
+ } else {
+ _currentSurfaceId = _mazeData[_mazeDataIndex]._cells[pos.y][pos.x]._surfaceId;
+ }
+
+ if (_currentSurfaceId == SURFTYPE_SPACE || _currentSurfaceId == SURFTYPE_SKY) {
+ _currentSteppedOn = true;
+ } else {
+ _currentSteppedOn = _mazeData[_mazeDataIndex]._steppedOnTiles[pos.y][pos.x];
+ }
+
+ return (_mazeData[_mazeDataIndex]._wallData[pos.y][pos.x]._data >> layerShift) & wallMask;
+
+ } else {
+ _currentSteppedOn = _isOutdoors;
+ return _isOutdoors ? SURFTYPE_SPACE : INVALID_CELL;
+ }
+}
+
+void Map::loadEvents(int mapId) {
+ // Load events
+ Common::String filename = Common::String::format("maze%c%03d.evt",
+ (mapId >= 100) ? 'x' : '0', mapId);
+ File fEvents(filename, *_vm->_saves);
+ XeenSerializer sEvents(&fEvents, nullptr);
+ _events.synchronize(sEvents);
+ fEvents.close();
+
+ // Load text data
+ filename = Common::String::format("aaze%c%03d.txt",
+ (mapId >= 100) ? 'x' : '0', mapId);
+ File fText(filename);
+ _events._text.clear();
+ while (fText.pos() < fText.size())
+ _events._text.push_back(fText.readString());
+ fText.close();
+}
+
+void Map::saveMaze() {
+ int mazeNum = _mazeData[0]._mazeNumber;
+ if (!mazeNum || (mazeNum == 85 && !_vm->_files->_isDarkCc))
+ return;
+
+ // Save the event data
+ Common::String filename = Common::String::format("maze%c%03d.evt",
+ (mazeNum >= 100) ? 'x' : '0', mazeNum);
+ OutFile fEvents(_vm, filename);
+ XeenSerializer sEvents(nullptr, &fEvents);
+ _events.synchronize(sEvents);
+ fEvents.finalize();
+
+ // Save the maze MOB file
+ filename = Common::String::format("maze%c%03d.mob",
+ (mazeNum >= 100) ? 'x' : '0', mazeNum);
+ OutFile fMob(_vm, filename);
+ XeenSerializer sMob(nullptr, &fEvents);
+ _mobData.synchronize(sMob, _monsterData);
+ fEvents.finalize();
+}
+
+void Map::cellFlagLookup(const Common::Point &pt) {
+ Common::Point pos = pt;
+ int mapId = _vm->_party->_mazeId;
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+
+ // Handle map changing to the north or south as necessary
+ if (pos.y & 16) {
+ if (pos.y >= 0) {
+ pos.y -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
+ } else {
+ pos.y += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
+ }
+
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+ }
+
+ // Handle map changing to the east or west as necessary
+ if (pos.x & 16) {
+ if (pos.x >= 0) {
+ pos.x -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
+ } else {
+ pos.x += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._west;
+ }
+
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+ }
+
+ // Get the cell flags
+ const MazeCell &cell = _mazeData[_mazeDataIndex]._cells[pos.y][pos.x];
+ _currentGrateUnlocked = cell._flags & OUTFLAG_GRATE;
+ _currentCantRest = cell._flags & RESTRICTION_REST;
+ _currentIsDrain = cell._flags & OUTFLAG_DRAIN;
+ _currentIsEvent = cell._flags & FLAG_AUTOEXECUTE_EVENT;
+ _currentSky = (cell._flags & OUTFLAG_OBJECT_EXISTS) ? 1 : 0;
+ _currentMonsterFlags = cell._flags & 7;
+}
+
+void Map::setCellSurfaceFlags(const Common::Point &pt, int bits) {
+ mazeLookup(pt, 0);
+
+ Common::Point mapPos(pt.x & 15, pt.y & 15);
+ MazeCell &cell = _mazeData[_mazeDataIndex]._cells[mapPos.y][mapPos.x];
+ cell._flags |= bits & 0xF8;
+}
+
+void Map::setWall(const Common::Point &pt, Direction dir, int v) {
+ const int XOR_MASKS[4] = { 0xFFF, 0xF0FF, 0xFF0F, 0xFFF0 };
+ mazeLookup(pt, 0, 0);
+
+ Common::Point mapPos(pt.x & 15, pt.y & 15);
+ MazeWallLayers &wallLayer = _mazeData[_mazeDataIndex]._wallData[mapPos.y][mapPos.x];
+ wallLayer._data &= XOR_MASKS[dir];
+ wallLayer._data |= v << WALL_SHIFTS[dir][2];
+}
+
+int Map::getCell(int idx) {
+ int mapId = _vm->_party->_mazeId;
+ Direction dir = _vm->_party->_mazeDirection;
+ Common::Point pt(
+ _vm->_party->_mazePosition.x + SCREEN_POSITIONING_X[_vm->_party->_mazeDirection][idx],
+ _vm->_party->_mazePosition.y + SCREEN_POSITIONING_Y[_vm->_party->_mazeDirection][idx]
+ );
+
+ if (pt.x > 31 || pt.y > 31) {
+ if (_vm->_files->_isDarkCc) {
+ if ((mapId >= 53 && mapId <= 88 && mapId != 73) || (mapId >= 74 && mapId <= 120) ||
+ mapId == 125 || mapId == 126 || mapId == 128 || mapId == 129) {
+ _currentSurfaceId = SURFTYPE_DESERT;
+ } else {
+ _currentSurfaceId = 0;
+ }
+ } else {
+ _currentSurfaceId = (mapId >= 25 && mapId <= 27) ? 7 : 0;
+ }
+ _currentWall = INVALID_CELL;
+ return INVALID_CELL;
+ }
+
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+
+ if (pt.y & 16) {
+ if (pt.y >= 0) {
+ pt.y -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
+ } else {
+ pt.y += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
+ }
+
+ if (!mapId) {
+ if (_isOutdoors) {
+ _currentSurfaceId = SURFTYPE_SPACE;
+ _currentWall = 0;
+ return 0;
+ } else {
+ if (_vm->_files->_isDarkCc) {
+ if ((mapId >= 53 && mapId <= 88 && mapId != 73) || (mapId >= 74 && mapId <= 120) ||
+ mapId == 125 || mapId == 126 || mapId == 128 || mapId == 129) {
+ _currentSurfaceId = 6;
+ } else {
+ _currentSurfaceId = 0;
+ }
+ } else {
+ _currentSurfaceId = (mapId >= 25 && mapId <= 27) ? SURFTYPE_ROAD : SURFTYPE_DEFAULT;
+ }
+
+ _currentWall = INVALID_CELL;
+ return INVALID_CELL;
+ }
+
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+ }
+ }
+
+ if (pt.x & 16) {
+ if (pt.x >= 0) {
+ pt.x -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
+ } else {
+ pt.x += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
+ }
+
+ if (!mapId) {
+ if (_isOutdoors) {
+ _currentSurfaceId = SURFTYPE_SPACE;
+ _currentWall = 0;
+ return 0;
+ } else {
+ if (_vm->_files->_isDarkCc) {
+ if ((mapId >= 53 && mapId <= 88 && mapId != 73) || (mapId >= 74 && mapId <= 120) ||
+ mapId == 125 || mapId == 126 || mapId == 128 || mapId == 129) {
+ _currentSurfaceId = 6;
+ } else {
+ _currentSurfaceId = 0;
+ }
+ } else {
+ _currentSurfaceId = (mapId >= 25 && mapId <= 27) ? SURFTYPE_ROAD : SURFTYPE_DEFAULT;
+ }
+
+ _currentWall = INVALID_CELL;
+ return INVALID_CELL;
+ }
+ }
+
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+ }
+
+ int wallData = _mazeData[_mazeDataIndex]._wallData[pt.y][pt.x]._data;
+ if (_isOutdoors) {
+ if (mapId) {
+ // TODO: tile is set to word of (wallLayers >> 8) && 0xff? Makes no sense
+ _currentTile = (wallData >> 8) & 0xFF;
+ _currentWall = (wallData >> 4) & 0xF;
+ _currentSurfaceId = wallData & 0xF;
+ } else {
+ _currentSurfaceId = SURFTYPE_DEFAULT;
+ _currentWall = 0;
+ _currentTile = 0;
+ }
+ } else {
+ if (!mapId)
+ return 0;
+
+ if (pt.x > 31 || pt.y > 31)
+ _currentSurfaceId = 7;
+ else
+ _currentSurfaceId = _mazeData[_mazeDataIndex]._cells[pt.y][pt.x]._surfaceId;
+
+ _currentWall = wallData;
+ return (_currentWall >> WALL_SHIFTS[dir][idx]) & 0xF;
+ }
+
+ return _currentWall;
+}
+
+void Map::loadSky() {
+ Party &party = *_vm->_party;
+
+ party._isNight = party._minutes < (5 * 60) || party._minutes >= (21 * 60);
+ _skySprites[0].load(((party._mazeId >= 89 && party._mazeId <= 112) ||
+ party._mazeId == 128 || party._mazeId == 129) || !party._isNight
+ ? "sky.sky" : "night.sky");
+}
+
+void Map::getNewMaze() {
+ Party &party = *_vm->_party;
+ Common::Point pt = party._mazePosition;
+ int mapId = party._mazeId;
+
+ // Get the correct map to use from the cached list
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId == mapId)
+ ++_mazeDataIndex;
+
+ // Adjust Y and X to be in the 0-15 range, and on the correct surrounding
+ // map if either value is < 0 or >= 16
+ if (pt.y & 16) {
+ if (pt.y >= 0) {
+ pt.y -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
+ } else {
+ pt.y += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
+ }
+
+ if (mapId) {
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId == mapId)
+ ++_mazeDataIndex;
+ }
+ }
+
+ if (pt.x & 16) {
+ if (pt.x >= 0) {
+ pt.x -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
+ } else {
+ pt.x += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._west;
+ }
+
+ if (mapId) {
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId == mapId)
+ ++_mazeDataIndex;
+ }
+ }
+
+ // Save the adjusted (0,0)-(15,15) position and load the given map.
+ // This will make it the new center, with it's own surrounding mazees loaded
+ party._mazePosition = pt;
+ if (mapId)
+ load(mapId);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/map.h b/engines/xeen/map.h
new file mode 100644
index 0000000000..2c02e6ed2f
--- /dev/null
+++ b/engines/xeen/map.h
@@ -0,0 +1,437 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_MAP_H
+#define XEEN_MAP_H
+
+#include "common/stream.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "xeen/combat.h"
+#include "xeen/party.h"
+#include "xeen/scripts.h"
+#include "xeen/sprites.h"
+
+namespace Xeen {
+
+#define MAP_WIDTH 16
+#define MAP_HEIGHT 16
+#define TOTAL_SURFACES 16
+#define INVALID_CELL 0x8888
+
+class XeenEngine;
+
+enum MonsterType {
+ MONSTER_0 = 0, MONSTER_ANIMAL = 1, MONSTER_INSECT = 2,
+ MONSTER_HUMANOID = 3, MONSTER_UNDEAD = 4, MONSTER_GOLEM = 5,
+ MONSTER_DRAGON = 6
+};
+
+class MonsterStruct {
+public:
+ Common::String _name;
+ int _experience;
+ int _hp;
+ int _accuracy;
+ int _speed;
+ int _numberOfAttacks;
+ int _hatesClass;
+ int _strikes;
+ int _dmgPerStrike;
+ DamageType _attackType;
+ SpecialAttack _specialAttack;
+ int _hitChance;
+ int _rangeAttack;
+ MonsterType _monsterType;
+ int _fireResistence;
+ int _electricityResistence;
+ int _coldResistence;
+ int _poisonResistence;
+ int _energyResistence;
+ int _magicResistence;
+ int _phsyicalResistence;
+ int _field29;
+ int _gold;
+ int _gems;
+ int _itemDrop;
+ bool _flying;
+ int _imageNumber;
+ int _loopAnimation;
+ int _animationEffect;
+ int _fx;
+ Common::String _attackVoc;
+public:
+ MonsterStruct();
+ MonsterStruct(Common::String name, int experience, int hp, int accuracy,
+ int speed, int numberOfAttacks, CharacterClass hatesClass, int strikes,
+ int dmgPerStrike, DamageType attackType, SpecialAttack specialAttack,
+ int hitChance, int rangeAttack, MonsterType monsterType,
+ int fireResistence, int electricityResistence, int coldResistence,
+ int poisonResistence, int energyResistence, int magicResistence,
+ int phsyicalResistence, int field29, int gold, int gems, int itemDrop,
+ bool flying, int imageNumber, int loopAnimation, int animationEffect,
+ int field32, Common::String attackVoc);
+
+ void synchronize(Common::SeekableReadStream &s);
+};
+
+class MonsterData : public Common::Array<MonsterStruct> {
+private:
+ void synchronize(Common::SeekableReadStream &s);
+public:
+ MonsterData();
+
+ void load(const Common::String &name);
+};
+
+class SurroundingMazes {
+public:
+ int _north;
+ int _east;
+ int _south;
+ int _west;
+public:
+ SurroundingMazes();
+
+ void clear();
+
+ void synchronize(Common::SeekableReadStream &s);
+
+ int &operator[](int idx);
+};
+
+class MazeDifficulties {
+public:
+ int _wallNoPass;
+ int _surfaceNoPass;
+ int _unlockDoor;
+ int _unlockBox;
+ int _bashDoor;
+ int _bashGrate;
+ int _bashWall;
+ int _chance2Run;
+public:
+ MazeDifficulties();
+
+ void synchronize(Common::SeekableReadStream &s);
+};
+
+enum MazeFlags {
+ OUTFLAG_GRATE = 0x80, OUTFLAG_DRAIN = 0x20, OUTFLAG_OBJECT_EXISTS = 0x08,
+ INFLAG_INSIDE = 0x08, FLAG_AUTOEXECUTE_EVENT = 0x10,
+ RESTRICTION_ETHERIALIZE = 0x40, RESTRICTION_80 = 0x80,
+ RESTRICTION_TOWN_PORTAL = 0x100, RESTRICTION_SUPER_SHELTER = 0x200,
+ RESTRICTION_TIME_DISTORTION = 0x400, RESTRICTION_LLOYDS_BEACON = 0x800,
+ RESTRICTION_TELPORT = 0x1000, RESTRICTION_2000 = 0x2000,
+ RESTRICTION_REST = 0x4000, RESTRICTION_SAVE = 0x8000,
+
+ FLAG_GROUND_BITS = 7
+};
+
+enum MazeFlags2 { FLAG_IS_OUTDOORS = 0x8000, FLAG_IS_DARK = 0x4000 };
+
+enum SurfaceType {
+ SURFTYPE_DEFAULT = 0,
+ SURFTYPE_WATER = 0, SURFTYPE_DIRT = 1, SURFTYPE_GRASS = 2,
+ SURFTYPE_SNOW = 3, SURFTYPE_SWAMP = 4, SURFTYPE_LAVA = 5,
+ SURFTYPE_DESERT = 6, SURFTYPE_ROAD = 7, SURFTYPE_DWATER = 8,
+ SURFTYPE_TFLR = 9, SURFTYPE_SKY = 10, SURFTYPE_CROAD = 11,
+ SURFTYPE_SEWER = 12, SURFTYPE_CLOUD = 13, SURFTYPE_SCORCH = 14,
+ SURFTYPE_SPACE = 15
+};
+
+union MazeWallLayers {
+ struct MazeWallIndoors {
+ int _wallNorth : 4;
+ int _wallEast : 4;
+ int _wallSouth : 4;
+ int _wallWest : 4;
+ } _indoors;
+ struct MazeWallOutdoors {
+ SurfaceType _surfaceId : 4;
+ int _iMiddle : 4;
+ int _iTop : 4;
+ int _iOverlay : 4;
+ } _outdoors;
+ uint16 _data;
+};
+
+struct MazeCell {
+ int _flags;
+ int _surfaceId;
+ MazeCell() : _flags(0), _surfaceId(0) {}
+};
+
+class MazeData {
+public:
+ // Resource fields
+ MazeWallLayers _wallData[MAP_HEIGHT][MAP_WIDTH];
+ MazeCell _cells[MAP_HEIGHT][MAP_WIDTH];
+ int _mazeNumber;
+ SurroundingMazes _surroundingMazes;
+ int _mazeFlags;
+ int _mazeFlags2;
+ int _wallTypes[16];
+ int _surfaceTypes[16];
+ int _floorType;
+ Common::Point _runPosition;
+ MazeDifficulties _difficulties;
+ int _trapDamage;
+ int _wallKind;
+ int _tavernTips;
+ bool _seenTiles[MAP_HEIGHT][MAP_WIDTH];
+ bool _steppedOnTiles[MAP_HEIGHT][MAP_WIDTH];
+
+ // Misc fields
+ int _mazeId;
+public:
+ MazeData();
+
+ void clear();
+
+ void synchronize(Common::SeekableReadStream &s);
+
+ /**
+ * Flags all tiles for the map as having been stepped on
+ */
+ void setAllTilesStepped();
+
+ void clearCellSurfaces();
+};
+
+class MobStruct {
+public:
+ Common::Point _pos;
+ int _id;
+ Direction _direction;
+public:
+ MobStruct();
+
+ bool synchronize(XeenSerializer &s);
+};
+
+struct MazeObject {
+public:
+ Common::Point _position;
+ int _id;
+ int _frame;
+ int _spriteId;
+ Direction _direction;
+ bool _flipped;
+ SpriteResource *_sprites;
+
+ MazeObject();
+};
+
+struct MazeMonster {
+ Common::Point _position;
+ int _frame;
+ int _id;
+ int _spriteId;
+ bool _isAttacking;
+ int _damageType;
+ int _field9;
+ int _fieldA;
+ int _hp;
+ int _effect1, _effect2;
+ int _effect3;
+ SpriteResource *_sprites;
+ SpriteResource *_attackSprites;
+ MonsterStruct *_monsterData;
+
+ MazeMonster();
+
+ /**
+ * Return the text color to use when displaying the monster's name in combat
+ * to indicate how damaged they are
+ */
+ int getTextColor() const;
+};
+
+class MazeWallItem {
+public:
+ Common::Point _position;
+ int _id;
+ int _frame;
+ int _spriteId;
+ Direction _direction;
+ SpriteResource *_sprites;
+public:
+ MazeWallItem();
+};
+
+struct WallSprites {
+ SpriteResource _surfaces[TOTAL_SURFACES];
+ SpriteResource _fwl1;
+ SpriteResource _fwl2;
+ SpriteResource _fwl3;
+ SpriteResource _fwl4;
+ SpriteResource _swl;
+};
+
+class Map;
+
+class MonsterObjectData {
+ friend class Map;
+public:
+ struct SpriteResourceEntry {
+ int _spriteId;
+ SpriteResource _sprites;
+ SpriteResource _attackSprites;
+
+ SpriteResourceEntry() { _spriteId = -1; }
+ SpriteResourceEntry(int spriteId): _spriteId(spriteId) { }
+ };
+private:
+ XeenEngine *_vm;
+ Common::Array<SpriteResourceEntry> _objectSprites;
+ Common::Array<SpriteResourceEntry> _monsterSprites;
+ Common::Array<SpriteResourceEntry> _monsterAttackSprites;
+ Common::Array<SpriteResourceEntry> _wallItemSprites;
+public:
+ Common::Array<MazeObject> _objects;
+ Common::Array<MazeMonster> _monsters;
+ Common::Array<MazeWallItem> _wallItems;
+public:
+ MonsterObjectData(XeenEngine *vm);
+
+ void synchronize(XeenSerializer &s, MonsterData &monsterData);
+};
+
+class HeadData {
+public:
+ struct HeadEntry {
+ int _left;
+ int _right;
+ };
+ HeadEntry _data[MAP_HEIGHT][MAP_WIDTH];
+public:
+ HeadData();
+
+ void synchronize(Common::SeekableReadStream &s);
+
+ HeadEntry *operator[](int y) { return &_data[y][0]; }
+};
+
+struct AnimationFrame { int _front, _left, _back, _right; };
+struct AnimationFlipped { bool _front, _left, _back, _right; };
+struct AnimationEntry {
+ union {
+ AnimationFrame _positions;
+ int _frames[4];
+ } _frame1;
+ union {
+ AnimationFlipped _positions;
+ bool _flags[4];
+ } _flipped;
+ union {
+ AnimationFrame _positions;
+ int _frames[4];
+ } _frame2;
+
+ /**
+ * Synchronize data for an animation entry
+ */
+ void synchronize(Common::SeekableReadStream &s);
+};
+
+class AnimationInfo : public Common::Array<AnimationEntry> {
+public:
+ /**
+ * Synchronize data for object animations within the game
+ */
+ void synchronize(Common::SeekableReadStream &s);
+
+ /**
+ * Load the animation info objects in the game
+ */
+ void load(const Common::String &name);
+};
+
+class Map {
+private:
+ XeenEngine *_vm;
+ MazeData _mazeData[9];
+ SpriteResource _wallPicSprites;
+ int _sidePictures;
+ int _sideObjects;
+ int _sideMonsters;
+ int _mazeDataIndex;
+
+ /**
+ * Load the events for a new map
+ */
+ void loadEvents(int mapId);
+public:
+ Common::String _mazeName;
+ bool _isOutdoors;
+ MonsterObjectData _mobData;
+ MonsterData _monsterData;
+ MazeEvents _events;
+ HeadData _headData;
+ AnimationInfo _animationInfo;
+ SpriteResource _skySprites[2];
+ SpriteResource _groundSprites;
+ SpriteResource _tileSprites;
+ SpriteResource _surfaceSprites[TOTAL_SURFACES];
+ WallSprites _wallSprites;
+ bool _currentGrateUnlocked;
+ bool _currentCantRest;
+ bool _currentIsDrain;
+ bool _currentIsEvent;
+ int _currentSky;
+ int _currentMonsterFlags;
+ int _currentWall;
+ int _currentTile;
+ int _currentSurfaceId;
+ bool _currentSteppedOn;
+ bool _loadDarkSide;
+ int _sideTownPortal;
+public:
+ Map(XeenEngine *vm);
+
+ void load(int mapId);
+
+ int mazeLookup(const Common::Point &pt, int layerShift, int wallMask = 0xf);
+
+ void cellFlagLookup(const Common::Point &pt);
+
+ void setCellSurfaceFlags(const Common::Point &pt, int bits);
+
+ void setWall(const Common::Point &pt, Direction dir, int v);
+
+ void saveMaze();
+
+ int getCell(int idx);
+
+ MazeData &mazeData() { return _mazeData[0]; }
+
+ MazeData &mazeDataCurrent() { return _mazeData[_mazeDataIndex]; }
+
+ void loadSky();
+
+ void getNewMaze();
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_MAP_H */
diff --git a/engines/xeen/module.mk b/engines/xeen/module.mk
new file mode 100644
index 0000000000..b18f44fa6d
--- /dev/null
+++ b/engines/xeen/module.mk
@@ -0,0 +1,53 @@
+MODULE := engines/xeen
+
+MODULE_OBJS := \
+ worldofxeen/clouds_cutscenes.o \
+ worldofxeen/darkside_cutscenes.o \
+ worldofxeen/worldofxeen.o \
+ character.o \
+ combat.o \
+ debugger.o \
+ detection.o \
+ dialogs.o \
+ dialogs_automap.o \
+ dialogs_char_info.o \
+ dialogs_control_panel.o \
+ dialogs_dismiss.o \
+ dialogs_error.o \
+ dialogs_exchange.o \
+ dialogs_fight_options.o \
+ dialogs_options.o \
+ dialogs_info.o \
+ dialogs_input.o \
+ dialogs_items.o \
+ dialogs_party.o \
+ dialogs_query.o \
+ dialogs_quests.o \
+ dialogs_quick_ref.o \
+ dialogs_spells.o \
+ dialogs_whowill.o \
+ events.o \
+ files.o \
+ font.o \
+ interface.o \
+ interface_map.o \
+ map.o \
+ party.o \
+ resources.o \
+ saves.o \
+ screen.o \
+ scripts.o \
+ sound.o \
+ spells.o \
+ sprites.o \
+ town.o \
+ xeen.o \
+ xsurface.o
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_XEEN), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/engines/xeen/party.cpp b/engines/xeen/party.cpp
new file mode 100644
index 0000000000..35a8f00fbe
--- /dev/null
+++ b/engines/xeen/party.cpp
@@ -0,0 +1,733 @@
+/* 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.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/algorithm.h"
+#include "xeen/party.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/files.h"
+#include "xeen/resources.h"
+#include "xeen/saves.h"
+#include "xeen/spells.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+Roster::Roster() {
+ resize(TOTAL_CHARACTERS);
+
+ for (int idx = 0; idx < TOTAL_CHARACTERS; ++idx) {
+ // Set the index of the character in the roster list
+ operator[](idx)._rosterId = idx;
+
+ if (idx < XEEN_TOTAL_CHARACTERS) {
+ // Load new character resource
+ Common::String name = Common::String::format("char%02d.fac", idx + 1);
+ _charFaces[idx].load(name);
+ operator[](idx)._faceSprites = &_charFaces[idx];
+ } else {
+ operator[](idx)._faceSprites = nullptr;
+ }
+ }
+}
+
+void Roster::synchronize(Common::Serializer &s) {
+ for (uint i = 0; i < TOTAL_CHARACTERS; ++i)
+ (*this)[i].synchronize(s);
+}
+
+/*------------------------------------------------------------------------*/
+
+Treasure::Treasure() {
+ _hasItems = false;
+ _gold = _gems = 0;
+
+ _categories[0] = &_weapons[0];
+ _categories[1] = &_armor[0];
+ _categories[2] = &_accessories[0];
+ _categories[3] = &_misc[0];
+}
+
+/*------------------------------------------------------------------------*/
+
+XeenEngine *Party::_vm;
+
+Party::Party(XeenEngine *vm) {
+ _vm = vm;
+ _mazeDirection = DIR_NORTH;
+ _mazeId = _priorMazeId = 0;
+ _levitateActive = false;
+ _automapOn = false;
+ _wizardEyeActive = false;
+ _clairvoyanceActive = false;
+ _walkOnWaterActive = false;
+ _blessed = 0;
+ _powerShield = 0;
+ _holyBonus = 0;
+ _heroism = 0;
+ _difficulty = ADVENTURER;
+ _cloudsEnd = false;
+ _darkSideEnd = false;
+ _worldEnd = false;
+ _ctr24 = 0;
+ _day = 0;
+ _year = 0;
+ _minutes = 0;
+ _food = 0;
+ _lightCount = 0;
+ _torchCount = 0;
+ _fireResistence = 0;
+ _electricityResistence = 0;
+ _coldResistence = 0;
+ _poisonResistence = 0;
+ _deathCount = 0;
+ _winCount = 0;
+ _lossCount = 0;
+ _gold = 0;
+ _gems = 0;
+ _bankGold = 0;
+ _bankGems = 0;
+ _totalTime = 0;
+ _rested = false;
+
+ Common::fill(&_gameFlags[0], &_gameFlags[512], false);
+ Common::fill(&_worldFlags[0], &_worldFlags[128], false);
+ Common::fill(&_quests[0], &_quests[64], false);
+ Common::fill(&_questItems[0], &_questItems[85], 0);
+
+ for (int i = 0; i < TOTAL_CHARACTERS; ++i)
+ Common::fill(&_characterFlags[i][0], &_characterFlags[i][24], false);
+
+ _partyDead = false;
+ _newDay = false;
+ _isNight = false;
+ _stepped = false;
+ _damageType = DT_PHYSICAL;
+ _fallMaze = 0;
+ _fallDamage = 0;
+ _dead = false;
+}
+
+void Party::synchronize(Common::Serializer &s) {
+ byte dummy[30];
+ Common::fill(&dummy[0], &dummy[30], 0);
+ int partyCount = _activeParty.size();
+
+ int8 partyMembers[MAX_PARTY_COUNT];
+ if (s.isSaving()) {
+ Common::fill(&partyMembers[0], &partyMembers[8], -1);
+ for (uint idx = 0; idx < _activeParty.size(); ++idx)
+ partyMembers[idx] = _activeParty[idx]._rosterId;
+ } else {
+ _activeParty.clear();
+ }
+
+ s.syncAsByte(partyCount); // Party count
+ s.syncAsByte(partyCount); // Real party count
+ for (int idx = 0; idx < MAX_PARTY_COUNT; ++idx) {
+ s.syncAsByte(partyMembers[idx]);
+ if (s.isLoading() && idx < partyCount && partyMembers[idx] != -1)
+ _activeParty.push_back(_roster[partyMembers[idx]]);
+ }
+
+ s.syncAsByte(_mazeDirection);
+ s.syncAsByte(_mazePosition.x);
+ s.syncAsByte(_mazePosition.y);
+ s.syncAsByte(_mazeId);
+
+ // Game configuration flags not used in this implementation
+ s.syncBytes(dummy, 3);
+
+ s.syncAsByte(_priorMazeId);
+ s.syncAsByte(_levitateActive);
+ s.syncAsByte(_automapOn);
+ s.syncAsByte(_wizardEyeActive);
+ s.syncAsByte(_clairvoyanceActive);
+ s.syncAsByte(_walkOnWaterActive);
+ s.syncAsByte(_blessed);
+ s.syncAsByte(_powerShield);
+ s.syncAsByte(_holyBonus);
+ s.syncAsByte(_heroism);
+ s.syncAsByte(_difficulty);
+
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithWeapons[0][i].synchronize(s);
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithArmor[0][i].synchronize(s);
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithAccessories[0][i].synchronize(s);
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithMisc[0][i].synchronize(s);
+
+ s.syncAsUint16LE(_cloudsEnd);
+ s.syncAsUint16LE(_darkSideEnd);
+ s.syncAsUint16LE(_worldEnd);
+ s.syncAsUint16LE(_ctr24);
+ s.syncAsUint16LE(_day);
+ s.syncAsUint16LE(_year);
+ s.syncAsUint16LE(_minutes);
+ s.syncAsUint16LE(_food);
+ s.syncAsUint16LE(_lightCount);
+ s.syncAsUint16LE(_torchCount);
+ s.syncAsUint16LE(_fireResistence);
+ s.syncAsUint16LE(_electricityResistence);
+ s.syncAsUint16LE(_coldResistence);
+ s.syncAsUint16LE(_poisonResistence);
+ s.syncAsUint16LE(_deathCount);
+ s.syncAsUint16LE(_winCount);
+ s.syncAsUint16LE(_lossCount);
+ s.syncAsUint32LE(_gold);
+ s.syncAsUint32LE(_gems);
+ s.syncAsUint32LE(_bankGold);
+ s.syncAsUint32LE(_bankGems);
+ s.syncAsUint32LE(_totalTime);
+ s.syncAsByte(_rested);
+ SavesManager::syncBitFlags(s, &_gameFlags[0], &_gameFlags[512]);
+ SavesManager::syncBitFlags(s, &_worldFlags[0], &_worldFlags[128]);
+ SavesManager::syncBitFlags(s, &_quests[0], &_quests[64]);
+
+ for (int i = 0; i < 85; ++i)
+ s.syncAsByte(_questItems[i]);
+
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithWeapons[1][i].synchronize(s);
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithArmor[1][i].synchronize(s);
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithAccessories[1][i].synchronize(s);
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithMisc[1][i].synchronize(s);
+
+ for (int i = 0; i < TOTAL_CHARACTERS; ++i)
+ SavesManager::syncBitFlags(s, &_characterFlags[i][0], &_characterFlags[i][24]);
+ s.syncBytes(&dummy[0], 30);
+}
+
+void Party::loadActiveParty() {
+ // No implementation needed
+}
+
+bool Party::checkSkill(Skill skillId) {
+ uint total = 0;
+ for (uint i = 0; i < _activeParty.size(); ++i) {
+ if (_activeParty[i]._skills[skillId]) {
+ ++total;
+
+ switch (skillId) {
+ case MOUNTAINEER:
+ case PATHFINDER:
+ // At least two characters need skill for check to return true
+ if (total == 2)
+ return true;
+ break;
+ case CRUSADER:
+ case SWIMMING:
+ // Entire party must have skill for check to return true
+ if (total == _activeParty.size())
+ return true;
+ break;
+ default:
+ // All other skills only need to have a single player having it
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool Party::isInParty(int charId) {
+ for (uint i = 0; i < _activeParty.size(); ++i) {
+ if (_activeParty[i]._rosterId == charId)
+ return true;
+ }
+
+ return false;
+}
+
+void Party::copyPartyToRoster() {
+ for (uint i = 0; i < _activeParty.size(); ++i) {
+ _roster[_activeParty[i]._rosterId] = _activeParty[i];
+ }
+}
+
+void Party::changeTime(int numMinutes) {
+ bool killed = false;
+
+ if (((_minutes + numMinutes) / 480) != (_minutes / 480)) {
+ for (int idx = 0; idx < (int)_activeParty.size(); ++idx) {
+ Character &player = _activeParty[idx];
+
+ if (!player._conditions[DEAD] && !player._conditions[STONED] &&
+ !player._conditions[ERADICATED]) {
+ for (int statNum = 0; statNum < TOTAL_STATS; ++statNum) {
+ int statVal = player.getStat((Attribute)statNum);
+ if (statVal < 1)
+ player._conditions[DEAD] = 1;
+ }
+ }
+
+ // Handle heart broken condition becoming depression
+ if (player._conditions[HEART_BROKEN]) {
+ if (++player._conditions[HEART_BROKEN] > 10) {
+ player._conditions[HEART_BROKEN] = 0;
+ player._conditions[DEPRESSED] = 1;
+ }
+ }
+
+ // Handle poisoning
+ if (!player._conditions[POISONED]) {
+ if (_vm->getRandomNumber(1, 10) != 1 || !player.charSavingThrow(DT_ELECTRICAL))
+ player._conditions[POISONED] *= 2;
+ else
+ // Poison wears off
+ player._conditions[POISONED] = 0;
+ }
+
+ // Handle disease
+ if (!player._conditions[DISEASED]) {
+ if (_vm->getRandomNumber(9) != 1 || !player.charSavingThrow(DT_COLD))
+ player._conditions[DISEASED] *= 2;
+ else
+ // Disease wears off
+ player._conditions[DISEASED] = 0;
+ }
+
+ // Handle insane status
+ if (player._conditions[INSANE])
+ player._conditions[INSANE]++;
+
+ if (player._conditions[DEAD]) {
+ if (++player._conditions[DEAD] == 0)
+ player._conditions[DEAD] = -1;
+ }
+
+ if (player._conditions[STONED]) {
+ if (++player._conditions[STONED] == 0)
+ player._conditions[STONED] = -1;
+ }
+
+ if (player._conditions[ERADICATED]) {
+ if (++player._conditions[ERADICATED] == 0)
+ player._conditions[ERADICATED] = -1;
+ }
+
+ if (player._conditions[IN_LOVE]) {
+ if (++player._conditions[IN_LOVE] > 10) {
+ player._conditions[IN_LOVE] = 0;
+ player._conditions[HEART_BROKEN] = 1;
+ }
+ }
+
+ player._conditions[WEAK] = player._conditions[DRUNK];
+ player._conditions[DRUNK] = 0;
+
+ if (player._conditions[DEPRESSED]) {
+ player._conditions[DEPRESSED] = (player._conditions[DEPRESSED] + 1) % 4;
+ }
+ }
+ }
+
+ // Increment the time
+ addTime(numMinutes);
+
+ for (int idx = 0; idx < (int)_activeParty.size(); ++idx) {
+ Character &player = _activeParty[idx];
+
+ if (player._conditions[CONFUSED] && _vm->getRandomNumber(2) == 1) {
+ if (player.charSavingThrow(DT_PHYSICAL)) {
+ player._conditions[CONFUSED] = 0;
+ } else {
+ player._conditions[CONFUSED]--;
+ }
+ }
+
+ if (player._conditions[PARALYZED] && _vm->getRandomNumber(4) == 1)
+ player._conditions[PARALYZED]--;
+ }
+
+ if (killed)
+ _vm->_interface->drawParty(true);
+
+ if (_isNight != (_minutes < (5 * 60) || _minutes >= (21 * 60)))
+ _vm->_map->loadSky();
+}
+
+void Party::addTime(int numMinutes) {
+ int day = _day;
+ _minutes += numMinutes;
+
+ // If the total minutes has exceeded a day, move to next one
+ while (_minutes >= (24 * 60)) {
+ _minutes -= 24 * 60;
+ if (++_day >= 100) {
+ _day -= 100;
+ ++_year;
+ }
+ }
+
+ if ((_day % 10) == 1 || numMinutes > (24 * 60)) {
+ if (_day != day) {
+ warning("TODO: resetBlacksmith? and giveInterest?");
+ }
+ }
+
+ if (_day != day)
+ _newDay = true;
+
+ if (_newDay && _minutes >= 300) {
+ if (_vm->_mode != MODE_9 && _vm->_mode != MODE_17) {
+ resetTemps();
+ if (_rested || _vm->_mode == MODE_SLEEPING) {
+ _rested = false;
+ } else {
+ for (int idx = 0; idx < (int)_activeParty.size(); ++idx) {
+ if (_activeParty[idx]._conditions[WEAK] >= 0)
+ _activeParty[idx]._conditions[WEAK]++;
+ }
+
+ ErrorScroll::show(_vm, THE_PARTY_NEEDS_REST, WT_NONFREEZED_WAIT);
+ }
+
+ _vm->_interface->drawParty(true);
+ }
+
+ _newDay = false;
+ }
+}
+
+void Party::resetTemps() {
+ for (int idx = 0; idx < (int)_activeParty.size(); ++idx) {
+ Character &player = _activeParty[idx];
+
+ player._magicResistence._temporary = 0;
+ player._energyResistence._temporary = 0;
+ player._poisonResistence._temporary = 0;
+ player._electricityResistence._temporary = 0;
+ player._coldResistence._temporary = 0;
+ player._fireResistence._temporary = 0;
+ player._ACTemp = 0;
+ player._level._temporary = 0;
+ player._luck._temporary = 0;
+ player._accuracy._temporary = 0;
+ player._speed._temporary = 0;
+ player._endurance._temporary = 0;
+ player._personality._temporary = 0;
+ player._intellect._temporary = 0;
+ player._might._temporary = 0;
+ }
+
+ _poisonResistence = 0;
+ _coldResistence = 0;
+ _electricityResistence = 0;
+ _fireResistence = 0;
+ _lightCount = 0;
+ _levitateActive = false;
+ _walkOnWaterActive = false;
+ _wizardEyeActive = false;
+ _clairvoyanceActive = false;
+ _heroism = 0;
+ _holyBonus = 0;
+ _powerShield = 0;
+ _blessed = 0;
+}
+
+void Party::handleLight() {
+ Map &map = *_vm->_map;
+
+ if (_stepped) {
+ map.cellFlagLookup(_mazePosition);
+ if (map._currentIsDrain && _lightCount)
+ --_lightCount;
+
+ if (checkSkill(CARTOGRAPHER)) {
+ map.mazeDataCurrent()._steppedOnTiles[_mazePosition.y & 15][_mazePosition.x & 15] = true;
+ }
+ }
+
+ _vm->_interface->_intrIndex1 = _lightCount ||
+ (map.mazeData()._mazeFlags2 & FLAG_IS_DARK) == 0 ? 4 : 0;
+}
+
+int Party::subtract(int mode, uint amount, int whereId, ErrorWaitType wait) {
+ switch (mode) {
+ case 0:
+ // Gold
+ if (whereId) {
+ if (amount <= _bankGold) {
+ _bankGold -= amount;
+ } else {
+ notEnough(0, whereId, false, wait);
+ return false;
+ }
+ }
+ else {
+ if (amount <= _gold) {
+ _gold -= amount;
+ } else {
+ notEnough(0, whereId, false, wait);
+ return false;
+ }
+ }
+ break;
+
+ case 1:
+ // Gems
+ if (whereId) {
+ if (amount <= _bankGems) {
+ _bankGems -= amount;
+ } else {
+ notEnough(0, whereId, false, wait);
+ return false;
+ }
+ }
+ else {
+ if (amount <= _gems) {
+ _gems -= amount;
+ } else {
+ notEnough(0, whereId, false, wait);
+ return false;
+ }
+ }
+ break;
+
+ case 2:
+ // Food
+ if (amount > _food) {
+ _food -= amount;
+ } else {
+ notEnough(5, 0, 0, wait);
+ return false;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+void Party::notEnough(int consumableId, int whereId, bool mode, ErrorWaitType wait) {
+ Common::String msg = Common::String::format(
+ mode ? NO_X_IN_THE_Y : NOT_ENOUGH_X_IN_THE_Y,
+ CONSUMABLE_NAMES[consumableId], WHERE_NAMES[whereId]);
+ ErrorScroll::show(_vm, msg, wait);
+}
+
+void Party::checkPartyDead() {
+ Combat &combat = *_vm->_combat;
+ bool inCombat = _vm->_mode == MODE_COMBAT;
+
+ for (uint charIdx = 0; charIdx < (inCombat ? combat._combatParty.size() : _activeParty.size()); ++charIdx) {
+ Character &c = inCombat ? *combat._combatParty[charIdx] : _activeParty[charIdx];
+ Condition cond = c.worstCondition();
+ if (cond <= CONFUSED || cond == NO_CONDITION) {
+ _dead = false;
+ return;
+ }
+ }
+
+ _dead = true;
+}
+
+void Party::moveToRunLocation() {
+ _mazePosition = _vm->_map->mazeData()._runPosition;
+}
+
+void Party::giveTreasure() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ Scripts &scripts = *_vm->_scripts;
+ SoundManager &sound = *_vm->_sound;
+ Window &w = screen._windows[10];
+
+ if (!_treasure._gold && !_treasure._gems)
+ return;
+
+ bool monstersPresent = false;
+ for (int idx = 0; idx < 26 && !monstersPresent; ++idx)
+ monstersPresent = combat._attackMonsters[idx] != -1;
+
+ if (_vm->_mode != MODE_9 && monstersPresent)
+ return;
+
+ Common::fill(&combat._shooting[0], &combat._shooting[MAX_PARTY_COUNT], 0);
+ intf._charsShooting = false;
+ intf.draw3d(true);
+
+ if (_treasure._gold || _treasure._gems)
+ sound.playFX(54);
+
+ events.clearEvents();
+ w.close();
+ w.open();
+ w.writeString(Common::String::format(PARTY_FOUND, _treasure._gold, _treasure._gems));
+ w.update();
+
+ if (_vm->_mode != MODE_COMBAT)
+ _vm->_mode = MODE_7;
+
+ if (arePacksFull())
+ ErrorScroll::show(_vm, BACKPACKS_FULL_PRESS_KEY, WT_NONFREEZED_WAIT);
+
+ for (int categoryNum = 0; categoryNum < NUM_ITEM_CATEGORIES; ++categoryNum) {
+ for (int itemNum = 0; itemNum < MAX_TREASURE_ITEMS; ++itemNum) {
+ if (arePacksFull()) {
+ if (_treasure._weapons[itemNum]._id == 34) {
+ // Important item, so clear a slot for it
+ _activeParty[0]._weapons[INV_ITEMS_TOTAL - 1].clear();
+ } else {
+ // Otherwise, clear all the remaining treasure items,
+ // since all the party's packs are full
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ _treasure._weapons[idx].clear();
+ _treasure._armor[idx].clear();
+ _treasure._accessories[idx].clear();
+ _treasure._armor[idx].clear();
+ }
+ }
+ }
+
+ // If there's no treasure item to be distributed, skip to next slot
+ if (!_treasure._categories[categoryNum][itemNum]._id)
+ continue;
+
+ int charIndex = scripts._whoWill - 1;
+ if (charIndex >= 0 && charIndex < (int)_activeParty.size()) {
+ // Check the designated character first
+ Character &c = _activeParty[charIndex];
+ if (!c._items[(ItemCategory)categoryNum].isFull() && !c.isDisabledOrDead()) {
+ giveTreasureToCharacter(c, (ItemCategory)categoryNum, itemNum);
+ continue;
+ }
+
+ // Fall back on checking the entire conscious party
+ for (charIndex = 0; charIndex < (int)_activeParty.size(); ++charIndex) {
+ Character &ch = _activeParty[charIndex];
+ if (!ch._items[(ItemCategory)categoryNum].isFull() && !ch.isDisabledOrDead()) {
+ giveTreasureToCharacter(ch, (ItemCategory)categoryNum, itemNum);
+ break;
+ }
+ }
+ if (charIndex != (int)_activeParty.size())
+ continue;
+ }
+
+ // At this point, find an empty pack for any character, irrespective
+ // of whether the character is conscious or not
+ for (charIndex = 0; charIndex < (int)_activeParty.size(); ++charIndex) {
+ Character &c = _activeParty[charIndex];
+ if (!c._items[(ItemCategory)categoryNum].isFull() && !c.isDisabledOrDead()) {
+ giveTreasureToCharacter(c, (ItemCategory)categoryNum, itemNum);
+ continue;
+ }
+ }
+ }
+ }
+
+ w.writeString(HIT_A_KEY);
+ w.update();
+
+ do {
+ events.updateGameCounter();
+ intf.draw3d(true);
+
+ while (!events.isKeyMousePressed() && events.timeElapsed() < 1)
+ events.pollEventsAndWait();
+ } while (!_vm->shouldQuit() && events.timeElapsed() == 1);
+
+ if (_vm->_mode != MODE_COMBAT)
+ _vm->_mode = MODE_1;
+
+ w.close();
+ _gold += _treasure._gold;
+ _gems += _treasure._gems;
+ _treasure._gold = 0;
+ _treasure._gems = 0;
+
+ _treasure._hasItems = false;
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ _treasure._weapons[idx].clear();
+ _treasure._armor[idx].clear();
+ _treasure._accessories[idx].clear();
+ _treasure._armor[idx].clear();
+ }
+
+ scripts._v2 = 1;
+}
+
+bool Party::arePacksFull() const {
+ uint total = 0;
+ for (uint idx = 0; idx < _activeParty.size(); ++idx) {
+ const Character &c = _activeParty[idx];
+ total += (c._weapons[INV_ITEMS_TOTAL - 1]._id != 0 ? 1 : 0)
+ + (c._armor[INV_ITEMS_TOTAL - 1]._id != 0 ? 1 : 0)
+ + (c._accessories[INV_ITEMS_TOTAL - 1]._id != 0 ? 1 : 0)
+ + (c._misc[INV_ITEMS_TOTAL - 1]._id != 0 ? 1 : 0);
+ }
+
+ return total == (_activeParty.size() * NUM_ITEM_CATEGORIES);
+}
+
+void Party::giveTreasureToCharacter(Character &c, ItemCategory category, int itemIndex) {
+ EventsManager &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Window &w = screen._windows[10];
+ XeenItem &treasureItem = _treasure._categories[category][itemIndex];
+ sound.playFX(20);
+
+ if (treasureItem._id < 82) {
+ // Copy item into the character's inventory
+ c._items[category][INV_ITEMS_TOTAL - 1] = treasureItem;
+ c._items[category].sort();
+ }
+
+ w.writeString(GIVE_TREASURE_FORMATTING);
+ w.update();
+ events.ipause(5);
+
+ w.writeString(Common::String::format(X_FOUND_Y, c._name.c_str(),
+ ITEM_NAMES[category][treasureItem._id]));
+ w.update();
+
+ events.ipause(5);
+}
+
+bool Party::canShoot() const {
+ for (uint idx = 0; idx < _activeParty.size(); ++idx) {
+ if (_activeParty[idx].hasMissileWeapon())
+ return true;
+ }
+
+ return false;
+}
+
+bool Party::giveTake(int mode1, uint32 mask1, int mode2, int mask2, int charIdx) {
+ error("TODO");
+}
+
+
+} // End of namespace Xeen
diff --git a/engines/xeen/party.h b/engines/xeen/party.h
new file mode 100644
index 0000000000..d753b75801
--- /dev/null
+++ b/engines/xeen/party.h
@@ -0,0 +1,204 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#ifndef XEEN_PARTY_H
+#define XEEN_PARTY_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/serializer.h"
+#include "xeen/character.h"
+#include "xeen/combat.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/items.h"
+
+namespace Xeen {
+
+enum Direction {
+ DIR_NORTH = 0, DIR_EAST = 1, DIR_SOUTH = 2, DIR_WEST = 3, DIR_ALL = 4
+};
+
+enum Difficulty { ADVENTURER = 0, WARRIOR = 1 };
+
+#define ITEMS_COUNT 36
+#define TOTAL_CHARACTERS 30
+#define XEEN_TOTAL_CHARACTERS 24
+#define MAX_ACTIVE_PARTY 6
+#define MAX_PARTY_COUNT 8
+#define TOTAL_STATS 7
+#define TOTAL_QUEST_ITEMS 85
+#define TOTAL_QUEST_FLAGS 56
+#define MAX_TREASURE_ITEMS 10
+
+class Roster: public Common::Array<Character> {
+public:
+ SpriteResource _charFaces[TOTAL_CHARACTERS];
+public:
+ Roster();
+
+ void synchronize(Common::Serializer &s);
+};
+
+class Treasure {
+public:
+ XeenItem _misc[MAX_TREASURE_ITEMS];
+ XeenItem _accessories[MAX_TREASURE_ITEMS];
+ XeenItem _armor[MAX_TREASURE_ITEMS];
+ XeenItem _weapons[MAX_TREASURE_ITEMS];
+ XeenItem *_categories[4];
+ bool _hasItems;
+ int _gems, _gold;
+public:
+ Treasure();
+};
+
+class Party {
+ friend class Character;
+ friend class InventoryItems;
+private:
+ static XeenEngine *_vm;
+
+ /**
+ * Give a treasure item to the given character's inventory
+ */
+ void giveTreasureToCharacter(Character &c, ItemCategory category, int itemIndex);
+public:
+ // Dynamic data that's saved
+ Direction _mazeDirection;
+ Common::Point _mazePosition;
+ int _mazeId;
+ int _priorMazeId;
+ bool _levitateActive;
+ bool _automapOn;
+ bool _wizardEyeActive;
+ bool _clairvoyanceActive;
+ bool _walkOnWaterActive;
+ int _blessed;
+ int _powerShield;
+ int _holyBonus;
+ int _heroism;
+ Difficulty _difficulty;
+ XeenItem _blacksmithWeapons[2][ITEMS_COUNT];
+ XeenItem _blacksmithArmor[2][ITEMS_COUNT];
+ XeenItem _blacksmithAccessories[2][ITEMS_COUNT];
+ XeenItem _blacksmithMisc[2][ITEMS_COUNT];
+ bool _cloudsEnd;
+ bool _darkSideEnd;
+ bool _worldEnd;
+ int _ctr24; // TODO: Figure out proper name
+ int _day;
+ uint _year;
+ int _minutes;
+ uint _food;
+ int _lightCount;
+ int _torchCount;
+ int _fireResistence;
+ int _electricityResistence;
+ int _coldResistence;
+ int _poisonResistence;
+ int _deathCount;
+ int _winCount;
+ int _lossCount;
+ uint _gold;
+ uint _gems;
+ uint _bankGold;
+ uint _bankGems;
+ int _totalTime;
+ bool _rested;
+ bool _gameFlags[512];
+ bool _worldFlags[128];
+ bool _quests[64];
+ int _questItems[TOTAL_QUEST_ITEMS];
+ bool _characterFlags[30][24];
+public:
+ // Other party related runtime data
+ Roster _roster;
+ Common::Array<Character> _activeParty;
+ bool _partyDead;
+ bool _newDay;
+ bool _isNight;
+ bool _stepped;
+ Common::Point _fallPosition;
+ int _fallMaze;
+ int _fallDamage;
+ DamageType _damageType;
+ bool _dead;
+ Treasure _treasure;
+ Treasure _savedTreasure;
+public:
+ Party(XeenEngine *vm);
+
+ void synchronize(Common::Serializer &s);
+
+ void loadActiveParty();
+
+ bool checkSkill(Skill skillId);
+
+ bool isInParty(int charId);
+
+ /**
+ * Copy the currently active party characters' data back to the roster
+ */
+ void copyPartyToRoster();
+
+ /**
+ * Adds time to the party's playtime, taking into account the effect of any
+ * stat modifier changes
+ */
+ void changeTime(int numMinutes);
+
+ void addTime(int numMinutes);
+
+ void resetTemps();
+
+ void handleLight();
+
+ int subtract(int mode, uint amount, int whereId, ErrorWaitType wait = WT_FREEZE_WAIT);
+
+ void notEnough(int consumableId, int whereId, bool mode, ErrorWaitType wait);
+
+ void checkPartyDead();
+
+ /**
+ * Move party position to the run destination on the current map
+ */
+ void moveToRunLocation();
+
+ /**
+ * Give treasure to the party
+ */
+ void giveTreasure();
+
+ /**
+ * Returns true if all the packs for all the characters are full
+ */
+ bool arePacksFull() const;
+
+ bool canShoot() const;
+
+ bool giveTake(int mode1, uint32 mask1, int mode2, int mask2, int charIdx);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_PARTY_H */
diff --git a/engines/xeen/resources.cpp b/engines/xeen/resources.cpp
new file mode 100644
index 0000000000..c171a1717b
--- /dev/null
+++ b/engines/xeen/resources.cpp
@@ -0,0 +1,1590 @@
+/* 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.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "xeen/resources.h"
+#include "xeen/files.h"
+
+namespace Xeen {
+
+Resources::Resources() {
+ _globalSprites.load("global.icn");
+
+ File f("mae.xen");
+ while (f.pos() < f.size())
+ _maeNames.push_back(f.readString());
+ f.close();
+}
+
+/*------------------------------------------------------------------------*/
+
+const char *const CREDITS =
+ "\013""012\010""000\003""c\014""35Designed and Directed By:\n"
+ "\014""17Jon Van Caneghem\003""l\n"
+ "\n"
+ "\t025\014""35Programming:\n"
+ "\t035\014""17Mark Caldwell\n"
+ "\t035Dave Hathaway\n"
+ "\n"
+ "\t025\014""35Sound System & FX:\n"
+ "\t035\014""17Mike Heilemann\n"
+ "\n"
+ "\t025\014""35Music & Speech:\n"
+ "\t035\014""17Tim Tully\n"
+ "\n"
+ "\t025\014""35Writing:\n"
+ "\t035\014""17Paul Rattner\n"
+ "\t035Debbie Van Caneghem\n"
+ "\t035Jon Van Caneghem\013""012\n"
+ "\n"
+ "\n"
+ "\t180\014""35Graphics:\n"
+ "\t190\014""17Jonathan P. Gwyn\n"
+ "\t190Bonita Long-Hemsath\n"
+ "\t190Julia Ulano\n"
+ "\t190Ricardo Barrera\n"
+ "\n"
+ "\t180\014""35Testing:\n"
+ "\t190\014""17Benjamin Bent\n"
+ "\t190Christian Dailey\n"
+ "\t190Mario Escamilla\n"
+ "\t190Marco Hunter\n"
+ "\t190Robert J. Lupo\n"
+ "\t190Clayton Retzer\n"
+ "\t190David Vela\003""c";
+
+const char *const OPTIONS_TITLE =
+ "\x0D\x01\003""c\014""dMight and Magic Options\n"
+ "World of Xeen\x02\n"
+ "\v117Copyright (c) 1993 NWC, Inc.\n"
+ "All Rights Reserved\x01";
+
+const char *const THE_PARTY_NEEDS_REST = "\x0B""012The Party needs rest!";
+
+const char *const WHO_WILL = "\x03""c\x0B""000\x09""000%s\x0A\x0A"
+ "Who will\x0A%s?\x0A\x0B""055F1 - F%d";
+
+const char *const WHATS_THE_PASSWORD = "What's the Password?";
+
+const char *const IN_NO_CONDITION = "\x0B""007%s is not in any condition to perform actions!";
+
+const char *const NOTHING_HERE = "\x03""c\x0B""010Nothing here.";
+
+const char *const TERRAIN_TYPES[6] = {
+ "town", "cave", "towr", "cstl", "dung", "scfi"
+};
+
+const char *const SURFACE_TYPE_NAMES[15] = {
+ nullptr, "mount", "ltree", "dtree", "grass", "snotree", "snomnt",
+ "dedltree", "mount", "lavamnt", "palm", "dmount", "dedltree",
+ "dedltree", "dedltree"
+};
+
+const char *const SURFACE_NAMES[16] = {
+ "water.srf", "dirt.srf", "grass.srf", "snow.srf", "swamp.srf",
+ "lava.srf", "desert.srf", "road.srf", "dwater.srf", "tflr.srf",
+ "sky.srf", "croad.srf", "sewer.srf", "cloud.srf", "scortch.srf",
+ "space.srf"
+};
+
+const char *const WHO_ACTIONS[32] = {
+ "search", "open", "drink", "mine", "touch", "read", "learn", "take",
+ "bang", "steal", "bribe", "pay", "sit", "try", "turn", "bathe",
+ "destroy", "pull", "descend", "toss a coin", "pray", "join", "act",
+ "play", "push", "rub", "pick", "eat", "sign", "close", "look", "try"
+};
+
+const char *const WHO_WILL_ACTIONS[4] = {
+ "Open Grate", "Open Door", "Open Scroll", "Select Char"
+};
+
+const byte SYMBOLS[20][64] = {
+ { // 0
+ 0x00, 0x00, 0xA8, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0x00, 0xA8, 0x9E, 0x9C, 0x9C, 0x9E, 0x9E, 0x9E,
+ 0xAC, 0x9C, 0xA4, 0xAC, 0xAC, 0x9A, 0x9A, 0x9A, 0xAC, 0x9E, 0xAC, 0xA8, 0xA8, 0xA6, 0x97, 0x98,
+ 0xAC, 0xA0, 0xAC, 0xAC, 0xA4, 0xA6, 0x98, 0x99, 0x00, 0xAC, 0xA0, 0xA0, 0xA8, 0xAC, 0x9A, 0x9A,
+ 0x00, 0x00, 0xAC, 0xAC, 0xAC, 0xA4, 0x9B, 0x9A, 0x00, 0x00, 0x00, 0x00, 0xAC, 0xA0, 0x9B, 0x9B,
+ },
+ { // 1
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E,
+ 0x99, 0x9A, 0x9A, 0x99, 0x99, 0x99, 0x9A, 0x99, 0x98, 0x98, 0x98, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x99, 0x98, 0x98, 0x99, 0x98, 0x98, 0x99, 0x99, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A,
+ 0x9A, 0x9B, 0x9B, 0x9C, 0x9B, 0x9A, 0x9C, 0x9A, 0x9B, 0x9A, 0x99, 0x99, 0x99, 0x9A, 0x9A, 0x9B,
+ },
+ { // 2
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E,
+ 0x99, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x99, 0x98, 0x98, 0x99, 0x98, 0x98, 0x97, 0x98, 0x98,
+ 0x99, 0x98, 0x98, 0x98, 0x99, 0x99, 0x98, 0x99, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A,
+ 0x9B, 0x9B, 0x9C, 0x9C, 0x9B, 0x9B, 0x9B, 0x9B, 0x99, 0x9A, 0x9B, 0x9B, 0x9A, 0x9A, 0x99, 0x9A,
+ },
+ { // 3
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E,
+ 0x99, 0x9A, 0x9A, 0x9A, 0x99, 0x99, 0x99, 0x9A, 0x98, 0x98, 0x97, 0x97, 0x98, 0x98, 0x98, 0x98,
+ 0x99, 0x99, 0x98, 0x99, 0x98, 0x98, 0x99, 0x99, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A,
+ 0x9B, 0x9C, 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0x9C, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x99, 0x99, 0x9A,
+ },
+ { // 4
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E,
+ 0x9A, 0x9A, 0x9A, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x97, 0x97, 0x97, 0x97, 0x97, 0x98, 0x98, 0x98,
+ 0x99, 0x99, 0x98, 0x99, 0x99, 0x98, 0x98, 0x98, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A,
+ 0x9A, 0x9C, 0x9B, 0x9B, 0x9C, 0x9B, 0x9B, 0x9B, 0x9A, 0x99, 0x9B, 0x9B, 0x9A, 0x99, 0x9A, 0x9A,
+ },
+ { // 5
+ 0xA4, 0xA4, 0xA8, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x9E, 0x9E, 0x9E, 0xA0, 0xA8, 0xAC, 0x00, 0x00,
+ 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9E, 0xAC, 0x00, 0x97, 0x97, 0x97, 0x98, 0x9C, 0x9C, 0xA0, 0xAC,
+ 0x99, 0x98, 0x99, 0x99, 0x99, 0x9B, 0xA0, 0xAC, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9B, 0xA0, 0xAC,
+ 0x9C, 0x9B, 0x9C, 0x9C, 0x9C, 0xA0, 0xAC, 0x00, 0x99, 0x9A, 0x9A, 0x9B, 0x9B, 0xA4, 0xAC, 0x00,
+ },
+ { // 6
+ 0x00, 0x00, 0x00, 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x00, 0x00, 0x00, 0xAC, 0xA0, 0x9C, 0x9B, 0x99,
+ 0x00, 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x98, 0x99, 0x99,
+ 0x00, 0xAC, 0xA0, 0x9C, 0x9C, 0xA0, 0x9C, 0x9A, 0x00, 0x00, 0xAC, 0xA4, 0xA0, 0x99, 0x99, 0x99,
+ 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x00, 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x99, 0x99,
+ },
+ { // 7
+ 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99, 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x00, 0x00, 0xAC, 0xA4, 0x9C, 0x9C, 0x99, 0x99,
+ 0x00, 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x00, 0x00, 0x00, 0xAC, 0xA4, 0x9C, 0x99, 0x99,
+ 0x00, 0x00, 0xAC, 0xA0, 0x9B, 0xA0, 0x9E, 0x9C, 0x00, 0xAC, 0xA4, 0x9C, 0x99, 0x9C, 0x99, 0x99,
+ },
+ { // 8
+ 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x9B, 0x99, 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99, 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x00, 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x99, 0x99, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x9C, 0x99, 0x00, 0xAC, 0xA4, 0x9C, 0x99, 0x9E, 0x9C, 0x99,
+ },
+ { // 9
+ 0x00, 0x00, 0xAC, 0xA4, 0xA0, 0x9C, 0x99, 0x99, 0x00, 0xAC, 0xA0, 0x9C, 0x9C, 0xA0, 0x9C, 0x9A,
+ 0xAC, 0xA4, 0x9C, 0x9A, 0x99, 0x99, 0x99, 0x99, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99, 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99,
+ 0x00, 0xAC, 0xA4, 0x9C, 0x9A, 0x9C, 0x99, 0x99, 0x00, 0x00, 0xAC, 0xA0, 0x9C, 0x9A, 0x99, 0x99,
+ },
+ { // 10
+ 0x99, 0x99, 0x99, 0x9A, 0xA0, 0xAC, 0x00, 0x00, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x00,
+ 0x99, 0x99, 0x9C, 0x9E, 0xA4, 0xAC, 0x00, 0x00, 0x99, 0x99, 0x9C, 0x99, 0x9C, 0xA4, 0xAC, 0x00,
+ 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x00,
+ 0x99, 0x99, 0x99, 0xA0, 0xA4, 0xAC, 0x00, 0x00, 0x9A, 0x9B, 0x9E, 0x9C, 0x9C, 0xA4, 0xAC, 0x00,
+ },
+ { // 11
+ 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0x9E, 0xAC,
+ 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA4, 0xAC, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC,
+ 0x9C, 0x99, 0x99, 0x99, 0x9C, 0x9C, 0xA4, 0xAC, 0x99, 0x9E, 0x9E, 0x9C, 0x9C, 0xA0, 0xAC, 0x00,
+ },
+ { // 12
+ 0x99, 0x99, 0x9C, 0xA0, 0xA4, 0xAC, 0x00, 0x00, 0x9B, 0x9C, 0x9E, 0x9C, 0x9C, 0xA4, 0xAC, 0x00,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0xA0, 0xAC, 0x00, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC,
+ 0x99, 0x99, 0x99, 0x99, 0x9C, 0x9C, 0xA4, 0xAC, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xA4, 0xAC, 0x00,
+ 0x99, 0x99, 0x9C, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC,
+ },
+ { // 13
+ 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x00,
+ 0x99, 0x9B, 0x9C, 0xA0, 0xA4, 0xAC, 0x00, 0x00, 0x99, 0x99, 0x9A, 0x99, 0x9C, 0xA0, 0xAC, 0x00,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA4, 0xAC, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC,
+ 0x99, 0x99, 0x99, 0x99, 0x9A, 0x9C, 0xA4, 0xAC, 0x99, 0x99, 0x99, 0x9A, 0x9C, 0xA4, 0xAC, 0x00,
+ },
+ { // 14
+ 0x00, 0x00, 0xAC, 0x9E, 0x9C, 0x9C, 0x9C, 0x9B, 0x00, 0xAC, 0x9C, 0xA0, 0x9E, 0xA4, 0xA4, 0xA4,
+ 0xAC, 0x9C, 0xA4, 0xAC, 0xAC, 0xAC, 0x9C, 0x9E, 0xAC, 0xA0, 0xAC, 0xA8, 0x9E, 0xA8, 0xAC, 0x99,
+ 0xAC, 0x9E, 0xAC, 0xA8, 0xAC, 0x9E, 0xA4, 0xAC, 0xAC, 0xA4, 0xA0, 0xAC, 0xAC, 0xA0, 0xA4, 0xAC,
+ 0x00, 0xAC, 0xA4, 0xA0, 0xA0, 0xA4, 0xAC, 0xA4, 0x00, 0x00, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC,
+ },
+ { // 15
+ 0x9C, 0x9C, 0x9C, 0x9B, 0x9C, 0x9C, 0x9C, 0x9B, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4,
+ 0x9E, 0x9E, 0x9E, 0x9C, 0x9E, 0x9E, 0x9E, 0x9E, 0x99, 0x99, 0x99, 0x99, 0x99, 0x98, 0x99, 0x98,
+ 0x9C, 0x9C, 0x9B, 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x9E, 0x9E, 0xA0,
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC,
+ },
+ { // 16
+ 0x9B, 0x9B, 0x9B, 0x9B, 0x9C, 0x9B, 0x9C, 0x9C, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4,
+ 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9E, 0x98, 0x98, 0x98, 0x98, 0x99, 0x99, 0x99, 0x99,
+ 0x9C, 0x9B, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0xA0, 0xA0, 0xA0, 0x9E, 0xA0, 0x9E, 0x9E, 0xA0,
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC,
+ },
+ { // 17
+ 0x9C, 0x9C, 0x9C, 0x9B, 0x9B, 0x9B, 0x9C, 0x9B, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4,
+ 0x9E, 0x9E, 0x9E, 0x9C, 0x9C, 0x9C, 0x9E, 0x9E, 0x98, 0x98, 0x98, 0x99, 0x9A, 0x9A, 0x99, 0x98,
+ 0x9C, 0x9B, 0x9C, 0x9C, 0x9C, 0x9B, 0x9B, 0x9C, 0xA0, 0x9E, 0x9E, 0xA0, 0xA0, 0xA0, 0xA0, 0x9E,
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC,
+ },
+ { // 18
+ 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0x9B, 0x9B, 0x9B, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4,
+ 0x9E, 0x9E, 0x9E, 0x9E, 0x9C, 0x9C, 0x9C, 0x9E, 0x98, 0x98, 0x98, 0x98, 0x9A, 0x9A, 0x98, 0x99,
+ 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9B, 0x9C, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0xA0, 0xA0, 0xA0,
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC,
+ },
+ { // 19
+ 0x9C, 0x9B, 0x9C, 0x9C, 0xA0, 0xA4, 0xAC, 0x00, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xAC, 0x00, 0x00,
+ 0x9E, 0x9E, 0x9C, 0x9C, 0x9E, 0xA0, 0xAC, 0x00, 0x99, 0x98, 0x98, 0x99, 0x9A, 0x9A, 0xA0, 0xAC,
+ 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0xA0, 0xAC, 0xA0, 0xA0, 0x9E, 0xA0, 0xA0, 0xA0, 0xA0, 0xAC,
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xAC, 0x00, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0x00, 0x00,
+ }
+};
+
+const byte TEXT_COLORS[40][4] = {
+ { 0x00, 0x19, 0x19, 0x19 },
+ { 0x00, 0x08, 0x08, 0x08 },
+ { 0x00, 0x0F, 0x0F, 0x0F },
+ { 0x00, 0x15, 0x15, 0x15 },
+ { 0x00, 0x01, 0x01, 0x01 },
+ { 0x00, 0x21, 0x21, 0x21 },
+ { 0x00, 0x26, 0x26, 0x26 },
+ { 0x00, 0x2B, 0x2B, 0x2B },
+ { 0x00, 0x31, 0x31, 0x31 },
+ { 0x00, 0x36, 0x36, 0x36 },
+ { 0x00, 0x3D, 0x3D, 0x3D },
+ { 0x00, 0x41, 0x41, 0x41 },
+ { 0x00, 0x46, 0x46, 0x46 },
+ { 0x00, 0x4C, 0x4C, 0x4C },
+ { 0x00, 0x50, 0x50, 0x50 },
+ { 0x00, 0x55, 0x55, 0x55 },
+ { 0x00, 0x5D, 0x5D, 0x5D },
+ { 0x00, 0x60, 0x60, 0x60 },
+ { 0x00, 0x65, 0x65, 0x65 },
+ { 0x00, 0x6C, 0x6C, 0x6C },
+ { 0x00, 0x70, 0x70, 0x70 },
+ { 0x00, 0x75, 0x75, 0x75 },
+ { 0x00, 0x7B, 0x7B, 0x7B },
+ { 0x00, 0x80, 0x80, 0x80 },
+ { 0x00, 0x85, 0x85, 0x85 },
+ { 0x00, 0x8D, 0x8D, 0x8D },
+ { 0x00, 0x90, 0x90, 0x90 },
+ { 0x00, 0x97, 0x97, 0x97 },
+ { 0x00, 0x9D, 0x9D, 0x9D },
+ { 0x00, 0xA4, 0xA4, 0xA4 },
+ { 0x00, 0xAB, 0xAB, 0xAB },
+ { 0x00, 0xB0, 0xB0, 0xB0 },
+ { 0x00, 0xB6, 0xB6, 0xB6 },
+ { 0x00, 0xBD, 0xBD, 0xBD },
+ { 0x00, 0xC0, 0xC0, 0xC0 },
+ { 0x00, 0xC6, 0xC6, 0xC6 },
+ { 0x00, 0xCD, 0xCD, 0xCD },
+ { 0x00, 0xD0, 0xD0, 0xD0 },
+ { 0x00, 0xD6, 0xD6, 0xD6 },
+ { 0x00, 0xDB, 0xDB, 0xDB },
+};
+
+const char *const DIRECTION_TEXT_UPPER[4] = { "NORTH", "EAST", "SOUTH", "WEST" };
+
+const char *const DIRECTION_TEXT[4] = { "North", "East", "South", "West" };
+
+const char *const RACE_NAMES[5] = { "Human", "Elf", "Dwarf", "Gnome", "H-Orc" };
+
+const int RACE_HP_BONUSES[5] = { 0, -2, 1, -1, 2 };
+
+const int RACE_SP_BONUSES[5][2] = {
+ { 0, 0 }, { 2, 0 }, { -1, -1 }, { 1, 1 }, { -2, -2 }
+};
+
+const char *const ALIGNMENT_NAMES[3] = { "Good", "Neutral", "Evil" };
+
+const char *const SEX_NAMES[2] = { "Male", "Female" };
+
+const char *const SKILL_NAMES[18] = {
+ "Thievery", "Arms Master", "Astrologer", "Body Builder", "Cartographer",
+ "Crusader", "Direction Sense", "Linguist", "Merchant", "Mountaineer",
+ "Navigator", "Path Finder", "Prayer Master", "Prestidigitator",
+ "Swimmer", "Tracker", "Spot Secret Door", "Danger Sense"
+};
+
+const char *const CLASS_NAMES[11] = {
+ "Knight", "Paladin", "Archer", "Cleric", "Sorcerer", "Robber",
+ "Ninja", "Barbarian", "Druid", "Ranger", nullptr
+};
+
+const uint CLASS_EXP_LEVELS[10] = {
+ 1500, 2000, 2000, 1500, 2000, 1000, 1500, 1500, 1500, 2000
+};
+
+const char *const CONDITION_NAMES[17] = {
+ "Cursed", "Heart Broken", "Weak", "Poisoned", "Diseased",
+ "Insane", "In Love", "Drunk", "Asleep", "Depressed", "Confused",
+ "Paralyzed", "Unconscious", "Dead", "Stone", "Eradicated", "Good"
+};
+
+const int CONDITION_COLORS[17] = {
+ 9, 9, 9, 9, 9, 9, 9, 9, 32, 32, 32, 32, 6, 6, 6, 6, 15
+};
+
+const char *const GOOD = "Good";
+
+const char *const BLESSED = "\n\t020Blessed\t095%+d";
+
+const char *const POWER_SHIELD = "\n\t020Power Shield\t095%+d";
+
+const char *const HOLY_BONUS = "\n\t020Holy Bonus\t095%+d";
+
+const char *const HEROISM = "\n\t020Heroism\t095%+d";
+
+const char *const IN_PARTY = "\014""15In Party\014""d";
+
+const char *const PARTY_DETAILS = "\015\003l\002\014""00"
+ "\013""001""\011""035%s"
+ "\013""009""\011""035%s"
+ "\013""017""\011""035%s"
+ "\013""025""\011""035%s"
+ "\013""001""\011""136%s"
+ "\013""009""\011""136%s"
+ "\013""017""\011""136%s"
+ "\013""025""\011""136%s"
+ "\013""044""\011""035%s"
+ "\013""052""\011""035%s"
+ "\013""060""\011""035%s"
+ "\013""068""\011""035%s"
+ "\013""044""\011""136%s"
+ "\013""052""\011""136%s"
+ "\013""060""\011""136%s"
+ "\013""068""\011""136%s";
+const char *const PARTY_DIALOG_TEXT =
+ "%s\x2\x3""c\v106\t013Up\t048Down\t083\f37D\fdel\t118\f37R\fdem"
+ "\t153\f37C\fdreate\t188E\f37x\fdit\x1";
+
+const int FACE_CONDITION_FRAMES[17] = {
+ 2, 2, 2, 1, 1, 4, 4, 4, 3, 2, 4, 3, 3, 5, 6, 7, 0
+};
+
+const int CHAR_FACES_X[6] = { 10, 45, 81, 117, 153, 189 };
+
+const int HP_BARS_X[6] = { 13, 50, 86, 122, 158, 194 };
+
+const char *const NO_ONE_TO_ADVENTURE_WITH = "You have no one to adventure with";
+
+const char *const YOUR_ROSTER_IS_FULL = "Your Roster is full!";
+
+const byte BACKGROUND_XLAT[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xF7, 0xFF, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xF9, 0xFF, 0x07, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xF7, 0xFF, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xF5, 0xFF, 0x0B, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xF3, 0xFF, 0x0D, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
+};
+
+const char *const PLEASE_WAIT = "\014""d\003""c\011""000"
+ "\013""002Please Wait...";
+
+const char *const OOPS = "\003""c\011""000\013""002Oops...";
+
+const int8 SCREEN_POSITIONING_X[4][48] = {
+ {
+ -1, 0, 0, 0, 1, -1, 0, 0, 0, 1, -2, -1,
+ -1, 0, 0, 0, 1, 1, 2, -4, -3, -3, -2, -2,
+ -1, -1, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4,
+ -3, -2, -1, 0, 0, 1, 2, 3, -4, 4, 0, 0
+ }, {
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 1
+ }, {
+ 1, 0, 0, 0, -1, 1, 0, 0, 0, -1, 2, 1,
+ 1, 0, 0, 0, -1, -1, -2, 4, 3, 3, 2, 2,
+ 1, 1, 0, 0, 0, -1, -1, -2, -2, -3, -3, -4,
+ 3, 2, 1, 0, 0, -1, -2, -3, 4, -4, 0, 0
+ }, {
+ 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -2, -2,
+ -2, -2, -2, -2, -2, -2, -2, -3, -3, -3, -3, -3,
+ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,
+ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, 0, -1
+ }
+};
+
+const int8 SCREEN_POSITIONING_Y[4][48] = {
+ {
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 1
+ }, {
+ 1, 0, 0, 0, -1, 1, 0, 0, 0, -1, 2, 1,
+ 1, 0, 0, 0, -1, -1, -2, 4, 3, 3, 2, 2,
+ 1, 1, 0, 0, 0, -1, -1, -2, -2, -3, -3, -4,
+ 3, 2, 1, 0, 0, -1, -2, -3, 4, -4, 0, 0
+ }, {
+ 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -2, -2,
+ -2, -2, -2, -2, -2, -2, -2, -3, -3, -3, -3, -3,
+ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,
+ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, 0, -1
+ }, {
+ -1, 0, 0, 0, 1, -1, 0, 0, 0, 1, -2, -1,
+ -1, 0, 0, 0, 1, 1, 2, -4, -3, -3, -2, -2,
+ -1, -1, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4,
+ -3, -2, -1, 0, 0, 1, 2, 3, -4, 4, 0, 0
+ }
+};
+
+const int MONSTER_GRID_BITMASK[12] = {
+ 0xC, 8, 4, 0, 0xF, 0xF000, 0xF00, 0xF0, 0xF00, 0xF0, 0x0F, 0xF000
+};
+
+const int INDOOR_OBJECT_X[2][12] = {
+ { 5, -7, -112, 98, -8, -65, 49, -9, -34, 16, -58, 40 },
+ { -35, -35, -142, 68, -35, -95, 19, -35, -62, -14, -98, 16 }
+};
+
+const int MAP_OBJECT_Y[2][12] = {
+ { 2, 25, 25, 25, 50, 50, 50, 58, 58, 58, 58, 58 },
+ { -65, -6, -6, -6, 36, 36, 36, 54, 54, 54, 54, 54 }
+};
+
+const int INDOOR_MONSTERS_Y[4] = { 2, 34, 53, 59 };
+
+const int OUTDOOR_OBJECT_X[2][12] = {
+ { -5, -7, -112, 98, -8, -77, 61, -9, -43, 25, -74, 56 },
+ { -35, -35, -142, 68, -35, -95, 19, -35, -62, -24, -98, 16 }
+};
+
+const int OUTDOOR_MONSTER_INDEXES[26] = {
+ 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 69, 70,
+ 71, 72, 73, 74, 75, 90, 91, 92, 93, 94, 112, 115, 118
+};
+
+const int OUTDOOR_MONSTERS_Y[26] = {
+ 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 53, 53,
+ 53, 53, 53, 53, 53, 34, 34, 34, 34, 34, 2, 2, 2
+};
+
+const int DIRECTION_ANIM_POSITIONS[4][4] = {
+ { 0, 1, 2, 3 }, { 3, 0, 1, 2 }, { 2, 3, 0, 1 }, { 1, 2, 3, 0 }
+};
+
+const byte WALL_SHIFTS[4][48] = {
+ {
+ 12, 0, 12, 8, 12, 12, 0, 12, 8, 12, 12, 0,
+ 12, 0, 12, 8, 12, 8, 12, 12, 0, 12, 0, 12,
+ 0, 12, 0, 12, 8, 12, 8, 12, 8, 12, 8, 12,
+ 0, 0, 0, 0, 8, 8, 8, 8, 0, 0, 4, 4
+ }, {
+ 8, 12, 8, 4, 8, 8, 12, 8, 4, 8, 8, 12,
+ 8, 12, 8, 4, 8, 4, 8, 8, 12, 8, 12, 8,
+ 12, 8, 12, 8, 4, 8, 4, 8, 4, 8, 4, 8,
+ 12, 12, 12, 12, 4, 4, 4, 4, 0, 0, 0, 0
+ }, {
+ 4, 8, 4, 0, 4, 4, 8, 4, 0, 4, 4, 8,
+ 4, 8, 4, 0, 4, 0, 4, 4, 8, 4, 8, 4,
+ 8, 4, 8, 4, 0, 4, 0, 4, 0, 4, 0, 4,
+ 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 12, 12
+ }, {
+ 0, 4, 0, 12, 0, 0, 4, 0, 12, 0, 0, 4,
+ 0, 4, 0, 12, 0, 12, 0, 0, 4, 0, 4, 0,
+ 4, 0, 4, 0, 12, 0, 12, 0, 12, 0, 12, 0,
+ 4, 4, 4, 4, 12, 12, 12, 12, 0, 0, 8, 8
+ }
+};
+
+const int DRAW_NUMBERS[25] = {
+ 36, 37, 38, 43, 42, 41,
+ 39, 20, 22, 24, 33, 31,
+ 29, 26, 10, 11, 18, 16,
+ 13, 5, 9, 6, 0, 4, 1
+};
+
+const int DRAW_FRAMES[25][2] = {
+ { 18, 24 }, { 19, 23 }, { 20, 22 }, { 24, 18 }, { 23, 19 }, { 22, 20 },
+ { 21, 21 }, { 11, 17 }, { 12, 16 }, { 13, 15 }, { 17, 11 }, { 16, 12 },
+ { 15, 13 }, { 14, 14 }, { 6, 10 }, { 7, 9 }, { 10, 6 }, { 9, 7 },
+ { 8, 8 }, { 3, 5 }, { 5, 3 }, { 4, 4 }, { 0, 2 }, { 2, 0 },
+ { 1, 1 }
+};
+
+const int COMBAT_FLOAT_X[8] = { -2, -1, 0, 1, 2, 1, 0, -1 };
+
+const int COMBAT_FLOAT_Y[8] = { -2, 0, 2, 0, -1, 0, 2, 0 };
+
+const int MONSTER_EFFECT_FLAGS[15][8] = {
+ { 0x104, 0x105, 0x106, 0x107, 0x108, 0x109, 0x10A, 0x10B },
+ { 0x10C, 0x10D, 0x10E, 0x10F, 0x0, 0x0, 0x0, 0x0 },
+ { 0x110, 0x111, 0x112, 0x113, 0x0, 0x0, 0x0, 0x0 },
+ { 0x114, 0x115, 0x116, 0x117, 0x0, 0x0, 0x0, 0x0 },
+ { 0x200, 0x201, 0x202, 0x203, 0x0, 0x0, 0x0, 0x0 },
+ { 0x300, 0x301, 0x302, 0x303, 0x400, 0x401, 0x402, 0x403 },
+ { 0x500, 0x501, 0x502, 0x503, 0x0, 0x0, 0x0, 0x0 },
+ { 0x600, 0x601, 0x602, 0x603, 0x0, 0x0, 0x0, 0x0 },
+ { 0x604, 0x605, 0x606, 0x607, 0x608, 0x609, 0x60A, 0x60B },
+ { 0x60C, 0x60D, 0x60E, 0x60F, 0x0, 0x0, 0x0, 0x0 },
+ { 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100 },
+ { 0x101, 0x101, 0x101, 0x101, 0x101, 0x101, 0x101, 0x101 },
+ { 0x102, 0x102, 0x102, 0x102, 0x102, 0x102, 0x102, 0x102 },
+ { 0x103, 0x103, 0x103, 0x103, 0x103, 0x103, 0x103, 0x103 },
+ { 0x108, 0x108, 0x108, 0x108, 0x108, 0x108, 0x108, 0x108 }
+};
+
+const uint SPELLS_ALLOWED[3][40] = {
+ {
+ 0, 1, 2, 3, 5, 6, 7, 8, 9, 10,
+ 12, 14, 16, 23, 26, 27, 28, 30, 31, 32,
+ 33, 42, 46, 48, 49, 50, 52, 55, 56, 58,
+ 59, 62, 64, 65, 67, 68, 71, 73, 74, 76
+ }, {
+ 1, 4, 11, 13, 15, 17, 18, 19, 20, 21,
+ 22, 24, 25, 29, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 47, 51, 53, 54,
+ 57, 60, 61, 63, 66, 69, 70, 72, 75, 76
+ }, {
+ 0, 1, 2, 3, 4, 5, 7, 9, 10, 20,
+ 25, 26, 27, 28, 30, 31, 34, 38, 40, 41,
+ 42, 43, 44, 45, 49, 50, 52, 53, 55, 59,
+ 60, 61, 62, 67, 68, 72, 73, 74, 75, 76
+ }
+};
+
+const int BASE_HP_BY_CLASS[10] = { 10, 8, 7, 5, 4, 8, 7, 12, 6, 9 };
+
+const int AGE_RANGES[10] = { 1, 6, 11, 18, 36, 51, 76, 101, 201, 0xffff };
+
+const int AGE_RANGES_ADJUST[2][10] = {
+ { -250, -50, -20, -10, 0, -2, -5, -10, -20, -50 },
+ { -250, -50, -20, -10, 0, 2, 5, 10, 20, 50 }
+};
+
+const uint STAT_VALUES[24] = {
+ 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 25, 30, 35, 40,
+ 50, 75, 100, 125, 150, 175, 200, 225, 250,
+};
+
+const int STAT_BONUSES[24] = {
+ -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20
+};
+
+const int ELEMENTAL_CATEGORIES[6] = { 8, 15, 20, 25, 33, 36 };
+
+const int ATTRIBUTE_CATEGORIES[10] = {
+ 9, 17, 25, 33, 39, 45, 50, 56, 61, 72 };
+
+const int ATTRIBUTE_BONUSES[72] = {
+ 2, 3, 5, 8, 12, 17, 23, 30, 38, 47, // Might bonus
+ 2, 3, 5, 8, 12, 17, 23, 30, // INT bonus
+ 2, 3, 5, 8, 12, 17, 23, 30, // PER bonus
+ 2, 3, 5, 8, 12, 17, 23, 30, // SPD bonus
+ 3, 5, 10, 15, 20, 30, // ACC bonus
+ 5, 10, 15, 20, 25, 30, // LUC bonus
+ 4, 6, 10, 20, 50, // HP bonus
+ 4, 8, 12, 16, 20, 25, // SP bonus
+ 2, 4, 6, 10, 16, // AC bonus
+ 4, 6, 8, 10, 12, 14, 16, 18, 20, 25 // Thievery bonus
+};
+
+const int ELEMENTAL_RESISTENCES[37] = {
+ 0, 5, 7, 9, 12, 15, 20, 25, 30, 5, 7, 9, 12, 15, 20, 25,
+ 5, 10, 15, 20, 25, 10, 15, 20, 25, 40, 5, 7, 9, 11, 13, 15, 20, 25,
+ 5, 10, 20
+};
+
+const int ELEMENTAL_DAMAGE[37] = {
+ 0, 2, 3, 4, 5, 10, 15, 20, 30, 2, 3, 4, 5, 10, 15, 20, 2, 4, 5, 10, 20,
+ 2, 4, 8, 16, 32, 2, 3, 4, 5, 10, 15, 20, 30, 5, 10, 25
+};
+
+const int WEAPON_DAMAGE_BASE[35] = {
+ 0, 3, 2, 3, 2, 2, 4, 1, 2, 4, 2, 3,
+ 2, 2, 1, 1, 1, 1, 4, 4, 3, 2, 4, 2,
+ 2, 2, 5, 3, 3, 3, 3, 5, 4, 2, 6
+};
+
+const int WEAPON_DAMAGE_MULTIPLIER[35] = {
+ 0, 3, 3, 4, 5, 4, 2, 3, 3, 3, 3, 3,
+ 2, 4, 10, 6, 8, 9, 4, 3, 6, 8, 5, 6,
+ 4, 5, 3, 5, 6, 7, 2, 2, 2, 2, 4
+};
+
+const int METAL_DAMAGE[22] = {
+ -3, -6, -4, -2, 2, 4, 6, 8, 10, 0, 1,
+ 1, 2, 2, 3, 4, 5, 12, 15, 20, 30, 50
+};
+
+const int METAL_DAMAGE_PERCENT[22] = {
+ 253, 252, 3, 2, 1, 2, 3, 4, 6, 0, 1,
+ 1, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10
+};
+
+const int METAL_LAC[9] = { -3, 0, -2, -1, 1, 2, 4, 6, 8 };
+
+const int ARMOR_STRENGTHS[14] = { 0, 2, 4, 5, 6, 7, 8, 10, 4, 2, 1, 1, 1, 1 };
+
+const int MAKE_ITEM_ARR1[6] = { 0, 8, 15, 20, 25, 33 };
+
+const int MAKE_ITEM_ARR2[6][7][2] = {
+ { { 0, 0 }, { 1, 3 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 5, 8 }, { 8, 8 } },
+ { { 0, 0 }, { 1, 3 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 6, 7 }, { 7, 7 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 5 }, { 4, 5 }, { 5, 5 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 4 }, { 4, 5 }, { 5, 5 } },
+ { { 0, 0 }, { 1, 3 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 5, 8 }, { 8, 8 } },
+ { { 0, 0 }, { 1, 1 }, { 1, 1 }, { 1, 2 }, { 2, 2 }, { 2, 3 }, { 3, 3 } }
+};
+
+const int MAKE_ITEM_ARR3[10][7][2] = {
+ { { 0, 0 }, { 1, 4 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 6, 10 }, { 10, 10 } },
+ { { 0, 0 }, { 1, 3 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 5, 8 }, { 8, 8 } },
+ { { 0, 0 }, { 1, 3 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 5, 8 }, { 8, 8 } },
+ { { 0, 0 }, { 1, 3 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 5, 8 }, { 8, 8 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 5 }, { 4, 6 }, { 6, 6 } },
+ { { 0, 0 }, { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 5 }, { 5, 6 }, { 6, 6 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 4 }, { 4, 5 }, { 5, 5 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 5 }, { 4, 6 }, { 6, 6 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 4 }, { 4, 5 }, { 5, 5 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 4 }, { 3, 6 }, { 5, 8 }, { 7, 10 }, { 10, 10 } }
+};
+
+const int MAKE_ITEM_ARR4[2][7][2] = {
+ { { 0, 0 }, { 1, 4 }, { 3, 7 }, { 4, 8 }, { 5, 9 }, { 8, 9 }, { 9, 9 } },
+ { { 0, 0 }, { 1, 4 }, { 2, 6 }, { 4, 7 }, { 6, 10 }, { 9, 13 }, { 13, 13 } }
+};
+
+
+const int MAKE_ITEM_ARR5[8][2] = {
+ { 0, 0 }, { 1, 15 }, { 16, 30 }, { 31, 40 }, { 41, 50 },
+ { 51, 60 }, { 61, 73 }, { 61, 73 }
+};
+
+const int OUTDOOR_DRAWSTRCT_INDEXES[44] = {
+ 37, 38, 39, 40, 41, 44, 42, 43, 47, 45, 46,
+ 48, 49, 52, 50, 51, 66, 67, 68, 69, 70, 71,
+ 72, 75, 73, 74, 87, 88, 89, 90, 91, 94, 92,
+ 93, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120
+};
+
+const int TOWN_MAXES[2][11] = {
+ { 23, 13, 32, 16, 26, 16, 16, 16, 16, 16, 16 },
+ { 26, 19, 48, 27, 26, 37, 16, 16, 16, 16, 16 }
+};
+
+const char *const TOWN_ACTION_MUSIC[14] = {
+ "bank.m", "smith.m", "guild.m", "tavern.m", "temple.m",
+ "grounds.m", "endgame.m", "bank.m", "sf09.m", "guild.m",
+ "tavern.m", "temple.m", "smith.m", "endgame.m"
+};
+
+const char *const TOWN_ACTION_SHAPES[4] = {
+ "bankr", "blck", "gild", "tvrn"
+};
+
+const int TOWN_ACTION_FILES[2][7] = {
+ { 3, 2, 4, 2, 4, 2, 1 }, { 5, 3, 7, 5, 4, 6, 1 }
+};
+
+const char *const BANK_TEXT = "\x0D\x02\x03""c\x0B""122\x09""013"
+ "\x0C""37D\x0C""dep\x09""040\x0C""37W\x0C""dith\x09""067ESC"
+ "\x01\x09""000\x0B""000Bank of Xeen\x0B""015\n"
+ "Bank\x03l\n"
+ "Gold\x03r\x09""000%s\x03l\n"
+ "Gems\x03r\x09""000%s\x03""c\n"
+ "\n"
+ "Party\x03l\n"
+ "Gold\x03r\x09""000%s\x03l\n"
+ "Gems\x03r\x09""000%s";
+
+const char *const BLACKSMITH_TEXT = "\x01\x0D\x03""c\x0B""000\x09""000"
+ "Store Options for\x09""039\x0B""027%s\x03""l\x0B""046\n"
+ "\x09""011\x0C""37B\x0C""drowse\n"
+ "\x09""000\x0B""090Gold\x03r\x09""000%s"
+ "\x02\x03""c\x0B""122\x09""040ESC\x01";
+
+const char *const GUILD_NOT_MEMBER_TEXT =
+ "\n\nYou have to be a member to shop here.";
+
+const char *const GUILD_TEXT = "\x03""c\x0B""027\x09""039%s"
+ "\x03l\x0B""046\n"
+ "\x09""012\x0C""37B\x0C""duy Spells\n"
+ "\x09""012\x0C""37S\x0C""dpell Info";
+
+const char *const TAVERN_TEXT =
+ "\x0D\x03""c\x0B""000\x09""000Tavern Options for\x09""039"
+ "\x0B""027%s%s\x03l\x09""000"
+ "\x0B""090Gold\x03r\x09""000%s\x02\x03""c\x0B""122"
+ "\x09""021\x0C""37S\x0C""dign in\x09""060ESC\x01";
+
+const char *const FOOD_AND_DRINK =
+ "\x03l\x09""017\x0B""046\x0C""37D\x0C""drink\n"
+ "\x09""017\x0C""37F\x0C""dood\n"
+ "\x09""017\x0C""37T\x0C""dip\n"
+ "\x09""017\x0C""37R\x0C""dumors";
+
+const char *const GOOD_STUFF = "\n"
+ "\n"
+ "Good Stuff\n"
+ "\n"
+ "Hit a key!";
+
+const char *const HAVE_A_DRINK = "\n\nHave a Drink\n\nHit a key!";
+
+const char *const YOURE_DRUNK = "\n\nYou're Drunk\n\nHit a key!";
+
+const int TAVERN_EXIT_LIST[2][6][5][2] = {
+ {
+ { { 21, 17 }, { 0, 0 }, { 20, 3 }, { 0, 0 }, { 0, 0 } },
+ { { 13, 4 }, { 0, 0 }, { 19, 9 }, { 0, 0 }, { 0, 0 } },
+ { { 20, 10 }, { 12, 8 }, { 5, 26 }, { 3, 4 }, { 7, 5 } },
+ { { 18, 4 }, { 0, 0 }, { 19, 16 }, { 0, 0 }, { 11, 12 } },
+ { { 15, 21 }, { 0, 0 }, { 13, 21 }, { 0, 0 }, { 0, 0 } },
+ { { 10, 8 }, { 0, 0 }, { 15, 12 }, { 0, 0 }, { 0, 0 } },
+ }, {
+ { { 21, 17 }, { 0, 0 }, { 20, 3 }, { 0, 0 }, { 0, 0 } },
+ { { 13, 4 }, { 0, 0 }, { 19, 9 }, { 0, 0 }, { 0, 0 } },
+ { { 20, 10 }, { 12, 8 }, { 5, 26 }, { 3, 4 }, { 7, 5 } },
+ { { 17, 24 }, { 14, 13 }, { 0, 0 }, { 0, 0 }, { 9, 4 } },
+ { { 15, 21 }, { 0, 0 }, { 13, 21 }, { 0, 0 }, { 0, 0 } },
+ { { 10, 8 }, { 0, 0 }, { 15, 12 }, { 0, 0 }, { 0, 0 } }
+ }
+};
+
+const char *const TEMPLE_TEXT =
+ "\x0D\x03""c\x0B""000\x09""000Temple Options for"
+ "\x09""039\x0B""027%s\x03l\x09""000\x0B""046"
+ "\x0C""37H\x0C""deal\x03r\x09""000%lu\x03l\n"
+ "\x0C""37D\x0C""donation\x03r\x09""000%lu\x03l\n"
+ "\x0C""37U\x0C""dnCurse\x03r\x09""000%s"
+ "\x03l\x09""000\x0B""090Gold\x03r\x09""000%s"
+ "\x02\x03""c\x0B""122\x09""040ESC\x01";
+
+const char *const EXPERIENCE_FOR_LEVEL =
+ "%s needs %lu experience for level %u.";
+
+const char *const LEARNED_ALL = "%s has learned all we can teach!";
+
+const char *const ELIGIBLE_FOR_LEVEL = "%s is eligible for level %d.";
+
+const char *const TRAINING_TEXT =
+ "\x0D\x03""cTraining Options\n"
+ "\n"
+ "%s\x03l\x0B""090\x09""000Gold\x03r\x09"
+ "000%s\x02\x03""c\x0B""122\x09""021"
+ "\x0C""37T\x0C""drain\x09""060ESC\x01";
+
+const char *const GOLD_GEMS =
+ "\x03""c\x0B""000\x09""000%s\x03l\n"
+ "\n"
+ "Gold\x03r\x09""000%s\x03l\n"
+ "Gems\x03r\x09""000%s\x02\x03""c\x0B""096\x09""013G"
+ "\x0C""37o\x0C""dld\x09""040G\x0C\x03""7e"
+ "\x0C""dms\x09""067ESC\x01";
+
+const char *const GOLD_GEMS_2 =
+ "\x09""000\x0B""000\x03""c%s\x03l\n"
+ "\n"
+ "\x04""077Gold\x03r\x09""000%s\x03l\n"
+ "\x04""077Gems\x03r\x09""000%s\x03l\x09""000\x0B""051\x04""077\n"
+ "\x04""077";
+
+const char *const DEPOSIT_WITHDRAWL[2] = { "Deposit", "Withdrawl" };
+
+const char *const NOT_ENOUGH_X_IN_THE_Y =
+ "\x03""c\x0B""012Not enough %s in the %s!\x03l";
+
+const char *const NO_X_IN_THE_Y = "\x03""c\x0B""012No %s in the %s!\x03l";
+
+const char *const STAT_NAMES[16] = {
+ "Might", "Intellect", "Personality", "Endurance", "Speed",
+ "Accuracy", "Luck", "Age", "Level", "Armor Class", "Hit Points",
+ "Spell Points", "Resistances", "Skills", "Awards", "Experience"
+};
+
+const char *const CONSUMABLE_NAMES[4] = { "Gold", "Gems", "Food", "Condition" };
+
+const char *const WHERE_NAMES[2] = { "Party", "Bank" };
+
+const char *const AMOUNT = "\x03""c\x09""000\x0B""051Amount\x03l\n";
+
+const char *const FOOD_PACKS_FULL = "\v007Your food packs are already full!";
+
+const char *const BUY_SPELLS =
+ "\x03""c\x0B""027\x09""039%s\x03l\x0B""046\n"
+ "\x09""012\x0C""37B\x0C""duy Spells\n"
+ "\x09""012\x0C""37S\x0C""dpell Info";
+
+const char *const GUILD_OPTIONS =
+ "\x0D\x0C""00\x03""c\x0B""000\x09""000Guild Options for%s"
+ "\x03l\x09""000\x0B""090Gold"
+ "\x03r\x09""000%s\x02\x03""c\x0B""122\x09""040ESC\x01";
+
+const int MISC_SPELL_INDEX[74] = {
+ NO_SPELL, MS_Light, MS_Awaken, MS_MagicArrow,
+ MS_FirstAid, MS_FlyingFist, MS_EnergyBlast, MS_Sleep,
+ MS_Revitalize, MS_CureWounds, MS_Sparks, MS_Shrapmetal,
+ MS_InsectSpray, MS_ToxicCloud, MS_ProtFromElements, MS_Pain,
+ MS_Jump, MS_BeastMaster, MS_Clairvoyance, MS_TurnUndead,
+ MS_Levitate, MS_WizardEye, MS_Bless, MS_IdentifyMonster,
+ MS_LightningBolt, MS_HolyBonus, MS_PowerCure, MS_NaturesCure,
+ MS_LloydsBeacon, MS_PowerShield, MS_Heroism, MS_Hynotize,
+ MS_WalkOnWater, MS_FrostBite, MS_DetectMonster, MS_Fireball,
+ MS_ColdRay, MS_CurePoison, MS_AcidSpray, MS_TimeDistortion,
+ MS_DragonSleep, MS_CureDisease, MS_Teleport, MS_FingerOfDeath,
+ MS_CureParalysis, MS_GolemStopper, MS_PoisonVolley, MS_DeadlySwarm,
+ MS_SuperShelter, MS_DayOfProtection, MS_DayOfSorcery, MS_CreateFood,
+ MS_FieryFlail, MS_RechargeItem, MS_FantasticFreeze, MS_TownPortal,
+ MS_StoneToFlesh, MS_RaiseDead, MS_Etheralize, MS_DancingSword,
+ MS_MoonRay, MS_MassDistortion, MS_PrismaticLight, MS_EnchantItem,
+ MS_Incinerate, MS_HolyWord, MS_Resurrection, MS_ElementalStorm,
+ MS_MegaVolts, MS_Inferno, MS_SunRay, MS_Implosion,
+ MS_StarBurst, MS_DivineIntervention
+};
+
+const int SPELL_COSTS[77] = {
+ 8, 1, 5, -2, 5, -2, 20, 10, 12, 8, 3,
+ - 3, 75, 40, 12, 6, 200, 10, 100, 30, -1, 30,
+ 15, 25, 10, -2, 1, 2, 7, 20, -2, -2, 100,
+ 15, 5, 100, 35, 75, 5, 20, 4, 5, 1, -2,
+ 6, 2, 75, 40, 60, 6, 4, 25, -2, -2, 60,
+ - 1, 50, 15, 125, 2, -1, 3, -1, 200, 35, 150,
+ 15, 5, 4, 10, 8, 30, 4, 5, 7, 5, 0
+};
+
+const int DARK_SPELL_RANGES[12][2] = {
+ { 0, 20 }, { 16, 35 }, { 27, 37 }, { 29, 39 },
+ { 0, 17 }, { 14, 34 }, { 26, 37 }, { 29, 39 },
+ { 0, 20 }, { 16, 35 }, { 27, 37 }, { 29, 39 }
+};
+
+const int CLOUDS_SPELL_OFFSETS[5][20] = {
+ {
+ 1, 10, 20, 26, 27, 38, 40, 42, 45, 50,
+ 55, 59, 60, 61, 62, 68, 72, 75, 77, 77
+ }, {
+ 3, 4, 5, 14, 15, 25, 30, 31, 34, 41,
+ 49, 51, 53, 67, 73, 75, -1, -1, -1, -1
+ }, {
+ 4, 8, 9, 12, 13, 22, 23, 24, 28, 34,
+ 41, 44, 52, 70, 73, 74, -1, -1, -1, -1
+ }, {
+ 6, 7, 9, 11, 12, 13, 17, 21, 22, 24,
+ 29, 36, 56, 58, 64, 71, -1, -1, -1, -1
+ }, {
+ 6, 7, 9, 11, 12, 13, 18, 21, 29, 32,
+ 36, 37, 46, 51, 56, 58, 69, -1, -1, -1
+ }
+};
+
+const uint DARK_SPELL_OFFSETS[3][39] = {
+ {
+ 42, 1, 26, 59, 27, 10, 50, 68, 55, 62, 67, 73, 2,
+ 5, 3, 31, 30, 52, 49, 28, 74, 0, 9, 7, 14, 8,
+ 33, 6, 23, 71, 64, 56, 48, 46, 12, 32, 58, 65, 16
+ }, {
+ 42, 1, 45, 61, 72, 40, 20, 60, 38, 41, 75, 34, 4,
+ 43, 25, 53, 44, 15, 70, 17, 24, 69, 22, 66, 57, 11,
+ 29, 39, 51, 21, 19, 36, 47, 13, 54, 37, 18, 35, 63
+ }, {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+ 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38
+ }
+};
+
+const int SPELL_GEM_COST[77] = {
+ 0, 0, 2, 1, 2, 4, 5, 0, 0, 0, 0, 10, 10, 10, 0, 0, 20, 4, 10, 20, 1, 10,
+ 5, 5, 4, 2, 0, 0, 0, 10, 3, 1, 20, 4, 0, 20, 10, 10, 1, 10, 0, 0, 0, 2,
+ 2, 0, 10, 10, 10, 0, 0, 10, 3, 2, 10, 1, 10, 10, 20, 0, 0, 1, 1, 20, 5, 20,
+ 5, 0, 0, 0, 0, 5, 1, 2, 0, 2, 0
+};
+
+const char *const NOT_A_SPELL_CASTER = "Not a spell caster...";
+
+const char *const SPELLS_FOR = "\xD\xC""d%s\x2\x3""c\x9""000\xB""002Spells for %s";
+
+const char *const SPELL_LINES_0_TO_9 =
+ "\x2\x3l\xB""015\x9""0011\n2\n3\n4\n5\n6\n7\n8\n9\n0";
+
+const char *const SPELLS_DIALOG_SPELLS = "\x3l\xB""015"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l"
+ "\x9""004\xB""110%s - %lu\x1";
+
+const char *const SPELL_PTS = "Spell Pts";
+
+const char *const GOLD = "Gold";
+
+const char *const SPELLS_PRESS_A_KEY =
+ "\x3""c\xC""09%s\xC""d\x3l\n"
+ "\n"
+ "%s\x3""c\x9""000\xB""100Press a Key!";
+
+const char *const SPELLS_PURCHASE =
+ "\x3l\xB""000\x9""000\xC""d%s Do you wish to purchase "
+ "\xC""09%s\xC""d for %u?";
+
+const char *const MAP_TEXT =
+ "\x3""c\xB""000\x9""000%s\x3l\xB""139"
+ "\x9""000X = %d\x3r\x9""000Y = %d\x3""c\x9""000%s";
+
+const char *const LIGHT_COUNT_TEXT = "\x3l\n\n\t024Light\x3r\t124%d";
+
+const char *const FIRE_RESISTENCE_TEXT = "%c%sFire%s%u";
+
+const char *const ELECRICITY_RESISTENCE_TEXT = "%c%sElectricity%s%u";
+
+const char *const COLD_RESISTENCE_TEXT = "c%sCold%s%u";
+
+const char *const POISON_RESISTENCE_TEXT = "%c%sPoison/Acid%s%u";
+
+const char *const CLAIRVOYANCE_TEXT = "%c%sClairvoyance%s";
+
+const char *const LEVITATE_TEXT = "%c%sLevitate%s";
+
+const char *const WALK_ON_WATER_TEXT = "%c%sWalk on Water";
+
+const char *const GAME_INFORMATION =
+ "\xD\x3""c\x9""000\xB""001\xC""37%s of Xeen\xC""d\n"
+ "Game Information\n"
+ "\n"
+ "Today is \xC""37%ssday\xC""d\n"
+ "\n"
+ "\x9""032Time\x9""072Day\x9""112Year\n"
+ "\x9""032\xC""37%d:%02d%c\x9""072%u\x9""112%u\xC""d%s";
+
+const char *const WORLD_GAME_TEXT = "World";
+const char *const DARKSIDE_GAME_TEXT = "Darkside";
+const char *const CLOUDS_GAME_TEXT = "Clouds";
+const char *const SWORDS_GAME_TEXT = "Swords";
+
+const char *const WEEK_DAY_STRINGS[10] = {
+ "Ten", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"
+};
+
+const char *const CHARACTER_DETAILS =
+ "\x3l\xB""041\x9""196%s\x9""000\xB""002%s : %s %s %s"
+ "\x3r\x9""053\xB""028\xC%02u%u\xC""d\x9""103\xC""%02u%u\xC""d"
+ "\x3l\x9""131\xC""%02u%d\xC""d\x9""196\xC""15%lu\xC""d\x3r"
+ "\x9""053\xB""051\xC""%02u%u\xC""d\x9""103\xC""%02u%u\xC""d"
+ "\x3l\x9""131\xC""%02u%u\xC""d\x9""196\xC""15%lu\xC""d"
+ "\x3r\x9""053\xB""074\xC""%02u%u\xC""d\x9""103\xC""%02u%u\xC""d"
+ "\x3l\x9""131\xC""15%u\xC""d\x9""196\xC""15%lu\xC""d"
+ "\x3r\x9""053\xB""097\xC""%02u%u\xC""d\x9""103\xC""%02u%u\xC""d"
+ "\x3l\x9""131\xC""15%u\xC""d\x9""196\xC""15%u day%c\xC""d"
+ "\x3r\x9""053\xB""120\xC""%02u%u\xC""d\x9""103\xC""%02u%u\xC""d"
+ "\x3l\x9""131\xC""15%u\xC""d\x9""196\xC""%02u%s\xC""d"
+ "\x9""230%s%s%s%s\xC""d";
+
+const char *const PARTY_GOLD = "Party Gold";
+
+const char *const PLUS_14 = "14+";
+
+const char *const CHARACTER_TEMPLATE =
+ "\x1\xC""00\xD\x3l\x9""029\xB""018Mgt\x9""080Acy\x9""131H.P.\x9""196Experience"
+ "\x9""029\xB""041Int\x9""080Lck\x9""131S.P.\x9""029\xB""064Per\x9""080Age"
+ "\x9""131Resis\x9""196Party Gems\x9""029\xB""087End\x9""080Lvl\x9""131Skills"
+ "\x9""196Party Food\x9""029\xB""110Spd\x9""080AC\x9""131Awrds\x9""196Condition\x3""c"
+ "\x9""290\xB""025\xC""37I\xC""dtem\x9""290\xB""057\xC""37Q"
+ "\xC""duick\x9""290\xB""089\xC""37E\xC""dxch\x9""290\xB""121Exit\x3l%s";
+
+const char *const EXCHANGING_IN_COMBAT = "\x3""c\xB""007\x9""000Exchanging in combat is not allowed!";
+
+const char *const CURRENT_MAXIMUM_RATING_TEXT = "\x2\x3""c%s\n"
+ "Current / Maximum\n"
+ "\x3r\x9""054%lu\x3l\x9""058/ %lu\n"
+ "\x3""cRating: %s";
+
+const char *const CURRENT_MAXIMUM_TEXT = "\x2\x3""c%s\n"
+ "Current / Maximum\n"
+ "\x3r\x9""054%u\x3l\x9""058/ %u";
+
+const char *const RATING_TEXT[24] = {
+ "Nonexistant", "Very Poor", "Poor", "Very Low", "Low", "Averarage", "Good",
+ "Very Good", "High", "Very High", "Great", "Super", "Amazing", "Incredible",
+ "Gigantic", "Fantastic", "Astoundig", "Astonishing", "Monumental", "Tremendous",
+ "Collosal", "Awesome", "AweInspiring", "aUltimate"
+};
+
+const char *const AGE_TEXT = "\x2\x3""c%s\n"
+ "Current / Natural\n"
+ "\x3r\x9""057%u\x3l\x9""061/ %u\n"
+ "\x3""cBorn: %u / %u\x1";
+
+const char *const LEVEL_TEXT =
+ "\x2\x3""c%s\n"
+ "Current / Maximum\n"
+ "\x3r\x9""054%u\x3l\x9""058/ %u\n"
+ "\x3""c%u Attack%s/Round\x1";
+
+const char *const RESISTENCES_TEXT =
+ "\x2\x3""c%s\x3l\n"
+ "\x9""020Fire\x9""100%u\n"
+ "\x9""020Cold\x9""100%u\n"
+ "\x9""020Electricity\x9""100%u\n"
+ "\x9""020Poison\x9""100%u\n"
+ "\x9""020Energy\x9""100%u\n"
+ "\x9""020Magic\x9""100%u";
+
+const char *const NONE = "\n\x9""020";
+
+const char *const EXPERIENCE_TEXT = "\x2\x3""c%s\x3l\n"
+ "\x9""010Current:\x9""070%lu\n"
+ "\x9""010Next Level:\x9""070%s\x1";
+
+const char *const ELIGIBLE = "\xC""12Eligible\xC""d";
+
+const char *const IN_PARTY_IN_BANK =
+ "\x2\x3""cParty %s\n"
+ "%lu on hand\n"
+ "%lu in bank\x1\x3l";
+
+const char *const FOOD_TEXT =
+ "\x2\x3""cParty %s\n"
+ "%u on hand\n"
+ "Enough for %u day%s\x3l";
+
+const char *const EXCHANGE_WITH_WHOM = "\t010\v005Exchange with whom?";
+
+const char *const QUICK_REF_LINE =
+ "\xB%3d\x9""007%u)\x9""027%s\x9""110%c%c%c\x3r\x9""160\xC%02u%u\xC""d"
+ "\x3l\x9""170\xC%02u%d\xC""d\x9""208\xC%02u%u\xC""d\x9""247\xC"
+ "%02u%u\xC""d\x9""270\xC%02u%c%c%c%c\xC""d";
+
+const char *const QUICK_REFERENCE =
+ "\xD\x3""cQuick Reference Chart\xB""012\x3l"
+ "\x9""007#\x9""027Name\x9""110Cls\x9""140Lvl\x9""176H.P."
+ "\x9""212S.P.\x9""241A.C.\x9""270Cond"
+ "%s%s%s%s%s%s%s%s"
+ "\xB""110\x9""064\x3""cGold\x9""144Gems\x9""224Food\xB""119"
+ "\x9""064\xC""15%lu\x9""144%lu\x9""224%u day%s\xC""d";
+
+const uint BLACKSMITH_MAP_IDS[2][4] = { { 28, 30, 73, 49 }, { 29, 31, 37, 43 } };
+
+const char *const ITEMS_DIALOG_TEXT1 =
+ "\r\x2\x3""c\v021\t017\f37W\fdeap\t051\f37A\fdrmor\t085A"
+ "\f37c\fdces\t119\f37M\fdisc\t153%s\t187%s\t221%s"
+ "\t255%s\t289Exit";
+const char *const ITEMS_DIALOG_TEXT2 =
+ "\r\x2\x3""c\v021\t017\f37W\fdeap\t051\f37A\fdrmor\t085A"
+ "\f37c\fdces\t119\f37M\fdisc\t153\f37%s\t289Exit";
+const char *const ITEMS_DIALOG_LINE1 = "\x3r\f%02u\f023%2d)\x3l\t028%s\n";
+const char *const ITEMS_DIALOG_LINE2 = "\x3r\f%02u\t023%2d)\x3l\t028%s\x3r\t000%lu\n";
+
+const char *const BTN_BUY = "\f37B\fduy";
+const char *const BTN_SELL = "\f37S\fdell";
+const char *const BTN_IDENTIFY = "\f37I\fddentify";
+const char *const BTN_FIX = "\f37F\fdix";
+const char *const BTN_USE = "\f37U\fdse";
+const char *const BTN_EQUIP = "\f37E\fdquip";
+const char *const BTN_REMOVE = "\f37R\fdem";
+const char *const BTN_DISCARD = "\f37D\fdisc";
+const char *const BTN_QUEST = "\f37Q\fduest";
+const char *const BTN_ENCHANT = "E\fdnchant";
+const char *const BTN_RECHARGE = "R\fdechrg";
+const char *const BTN_GOLD = "G\fdold";
+
+const char *const ITEM_BROKEN = "\f32broken ";
+const char *const ITEM_CURSED = "\f09cursed ";
+const char *const BONUS_NAMES[7] = {
+ "", "Dragon Slayer", "Undead Eater", "Golem Smasher",
+ "Bug Zapper", "Monster Masher", "Beast Bopper"
+};
+
+const char *const WEAPON_NAMES[35] = {
+ nullptr, "long sword ", "short sword ", "broad sword ", "scimitar ",
+ "cutlass ", "sabre ", "club ", "hand axe ", "katana ", "nunchakas ",
+ "wakazashi ", "dagger ", "mace ", "flail ", "cudgel ", "maul ", "spear ",
+ "bardiche ", "glaive ", "halberd ", "pike ", "flamberge ", "trident ",
+ "staff ", "hammer ", "naginata ", "battle axe ", "grand axe ", "great axe ",
+ "short bow ", "long bow ", "crossbow ", "sling ", "Xeen Slayer Sword"
+};
+
+const char *const ARMOR_NAMES[14] = {
+ nullptr, "Robes ", "Scale rmor ", "ring mail ", "chain mail ",
+ "splint mail ", "plate mail ", "plate armor ", "shield ",
+ "helm ", "boots ", "cloak ", "cape ", "gauntlets "
+};
+
+const char *const ACCESSORY_NAMES[11] = {
+ nullptr, "ring ", "belt ", "broach ", "medal ", "charm ", "cameo ",
+ "scarab ", "pendant ", "necklace ", "amulet "
+};
+
+const char *const MISC_NAMES[22] = {
+ nullptr, "rod ", "jewel ", "gem ", "box ", "orb ", "horn ", "coin ",
+ "wand ", "whistle ", "potion ", "scroll ", "RogueVM",
+ "bogusg", "bogus", "bogus", "bogus", "bogus",
+ "bogus", "bogus", "bogus", "bogus"
+};
+
+const char *const *ITEM_NAMES[4] = {
+ &WEAPON_NAMES[0], &ARMOR_NAMES[0], &ACCESSORY_NAMES[0], &MISC_NAMES[0]
+};
+
+const char *const ELEMENTAL_NAMES[6] = {
+ "Fire", "Elec", "Cold", "Acid/Poison", "Energy", "Magic"
+};
+
+const char *const ATTRIBUTE_NAMES[10] = {
+ "might", "Intellect", "Personality", "Speed", "accuracy", "Luck",
+ "Hit Points", "Spell Points", "Armor Class", "Thievery"
+};
+
+const char *const EFFECTIVENESS_NAMES[7] = {
+ nullptr, "Dragons", "Undead", "Golems", "Bugs", "Monsters", "Beasts"
+};
+
+const char *const QUEST_ITEM_NAMES[85] = {
+ "Deed to New Castle",
+ "Crystal Key to Witch Tower",
+ "Skeleton Key to Darzog's Tower",
+ "Enchanted Key to Tower of High Magic",
+ "Jeweled Amulet of the Northern Sphinx",
+ "Stone of a Thousand Terrors",
+ "Golem Stone of Admittance",
+ "Yak Stone of Opening",
+ "Xeen's Scepter of Temporal Distortion",
+ "Alacorn of Falista",
+ "Elixir of Restoration",
+ "Wand of Faery Magic",
+ "Princess Roxanne's Tiara",
+ "Holy Book of Elvenkind",
+ "Scarab of Imaging",
+ "Crystals of Piezoelectricity",
+ "Scroll of Insight",
+ "Phirna Root",
+ "Orothin's Bone Whistle",
+ "Barok's Magic Pendant",
+ "Ligono's Missing Skull",
+ "Last Flower of Summer",
+ "Last Raindrop of Spring",
+ "Last Snowflake of Winter",
+ "Last Leaf of Autumn",
+ "Ever Hot Lava Rock",
+ "King's Mega Credit",
+ "Excavation Permit",
+ "Cupie Doll",
+ "Might Doll",
+ "Speed Doll",
+ "Endurance Doll",
+ "Accuracy Doll",
+ "Luck Doll",
+ "Widget",
+ "Pass to Castleview",
+ "Pass to Sandcaster",
+ "Pass to Lakeside",
+ "Pass to Necropolis",
+ "Pass to Olympus",
+ "Key to Great Western Tower",
+ "Key to Great Southern Tower",
+ "Key to Great Eastern Tower",
+ "Key to Great Northern Tower",
+ "Key to Ellinger's Tower",
+ "Key to Dragon Tower",
+ "Key to Darkstone Tower",
+ "Key to Temple of Bark",
+ "Key to Dungeon of Lost Souls",
+ "Key to Ancient Pyramid",
+ "Key to Dungeon of Death",
+ "Amulet of the Southern Sphinx",
+ "Dragon Pharoah's Orb",
+ "Cube of Power",
+ "Chime of Opening",
+ "Gold ID Card",
+ "Silver ID Card",
+ "Vulture Repellant",
+ "Bridle",
+ "Enchanted Bridle",
+ "Treasure Map (Goto E1 x1, y11)",
+ "",
+ "Fake Map",
+ "Onyx Necklace",
+ "Dragon Egg",
+ "Tribble",
+ "Golden Pegasus Statuette",
+ "Golden Dragon Statuette",
+ "Golden Griffin Statuette",
+ "Chalice of Protection",
+ "Jewel of Ages",
+ "Songbird of Serenity",
+ "Sandro's Heart",
+ "Ector's Ring",
+ "Vespar's Emerald Handle",
+ "Queen Kalindra's Crown",
+ "Caleb's Magnifying Glass",
+ "Soul Box",
+ "Soul Box with Corak inside",
+ "Ruby Rock",
+ "Emerald Rock",
+ "Sapphire Rock",
+ "Diamond Rock",
+ "Monga Melon",
+ "Energy Disk"
+};
+
+const int WEAPON_BASE_COSTS[35] = {
+ 0, 50, 15, 100, 80, 40, 60, 1, 10, 150, 30, 60, 8, 50,
+ 100, 15, 30, 15, 200, 80, 250, 150, 400, 100, 40, 120,
+ 300, 100, 200, 300, 25, 100, 50, 15, 0
+};
+const int ARMOR_BASE_COSTS[25] = {
+ 0, 20, 100, 200, 400, 600, 1000, 2000, 100, 60, 40, 250, 200, 100
+};
+const int ACCESSORY_BASE_COSTS[11] = {
+ 0, 100, 100, 250, 100, 50, 300, 200, 500, 1000, 2000
+};
+const int MISC_MATERIAL_COSTS[22] = {
+ 0, 50, 1000, 500, 10, 100, 20, 10, 50, 10, 10, 100,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+const int MISC_BASE_COSTS[76] = {
+ 0, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
+ 100, 100, 100, 100, 200, 200, 200, 200, 200, 200, 200, 200,
+ 200, 200, 200, 200, 200, 200, 200, 300, 300, 300, 300, 300,
+ 300, 300, 300, 300, 300, 400, 400, 400, 400, 400, 400, 400,
+ 400, 400, 400, 500, 500, 500, 500, 500, 500, 500, 500, 500,
+ 500, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
+ 600, 600, 600, 600
+};
+const int METAL_BASE_MULTIPLIERS[22] = {
+ 10, 25, 5, 75, 2, 5, 10, 20, 50, 2, 3, 5, 10, 20, 30, 40,
+ 50, 60, 70, 80, 90, 100
+};
+const int ITEM_SKILL_DIVISORS[4] = { 1, 2, 100, 10 };
+
+const int RESTRICTION_OFFSETS[4] = { 0, 35, 49, 60 };
+
+const int ITEM_RESTRICTIONS[86] = {
+ 0, 86, 86, 86, 86, 86, 86, 0, 6, 239, 239, 239, 2, 4, 4, 4, 4,
+ 6, 70, 70, 70, 70, 94, 70, 0, 4, 239, 86, 86, 86, 70, 70, 70, 70,
+ 0, 0, 0, 68, 100, 116, 125, 255, 255, 85, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+const char *const NOT_PROFICIENT =
+ "\t000\v007\x3""c%ss are not proficient with a %s!";
+
+const char *const NO_ITEMS_AVAILABLE = "\x3""c\n"
+ "\t000No items available.";
+
+const char *const CATEGORY_NAMES[4] = { "Weapons", "Armor", "Accessories", "Miscellaneous" };
+
+const char *const X_FOR_THE_Y =
+ "\x1\fd\r%s\v000\t000%s for %s the %s%s\v011\x2%s%s%s%s%s%s%s%s%s\x1\fd";
+
+const char *const X_FOR_Y =
+ "\x1\xC""d\r\x3l\v000\t000%s for %s\x3r\t000%s\x3l\v011\x2%s%s%s%s%s%s%s%s%s\x1\xC""d";
+
+const char *const X_FOR_Y_GOLD =
+ "\x1\fd\r\x3l\v000\t000%s for %s\t150Gold - %lu%s\x3l\v011"
+ "\x2%s%s%s%s%s%s%s%s%s\x1\fd";
+
+const char *const FMT_CHARGES = "\x3rr\t000Charges\x3l";
+
+const char *const AVAILABLE_GOLD_COST =
+ "\x1\fd\r\x3l\v000\t000Available %s%s\t150Gold - %lu\x3r\t000Cost"
+ "\x3l\v011\x2%s%s%s%s%s%s%s%s%s\x1\xC""d";
+
+const char *const CHARGES = "Charges";
+
+const char *const COST = "Cost";
+
+const char *const ITEM_ACTIONS[7] = {
+ "Equip", "Remove", "Use", "Discard", "Enchant", "Recharge", "Gold"
+};
+const char *const WHICH_ITEM = "\t010\v005%s which item?";
+
+const char *const WHATS_YOUR_HURRY = "\v007What's your hurry?\n"
+ "Wait till you get out of here!";
+
+const char *const USE_ITEM_IN_COMBAT =
+ "\v007To use an item in Combat, invoke the Use command on your turn!";
+
+const char *const NO_SPECIAL_ABILITIES = "\v005\x3""c%s\fdhas no special abilities!";
+
+const char *const CANT_CAST_WHILE_ENGAGED = "\x3""c\v007Can't cast %s while engaged!";
+
+const char *const EQUIPPED_ALL_YOU_CAN = "\x3""c\v007You have equipped all the %ss you can!";
+const char *const REMOVE_X_TO_EQUIP_Y = "\x3""c\v007You must remove %sto equip %s\x8!";
+const char *const RING = "ring";
+const char *const MEDAL = "medal";
+
+const char *const CANNOT_REMOVE_CURSED_ITEM = "\x3""You cannot remove a cursed item!";
+
+const char *const CANNOT_DISCARD_CURSED_ITEM = "\3x""cYou cannot discard a cursed item!";
+
+const char *const PERMANENTLY_DISCARD = "\v000\t000\x03lPermanently discard %s\fd?";
+
+const char *const BACKPACK_IS_FULL = "\v005\x3""c\fd%s's backpack is full.";
+
+const char *const CATEGORY_BACKPACK_IS_FULL[4] = {
+ "\v010\t000\x3""c%s's weapons backpack is full.",
+ "\v010\t000\x3""c%s's armor backpack is full.",
+ "\v010\t000\x3""c%s's accessories backpack is full.",
+ "\v010\t000\x3""c%s's miscellaneous backpack is full."
+};
+
+const char *const BUY_X_FOR_Y_GOLD = "\x3l\v000\t000\fdBuy %s\fd for %lu gold?";
+
+const char *const SELL_X_FOR_Y_GOLD = "\x3l\v000\t000\fdSell %s\fd for %lu gold?";
+
+const char *const NO_NEED_OF_THIS = "\v005\x3""c\fdWe have no need of this %s\f!";
+
+const char *const NOT_RECHARGABLE = "\v012\x3""c\fdNot Rechargeable. %s";
+
+const char *const NOT_ENCHANTABLE = "\v012\t000\x3""cNot Enchantable. %s";
+
+const char *const SPELL_FAILED = "Spell Failed!";
+
+const char *const ITEM_NOT_BROKEN = "\fdThat item is not broken!";
+
+const char *const FIX_IDENTIFY[2] = { "Fix", "Identify" };
+
+const char *const FIX_IDENTIFY_GOLD = "\x3l\v000\t000%s %s\fd for %lu gold?";
+
+const char *const IDENTIFY_ITEM_MSG = "\fd\v000\t000\x3""cIdentify Item\x3l\n"
+ "\n"
+ "\v012%s\fd\n"
+ "\n"
+ "%s";
+
+const char *const ITEM_DETAILS =
+ "Proficient Classes\t132:\t140%s\n"
+ "to Hit Modifier\t132:\t140%s\n"
+ "Physical Damage\t132:\t140%s\n"
+ "Elemental Damage\t132:\t140%s\n"
+ "Elemental Resistance\t132:\t140%s\n"
+ "Armor Class Bonus\t132:\t140%s\n"
+ "Attribute Bonus\t132:\t140%s\n"
+ "Special Power\t132:\t140%s";
+
+const char *const ALL = "All";
+const char *const FIELD_NONE = "None";
+const char *const DAMAGE_X_TO_Y = "%d to %d";
+const char *const ELEMENTAL_XY_DAMAGE = "%+d %s Damage";
+const char *const ATTR_XY_BONUS = "%+d %s";
+const char *const EFFECTIVE_AGAINST = "x3 vs %s";
+
+const char *const QUESTS_DIALOG_TEXT =
+ "\r\x2\x3""c\v021\t017\f37I\fdtems\t085\f37Q\fduests\t153"
+ "\f37A\fduto Notes 221\f37U\fdp\t255\f37D\fdown"
+ "\t289Exit";
+const char *const CLOUDS_OF_XEEN_LINE = "\b \b*-- \f04Clouds of Xeen\fd --";
+const char *const DARKSIDE_OF_XEEN_LINE = "\b \b*-- \f04Darkside of Xeen\fd --";
+
+const char *const NO_QUEST_ITEMS =
+ "\r\x3""c\v000 000Quest Items\x3l\x2\n"
+ "\n"
+ "\x3""cNo Quest Items";
+const char *const NO_CURRENT_QUESTS =
+ "\x3""c\v000\t000\n"
+ "\n"
+ "No Current Quests";
+const char *const NO_AUTO_NOTES = "\x3""cNo Auto Notes";
+
+const char *const QUEST_ITEMS_DATA =
+ "\r\x1\fd\x3""c\v000\t000Quest Items\x3l\x2\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s";
+const char *const CURRENT_QUESTS_DATA =
+ "\r\x1\fd\x3""c\t000\v000Current Quests\x3l\x2\n"
+ "%s\n"
+ "\n"
+ "%s\n"
+ "\n"
+ "%s";
+const char *const AUTO_NOTES_DATA =
+ "\r\x1\fd\x3""c\t000\v000Auto Notes\x3l\x2\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l";
+
+const char *const REST_COMPLETE =
+ "\v000\t0008 hours pass. Rest complete.\n"
+ "%s\n"
+ "%d food consumed.";
+const char *const PARTY_IS_STARVING = "\f07The Party is Starving!\fd";
+const char *const HIT_SPELL_POINTS_RESTORED = "Hit Pts and Spell Pts restored.";
+const char *const TOO_DANGEROUS_TO_REST = "Too dangerous to rest here!";
+const char *const SOME_CHARS_MAY_DIE = "Some Chars may die. Rest anyway?";
+
+const char *const CANT_DISMISS_LAST_CHAR = "You cannot dismiss your last character!";
+
+const char *const REMOVE_DELETE[2] = { "Remove", "Delete" };
+
+const char *const REMOVE_OR_DELETE_WHICH = "\x3l\t010\v005%s which character?";
+
+const char *const YOUR_PARTY_IS_FULL = "\v007Your party is full!";
+
+const char *const HAS_SLAYER_SWORD =
+ "\v000\t000This character has the Xeen Slayer Sword and cannot be deleted!";
+const char *const SURE_TO_DELETE_CHAR =
+ "Are you sure you want to delete %s the %s?";
+
+const char *const CREATE_CHAR_DETAILS =
+ "\f04\x3""c\x2\t144\v119\f37R\f04oll\t144\v149\f37C\f04reate"
+ "\t144\v179\f37ESC\f04\x3l\x1\t195\v021\f37M\f04gt"
+ "\t195\v045\f37I\f04nt\t195\v069\f37P\f04er\t195\v093\f37E\f04nd"
+ "\t195\v116\f37S\f04pd\t195\v140\f37A\f04cy\t195\v164\f37L\f04ck%s";
+
+const char *const NEW_CHAR_STATS =
+ "\f04\x3l\t022\v148Race\t055: %s\n"
+ "\t022Sex\t055: %s\n"
+ "\t022Class\t055:\n"
+ "\x3r\t215\v031%d\t215\v055%d\t215\v079%d\t215\v103%d\t215\v127%d"
+ "\t215\v151%d\t215\v175%d\x3l\t242\v020\f%2dKnight\t242\v031\f%2d"
+ "Paladin\t242\v042\f%2dArcher\t242\v053\f%2dCleric\t242\v064\f%2d"
+ "Sorcerer\t242\v075\f%2dRobber\t242\v086\f%2dNinja\t242\v097\f%2d"
+ "Barbarian\t242\v108\f%2dDruid\t242\v119\f%2dRanger\f04\x3""c"
+ "\t265\v142Skills\x3l\t223\v155%s\t223\v170%s%s";
+
+const char *const NAME_FOR_NEW_CHARACTER =
+ "\x3""cEnter a Name for this Character";
+const char *const SELECT_CLASS_BEFORE_SAVING =
+ "\v006\x3""cSelect a Class before saving.\x3l";
+const char *const EXCHANGE_ATTR_WITH = "Exchange %s with...";
+
+const int NEW_CHAR_SKILLS[10] = { 1, 5, -1, -1, 4, 0, 0, -1, 6, 11 };
+const int NEW_CHAR_SKILLS_LEN[10] = { 11, 8, 0, 0, 12, 8, 8, 0, 9, 11 };
+const int NEW_CHAR_RACE_SKILLS[10] = { 14, -1, 17, 16, -1, 0, 0, 0, 0, 0 };
+
+const int RACE_MAGIC_RESISTENCES[5] = { 7, 5, 20, 0, 0 };
+const int RACE_FIRE_RESISTENCES[5] = { 7, 0, 2, 5, 10 };
+const int RACE_ELECTRIC_RESISTENCES[5] = { 7, 0, 2, 5, 10 };
+const int RACE_COLD_RESISTENCES[5] = { 7, 0, 2, 5, 10 };
+const int RACE_ENERGY_RESISTENCES[5] = { 7, 5, 2, 5, 0 };
+const int RACE_POISON_RESISTENCES[5] = { 7, 0, 2, 20, 0 };
+const int NEW_CHARACTER_SPELLS[10][4] = {
+ { -1, -1, -1, -1 },
+ { 21, -1, -1, -1 },
+ { 22, -1, -1, -1 },
+ { 21, 1, 14, -1 },
+ { 22, 0, 25, -1 },
+ { -1, -1, -1, -1 },
+ { -1, -1, -1, -1 },
+ { -1, -1, -1, -1 },
+ { 20, 1, 11, 23 },
+ { 20, 1, -1, -1 }
+};
+
+const char *const COMBAT_DETAILS = "\r\f00\x3""c\v000\t000\x2""Combat%s%s%s\x1";
+
+const char *NOT_ENOUGH_TO_CAST = "\x3""c\v010Not enough %s to Cast %s";
+const char *SPELL_CAST_COMPONENTS[2] = { "Spell Points", "Gems" };
+
+const char *const CAST_SPELL_DETAILS =
+ "\r\x2\x3""c\v122\t013\f37C\fdast\t040\f37N\fdew"
+ "\t067ESC\x1\t000\v000\x3""cCast Spell\n"
+ "\n"
+ "%s\x3l\n"
+ "\n"
+ "Spell Ready:\x3""c\n"
+ "\n"
+ "\f09%s\fd\x2\x3l\n"
+ "\v082Cost\x3r\t000%u/%u\x3l\n"
+ "Cur SP\x3r\t000%u\x1";
+
+const char *const PARTY_FOUND =
+ "\x3""cThe Party Found:\n"
+ "\n"
+ "\x3r\t000%lu Gold\n"
+ "%lu Gems";
+
+const char *const BACKPACKS_FULL_PRESS_KEY =
+ "\v007\f12Warning! BackPacks Full!\fd\n"
+ "Press a Key";
+
+const char *const HIT_A_KEY = "\x3l\v120\t000\x4""077\x3""c\f37Hit a key\f'd";
+
+const char *const GIVE_TREASURE_FORMATTING =
+ "\x3l\v060\t000\x4""077\n"
+ "\x4""077\n"
+ "\x4""077\n"
+ "\x4""077\n"
+ "\x4""077\n"
+ "\x4""077";
+
+const char *const X_FOUND_Y = "\v060\t000\x3""c%s found: %s";
+
+const char *const ON_WHO = "\x3""c\v009On Who?";
+
+const char *const WHICH_ELEMENT1 =
+ "\r\x3""c\x1Which Element?\x2\v034\t014\f15F\fdire\t044"
+ "\f15E\fdlec\t074\f15C\fdold\t104\f15A\fdcid\x1";
+
+const char *const WHICH_ELEMENT2 =
+ "\r\x3""cWhich Element?', 2, 0Bh, '034\t014\f15F\fdire\t044"
+ "\f15E\fdlec\t074\f15C\fdold\t104\f15A\fdcid\x1";
+
+const char *const DETECT_MONSTERS = "\x3""cDetect Monsters";
+
+const char *const LLOYDS_BEACON =
+ "\r\x3""c\v000\t000\x1Lloyd's Beacon\n"
+ "\n"
+ "Last Location\n"
+ "\n"
+ "%s\x3l\n"
+ "x = %d\x3r\t000y = %d\x3""c\x2\v122\t021\f15S\fdet\t060\f15R\fdeturn\x1";
+
+const char *const HOW_MANY_SQUARES = "\x3""cTeleport\nHow many squares %s (1-9)";
+
+const char *const TOWN_PORTAL =
+ "\x3""cTown Portal\x3l\n"
+ "\n"
+ "\t0101. %s\n"
+ "\t0102. %s\n"
+ "\t0103. %s\n"
+ "\t0104. %s\n"
+ "\t0105. %s\x3""c\n"
+ "\n"
+ "To which Town (1-5)\n"
+ "\n";
+
+const int TOWN_MAP_NUMBERS[2][5] = {
+ { 28, 29, 30, 31, 32 }, { 29, 31, 33, 35, 37 }
+};
+
+const char *const MONSTER_DETAILS =
+ "\x3l\n"
+ "%s\x3""c\t100%s\t140%u\t180%u\x3r\t000%s";
+
+const char *const MONSTER_SPECIAL_ATTACKS[23] = {
+ "None", "Magic", "Fire", "Elec", "Cold", "Poison", "Energy", "Disease",
+ "Insane", "Asleep", "CurseItm", "InLove", "DrnSPts", "Curse", "Paralys",
+ "Uncons", "Confuse", "BrkWpn", "Weak", "Erad", "Age+5", "Dead", "Stone"
+};
+
+const char *const IDENTIFY_MONSTERS =
+ "Name\x3""c\t100HP\t140AC\t177#Atks\x3r\t000Special%s%s%s";
+
+const char *const EVENT_SAMPLES[6] = {
+ "ahh.voc", "whereto.voc", "gulp.voc", "null.voc", "scream.voc", "laff1.voc"
+};
+
+} // End of namespace Xeen
diff --git a/engines/xeen/resources.h b/engines/xeen/resources.h
new file mode 100644
index 0000000000..6e9ce6ce39
--- /dev/null
+++ b/engines/xeen/resources.h
@@ -0,0 +1,569 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_RESOURCES_H
+#define XEEN_RESOURCES_H
+
+#include "common/scummsys.h"
+#include "common/str-array.h"
+#include "gui/debugger.h"
+#include "xeen/party.h"
+#include "xeen/spells.h"
+
+namespace Xeen {
+
+class Resources {
+public:
+ SpriteResource _globalSprites;
+ Common::StringArray _maeNames; // Magic and equipment names
+public:
+ Resources();
+};
+
+#define Res (*_vm->_resources)
+
+extern const char *const CREDITS;
+
+extern const char *const OPTIONS_TITLE;
+
+extern const char *const THE_PARTY_NEEDS_REST;
+
+extern const char *const WHO_WILL;
+
+extern const char *const WHATS_THE_PASSWORD;
+
+extern const char *const IN_NO_CONDITION;
+
+extern const char *const NOTHING_HERE;
+
+extern const char *const TERRAIN_TYPES[6];
+
+extern const char *const SURFACE_TYPE_NAMES[15];
+
+extern const char *const SURFACE_NAMES[16];
+
+extern const char *const WHO_ACTIONS[32];
+
+extern const char *const WHO_WILL_ACTIONS[4];
+
+extern const byte SYMBOLS[20][64];
+
+extern const byte TEXT_COLORS[40][4];
+
+extern const char *const DIRECTION_TEXT_UPPER[4];
+
+extern const char *const DIRECTION_TEXT[4];
+
+extern const char *const RACE_NAMES[5];
+
+extern const int RACE_HP_BONUSES[5];
+
+extern const int RACE_SP_BONUSES[5][2];
+
+extern const char *const CLASS_NAMES[11];
+
+extern const uint CLASS_EXP_LEVELS[10];
+
+extern const char *const ALIGNMENT_NAMES[3];
+
+extern const char *const SEX_NAMES[2];
+
+extern const char *const SKILL_NAMES[18];
+
+extern const char *const CONDITION_NAMES[17];
+
+extern const int CONDITION_COLORS[17];
+
+extern const char *const GOOD;
+
+extern const char *const BLESSED;
+
+extern const char *const POWER_SHIELD;
+
+extern const char *const HOLY_BONUS;
+
+extern const char *const HEROISM;
+extern const char *const IN_PARTY;
+
+extern const char *const PARTY_DETAILS;
+extern const char *const PARTY_DIALOG_TEXT;
+
+extern const int FACE_CONDITION_FRAMES[17];
+
+extern const int CHAR_FACES_X[6];
+
+extern const int HP_BARS_X[6];
+
+extern const char *const NO_ONE_TO_ADVENTURE_WITH;
+
+extern const byte BACKGROUND_XLAT[];
+
+extern const char *const YOUR_ROSTER_IS_FULL;
+
+extern const char *const PLEASE_WAIT;
+
+extern const char *const OOPS;
+
+extern const int8 SCREEN_POSITIONING_X[4][48];
+
+extern const int8 SCREEN_POSITIONING_Y[4][48];
+
+extern const int MONSTER_GRID_BITMASK[12];
+
+extern const int INDOOR_OBJECT_X[2][12];
+
+extern const int MAP_OBJECT_Y[2][12];
+
+extern const int INDOOR_MONSTERS_Y[4];
+
+extern const int OUTDOOR_OBJECT_X[2][12];
+
+extern const int OUTDOOR_MONSTER_INDEXES[26];
+
+extern const int OUTDOOR_MONSTERS_Y[26];
+
+extern const int DIRECTION_ANIM_POSITIONS[4][4];
+
+extern const byte WALL_SHIFTS[4][48];
+
+extern const int DRAW_NUMBERS[25];
+
+extern const int DRAW_FRAMES[25][2];
+
+extern const int COMBAT_FLOAT_X[8];
+
+extern const int COMBAT_FLOAT_Y[8];
+
+extern const int MONSTER_EFFECT_FLAGS[15][8];
+
+extern const uint SPELLS_ALLOWED[3][40];
+
+extern const int BASE_HP_BY_CLASS[10];
+
+extern const int AGE_RANGES[10];
+
+extern const int AGE_RANGES_ADJUST[2][10];
+
+extern const uint STAT_VALUES[24];
+
+extern const int STAT_BONUSES[24];
+
+extern const int ELEMENTAL_CATEGORIES[6];
+
+extern const int ATTRIBUTE_CATEGORIES[10];
+
+extern const int ATTRIBUTE_BONUSES[72];
+
+extern const int ELEMENTAL_RESISTENCES[37];
+
+extern const int ELEMENTAL_DAMAGE[37];
+
+extern const int WEAPON_DAMAGE_BASE[35];
+extern const int WEAPON_DAMAGE_MULTIPLIER[35];
+extern const int METAL_DAMAGE[22];
+extern const int METAL_DAMAGE_PERCENT[22];
+
+extern const int METAL_LAC[9];
+
+extern const int ARMOR_STRENGTHS[14];
+
+extern const int MAKE_ITEM_ARR1[6];
+
+extern const int MAKE_ITEM_ARR2[6][7][2];
+
+extern const int MAKE_ITEM_ARR3[10][7][2];
+
+extern const int MAKE_ITEM_ARR4[2][7][2];
+
+extern const int MAKE_ITEM_ARR5[8][2];
+
+extern const int OUTDOOR_DRAWSTRCT_INDEXES[44];
+
+extern const int TOWN_MAXES[2][11];
+
+extern const char *const TOWN_ACTION_MUSIC[14];
+
+extern const char *const TOWN_ACTION_SHAPES[4];
+
+extern const int TOWN_ACTION_FILES[2][7];
+
+extern const char *const BANK_TEXT;
+
+extern const char *const BLACKSMITH_TEXT;
+
+extern const char *const GUILD_NOT_MEMBER_TEXT;
+
+extern const char *const GUILD_TEXT;
+
+extern const char *const TAVERN_TEXT;
+
+extern const char *const GOOD_STUFF;
+
+extern const char *const HAVE_A_DRINK;
+
+extern const char *const YOURE_DRUNK;
+
+extern const int TAVERN_EXIT_LIST[2][6][5][2];
+
+extern const char *const FOOD_AND_DRINK;
+
+extern const char *const TEMPLE_TEXT;
+
+extern const char *const EXPERIENCE_FOR_LEVEL;
+
+extern const char *const LEARNED_ALL;
+
+extern const char *const ELIGIBLE_FOR_LEVEL;
+
+extern const char *const TRAINING_TEXT;
+
+extern const char *const GOLD_GEMS;
+
+extern const char *const GOLD_GEMS_2;
+
+extern const char *const DEPOSIT_WITHDRAWL[2];
+
+extern const char *const NOT_ENOUGH_X_IN_THE_Y;
+
+extern const char *const NO_X_IN_THE_Y;
+
+extern const char *const STAT_NAMES[16];
+
+extern const char *const CONSUMABLE_NAMES[4];
+
+extern const char *const WHERE_NAMES[2];
+
+extern const char *const AMOUNT;
+
+extern const char *const FOOD_PACKS_FULL;
+
+extern const char *const BUY_SPELLS;
+
+extern const char *const GUILD_OPTIONS;
+
+extern const int MISC_SPELL_INDEX[74];
+
+extern const int SPELL_COSTS[77];
+
+extern const int CLOUDS_SPELL_OFFSETS[5][20];
+
+extern const uint DARK_SPELL_OFFSETS[3][39];
+
+extern const int DARK_SPELL_RANGES[12][2];
+
+extern const int SPELL_LEVEL_OFFSETS[3][39];
+
+extern const int SPELL_GEM_COST[77];
+
+extern const char *const NOT_A_SPELL_CASTER;
+
+extern const char *const SPELLS_FOR;
+
+extern const char *const SPELL_LINES_0_TO_9;
+
+extern const char *const SPELLS_DIALOG_SPELLS;
+
+extern const char *const SPELL_PTS;
+
+extern const char *const GOLD;
+
+extern const char *const SPELLS_PRESS_A_KEY;
+
+extern const char *const SPELLS_PURCHASE;
+
+extern const char *const MAP_TEXT;
+
+extern const char *const LIGHT_COUNT_TEXT;
+
+extern const char *const FIRE_RESISTENCE_TEXT;
+
+extern const char *const ELECRICITY_RESISTENCE_TEXT;
+
+extern const char *const COLD_RESISTENCE_TEXT;
+
+extern const char *const POISON_RESISTENCE_TEXT;
+
+extern const char *const CLAIRVOYANCE_TEXT;
+
+extern const char *const LEVITATE_TEXT;
+
+extern const char *const WALK_ON_WATER_TEXT;
+
+extern const char *const GAME_INFORMATION;
+
+extern const char *const WORLD_GAME_TEXT;
+
+extern const char *const DARKSIDE_GAME_TEXT;
+
+extern const char *const CLOUDS_GAME_TEXT;
+
+extern const char *const SWORDS_GAME_TEXT;
+
+extern const char *const WEEK_DAY_STRINGS[10];
+
+extern const char *const CHARACTER_DETAILS;
+
+extern const char *const PARTY_GOLD;
+
+extern const char *const PLUS_14;
+
+extern const char *const CHARACTER_TEMPLATE;
+
+extern const char *const EXCHANGING_IN_COMBAT;
+
+extern const char *const CURRENT_MAXIMUM_RATING_TEXT;
+
+extern const char *const CURRENT_MAXIMUM_TEXT;
+
+extern const char *const RATING_TEXT[24];
+
+extern const char *const AGE_TEXT;
+
+extern const char *const LEVEL_TEXT;
+
+extern const char *const RESISTENCES_TEXT;
+
+extern const char *const NONE;
+
+extern const char *const EXPERIENCE_TEXT;
+
+extern const char *const ELIGIBLE;
+
+extern const char *const IN_PARTY_IN_BANK;
+
+extern const char *const FOOD_TEXT;
+
+extern const char *const EXCHANGE_WITH_WHOM;
+
+extern const char *const QUICK_REF_LINE;
+
+extern const char *const QUICK_REFERENCE;
+
+extern const uint BLACKSMITH_MAP_IDS[2][4];
+
+extern const char *const ITEMS_DIALOG_TEXT1;
+extern const char *const ITEMS_DIALOG_TEXT2;
+extern const char *const ITEMS_DIALOG_LINE1;
+extern const char *const ITEMS_DIALOG_LINE2;
+
+extern const char *const BTN_BUY;
+extern const char *const BTN_SELL;
+extern const char *const BTN_IDENTIFY;
+extern const char *const BTN_FIX;
+extern const char *const BTN_USE;
+extern const char *const BTN_EQUIP;
+extern const char *const BTN_REMOVE;
+extern const char *const BTN_DISCARD;
+extern const char *const BTN_EQUIP;
+extern const char *const BTN_QUEST;
+extern const char *const BTN_ENCHANT;
+extern const char *const BTN_RECHARGE;
+extern const char *const BTN_GOLD;
+
+extern const char *const ITEM_BROKEN;
+extern const char *const ITEM_CURSED;
+extern const char *const BONUS_NAMES[7];
+extern const char *const WEAPON_NAMES[35];
+extern const char *const ARMOR_NAMES[14];
+extern const char *const ACCESSORY_NAMES[11];
+extern const char *const MISC_NAMES[22];
+extern const char *const *ITEM_NAMES[4];
+
+extern const char *const ELEMENTAL_NAMES[6];
+extern const char *const ATTRIBUTE_NAMES[10];
+extern const char *const EFFECTIVENESS_NAMES[7];
+extern const char *const QUEST_ITEM_NAMES[85];
+
+extern const int WEAPON_BASE_COSTS[35];
+extern const int ARMOR_BASE_COSTS[25];
+extern const int ACCESSORY_BASE_COSTS[11];
+extern const int MISC_MATERIAL_COSTS[22];
+extern const int MISC_BASE_COSTS[76];
+extern const int METAL_BASE_MULTIPLIERS[22];
+extern const int ITEM_SKILL_DIVISORS[4];
+
+extern const int RESTRICTION_OFFSETS[4];
+extern const int ITEM_RESTRICTIONS[86];
+
+extern const char *const NOT_PROFICIENT;
+
+extern const char *const NO_ITEMS_AVAILABLE;
+
+extern const char *const CATEGORY_NAMES[4];
+
+extern const char *const X_FOR_THE_Y;
+
+extern const char *const X_FOR_Y;
+
+extern const char *const X_FOR_Y_GOLD;
+
+extern const char *const FMT_CHARGES;
+
+extern const char *const AVAILABLE_GOLD_COST;
+
+extern const char *const CHARGES;
+
+extern const char *const COST;
+
+extern const char *const ITEM_ACTIONS[7];
+extern const char *const WHICH_ITEM;
+
+extern const char *const WHATS_YOUR_HURRY;
+
+extern const char *const USE_ITEM_IN_COMBAT;
+
+extern const char *const NO_SPECIAL_ABILITIES;
+
+extern const char *const CANT_CAST_WHILE_ENGAGED;
+
+extern const char *const EQUIPPED_ALL_YOU_CAN;
+extern const char *const REMOVE_X_TO_EQUIP_Y;
+extern const char *const RING;
+extern const char *const MEDAL;
+
+extern const char *const CANNOT_REMOVE_CURSED_ITEM;
+
+extern const char *const CANNOT_DISCARD_CURSED_ITEM;
+
+extern const char *const PERMANENTLY_DISCARD;
+
+extern const char *const BACKPACK_IS_FULL;
+
+extern const char *const CATEGORY_BACKPACK_IS_FULL[4];
+
+extern const char *const BUY_X_FOR_Y_GOLD;
+
+extern const char *const SELL_X_FOR_Y_GOLD;
+
+extern const char *const NO_NEED_OF_THIS;
+
+extern const char *const NOT_RECHARGABLE;
+
+extern const char *const SPELL_FAILED;
+
+extern const char *const NOT_ENCHANTABLE;
+
+extern const char *const ITEM_NOT_BROKEN;
+
+extern const char *const FIX_IDENTIFY[2];
+
+extern const char *const FIX_IDENTIFY_GOLD;
+
+extern const char *const IDENTIFY_ITEM_MSG;
+
+extern const char *const ITEM_DETAILS;
+
+extern const char *const ALL;
+extern const char *const FIELD_NONE;
+extern const char *const DAMAGE_X_TO_Y;
+extern const char *const ELEMENTAL_XY_DAMAGE;
+extern const char *const ATTR_XY_BONUS;
+extern const char *const EFFECTIVE_AGAINST;
+
+extern const char *const QUESTS_DIALOG_TEXT;
+extern const char *const CLOUDS_OF_XEEN_LINE;
+extern const char *const DARKSIDE_OF_XEEN_LINE;
+
+extern const char *const NO_QUEST_ITEMS;
+extern const char *const NO_CURRENT_QUESTS;
+extern const char *const NO_AUTO_NOTES;
+extern const char *const QUEST_ITEMS_DATA;
+extern const char *const CURRENT_QUESTS_DATA;
+extern const char *const AUTO_NOTES_DATA;
+
+extern const char *const REST_COMPLETE;
+extern const char *const PARTY_IS_STARVING;
+extern const char *const HIT_SPELL_POINTS_RESTORED;
+extern const char *const TOO_DANGEROUS_TO_REST;
+extern const char *const SOME_CHARS_MAY_DIE;
+
+extern const char *const CANT_DISMISS_LAST_CHAR;
+
+extern const char *const REMOVE_DELETE[2];
+extern const char *const REMOVE_OR_DELETE_WHICH;
+
+extern const char *const YOUR_PARTY_IS_FULL;
+
+extern const char *const HAS_SLAYER_SWORD;
+extern const char *const SURE_TO_DELETE_CHAR;
+
+extern const char *const CREATE_CHAR_DETAILS;
+
+extern const char *const NEW_CHAR_STATS;
+extern const char *const NAME_FOR_NEW_CHARACTER;
+extern const char *const SELECT_CLASS_BEFORE_SAVING;
+extern const char *const EXCHANGE_ATTR_WITH;
+extern const int NEW_CHAR_SKILLS[10];
+extern const int NEW_CHAR_SKILLS_LEN[10];
+extern const int NEW_CHAR_RACE_SKILLS[10];
+
+extern const int RACE_MAGIC_RESISTENCES[5];
+extern const int RACE_FIRE_RESISTENCES[5];
+extern const int RACE_ELECTRIC_RESISTENCES[5];
+extern const int RACE_COLD_RESISTENCES[5];
+extern const int RACE_ENERGY_RESISTENCES[5];
+extern const int RACE_POISON_RESISTENCES[5];
+extern const int NEW_CHARACTER_SPELLS[10][4];
+
+extern const char *const COMBAT_DETAILS;
+
+extern const char *NOT_ENOUGH_TO_CAST;
+extern const char *SPELL_CAST_COMPONENTS[2];
+
+extern const char *const CAST_SPELL_DETAILS;
+
+extern const char *const PARTY_FOUND;
+
+extern const char *const BACKPACKS_FULL_PRESS_KEY;
+
+extern const char *const HIT_A_KEY;
+
+extern const char *const GIVE_TREASURE_FORMATTING;
+
+extern const char *const X_FOUND_Y;
+
+extern const char *const ON_WHO;
+
+extern const char *const WHICH_ELEMENT1;
+extern const char *const WHICH_ELEMENT2;
+
+extern const char *const DETECT_MONSTERS;
+
+extern const char *const LLOYDS_BEACON;
+
+extern const char *const HOW_MANY_SQUARES;
+
+extern const char *const TOWN_PORTAL;
+
+extern const int TOWN_MAP_NUMBERS[2][5];
+
+extern const char *const MONSTER_DETAILS;
+
+extern const char *const MONSTER_SPECIAL_ATTACKS[23];
+
+extern const char *const IDENTIFY_MONSTERS;
+
+extern const char *const EVENT_SAMPLES[6];
+
+} // End of namespace Xeen
+
+#endif /* XEEN_RESOURCES_H */
diff --git a/engines/xeen/saves.cpp b/engines/xeen/saves.cpp
new file mode 100644
index 0000000000..9fd2d19a9b
--- /dev/null
+++ b/engines/xeen/saves.cpp
@@ -0,0 +1,171 @@
+/* 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.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/algorithm.h"
+#include "common/memstream.h"
+#include "xeen/saves.h"
+#include "xeen/files.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+OutFile::OutFile(XeenEngine *vm, const Common::String filename) :
+ _vm(vm), _filename(filename) {
+}
+
+void OutFile::finalize() {
+ uint16 id = BaseCCArchive::convertNameToId(_filename);
+
+ if (!_vm->_saves->_newData.contains(id))
+ _vm->_saves->_newData[id] = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
+
+ Common::MemoryWriteStreamDynamic &out = _vm->_saves->_newData[id];
+ out.write(getData(), size());
+}
+
+/*------------------------------------------------------------------------*/
+
+SavesManager::SavesManager(XeenEngine *vm, Party &party) :
+ BaseCCArchive(), _vm(vm), _party(party) {
+ SearchMan.add("saves", this, 0, false);
+ _data = nullptr;
+ _wonWorld = false;
+ _wonDarkSide = false;
+}
+
+SavesManager::~SavesManager() {
+ delete[] _data;
+}
+
+void SavesManager::syncBitFlags(Common::Serializer &s, bool *startP, bool *endP) {
+ byte data = 0;
+
+ int bitCounter = 0;
+ for (bool *p = startP; p <= endP; ++p, bitCounter = (bitCounter + 1) % 8) {
+ if (p == endP || bitCounter == 0) {
+ if (p != endP || s.isSaving())
+ s.syncAsByte(data);
+ if (p == endP)
+ break;
+
+ if (s.isSaving())
+ data = 0;
+ }
+
+ if (s.isLoading())
+ *p = (data >> bitCounter) != 0;
+ else if (*p)
+ data |= 1 << bitCounter;
+ }
+}
+
+Common::SeekableReadStream *SavesManager::createReadStreamForMember(const Common::String &name) const {
+ CCEntry ccEntry;
+
+ // If the given resource has already been perviously "written" to the
+ // save manager, then return that new resource
+ uint16 id = BaseCCArchive::convertNameToId(name);
+ if (_newData.contains(id)) {
+ Common::MemoryWriteStreamDynamic stream = _newData[id];
+ return new Common::MemoryReadStream(stream.getData(), stream.size());
+ }
+
+ // Retrieve the resource from the loaded savefile
+ if (getHeaderEntry(name, ccEntry)) {
+ // Open the correct CC entry
+ return new Common::MemoryReadStream(_data + ccEntry._offset, ccEntry._size);
+ }
+
+ return nullptr;
+}
+
+void SavesManager::load(Common::SeekableReadStream *stream) {
+ loadIndex(stream);
+
+ delete[] _data;
+ _data = new byte[stream->size()];
+ stream->seek(0);
+ stream->read(_data, stream->size());
+
+ // Load in the character stats and active party
+ Common::SeekableReadStream *chr = createReadStreamForMember("maze.chr");
+ Common::Serializer sChr(chr, nullptr);
+ _party._roster.synchronize(sChr);
+ delete chr;
+
+ Common::SeekableReadStream *pty = createReadStreamForMember("maze.pty");
+ Common::Serializer sPty(pty, nullptr);
+ _party.synchronize(sPty);
+ delete pty;
+}
+
+void SavesManager::reset() {
+ Common::String prefix = _vm->getGameID() != GType_DarkSide ? "xeen|" : "dark|";
+ Common::MemoryWriteStreamDynamic saveFile(DisposeAfterUse::YES);
+ Common::File fIn;
+
+ const int RESOURCES[6] = { 0x2A0C, 0x2A1C, 0x2A2C, 0x2A3C, 0x284C, 0x2A5C };
+ for (int i = 0; i < 6; ++i) {
+ Common::String filename = prefix + Common::String::format("%.4x", RESOURCES[i]);
+ if (fIn.exists(filename)) {
+ // Read in the next resource
+ fIn.open(filename);
+ byte *data = new byte[fIn.size()];
+ fIn.read(data, fIn.size());
+
+ // Copy it to the combined savefile resource
+ saveFile.write(data, fIn.size());
+ delete[] data;
+ fIn.close();
+ }
+ }
+
+ Common::MemoryReadStream f(saveFile.getData(), saveFile.size());
+ load(&f);
+
+ // Set up the party and characters from dark.cur
+ CCArchive gameCur("xeen.cur", false);
+ File fParty("maze.pty", gameCur);
+ Common::Serializer sParty(&fParty, nullptr);
+ _party.synchronize(sParty);
+ fParty.close();
+
+ File fChar("maze.chr", gameCur);
+ Common::Serializer sChar(&fChar, nullptr);
+ _party._roster.synchronize(sChar);
+ fChar.close();
+}
+
+void SavesManager::readCharFile() {
+ warning("TODO: readCharFile");
+}
+
+void SavesManager::writeCharFile() {
+ warning("TODO: writeCharFile");
+}
+
+void SavesManager::saveChars() {
+ warning("TODO: saveChars");
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/saves.h b/engines/xeen/saves.h
new file mode 100644
index 0000000000..2571c46600
--- /dev/null
+++ b/engines/xeen/saves.h
@@ -0,0 +1,96 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_SAVES_H
+#define XEEN_SAVES_H
+
+#include "common/scummsys.h"
+#include "common/memstream.h"
+#include "common/savefile.h"
+#include "graphics/surface.h"
+#include "xeen/party.h"
+#include "xeen/files.h"
+
+namespace Xeen {
+
+struct XeenSavegameHeader {
+ uint8 _version;
+ Common::String _saveName;
+ Graphics::Surface *_thumbnail;
+ int _year, _month, _day;
+ int _hour, _minute;
+ int _totalFrames;
+};
+
+class XeenEngine;
+class SavesManager;
+
+class OutFile : public Common::MemoryWriteStreamDynamic {
+private:
+ XeenEngine *_vm;
+ Common::String _filename;
+public:
+ OutFile(XeenEngine *vm, const Common::String filename);
+
+ void finalize();
+};
+
+class SavesManager: public BaseCCArchive {
+ friend class OutFile;
+private:
+ XeenEngine *_vm;
+ Party &_party;
+ byte *_data;
+ Common::HashMap<uint16, Common::MemoryWriteStreamDynamic > _newData;
+
+ void load(Common::SeekableReadStream *stream);
+public:
+ /**
+ * Synchronizes a boolean array as a bitfield set
+ */
+ static void syncBitFlags(Common::Serializer &s, bool *startP, bool *endP);
+public:
+ bool _wonWorld;
+ bool _wonDarkSide;
+public:
+ SavesManager(XeenEngine *vm, Party &party);
+
+ ~SavesManager();
+
+ /**
+ * Sets up the dynamic data for the game for a new game
+ */
+ void reset();
+
+ void readCharFile();
+
+ void writeCharFile();
+
+ void saveChars();
+
+ // Archive implementation
+ virtual Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_SAVES_H */
diff --git a/engines/xeen/screen.cpp b/engines/xeen/screen.cpp
new file mode 100644
index 0000000000..03b751a6b9
--- /dev/null
+++ b/engines/xeen/screen.cpp
@@ -0,0 +1,479 @@
+/* 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.
+ *
+ */
+
+#include "common/system.h"
+#include "graphics/palette.h"
+#include "graphics/surface.h"
+#include "xeen/screen.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+Window::Window() : XSurface(), _vm(nullptr), _enabled(false),
+ _a(0), _border(0), _xLo(0), _xHi(0), _ycL(0), _ycH(0) {
+}
+
+Window::Window(const Window &src) : XSurface(), _vm(src._vm), _enabled(src._enabled),
+ _a(src._a), _border(src._border), _xLo(src._xLo), _ycL(src._ycL),
+ _xHi(src._xHi), _ycH(src._ycH) {
+ if (src._vm) {
+ setBounds(src._bounds);
+ create(*_vm->_screen, Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ }
+}
+
+Window::Window(XeenEngine *vm, const Common::Rect &bounds, int a, int border,
+ int xLo, int ycL, int xHi, int ycH): XSurface(),
+ _vm(vm), _enabled(false), _a(a), _border(border),
+ _xLo(xLo), _ycL(ycL), _xHi(xHi), _ycH(ycH) {
+ setBounds(bounds);
+ create(*_vm->_screen, Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+}
+
+void Window::setBounds(const Common::Rect &r) {
+ _bounds = r;
+ _innerBounds = r;
+ _innerBounds.grow(-_border);
+}
+
+void Window::open() {
+ if (!_enabled) {
+ _enabled = true;
+ _vm->_screen->_windowStack.push_back(this);
+ open2();
+ }
+
+ if (_vm->_mode == MODE_9) {
+ warning("TODO: copyFileToMemory");
+ }
+}
+
+void Window::open2() {
+ Screen &screen = *_vm->_screen;
+
+ // Save a copy of the area under the window
+ _savedArea.create(_bounds.width(), _bounds.height());
+ _savedArea.copyRectToSurface(screen, 0, 0, _bounds);
+
+ // Mark the area as dirty and fill it with a default background
+ addDirtyRect(_bounds);
+ frame();
+ fill();
+
+ screen._writePos.x = _bounds.right - 8;
+ screen.writeSymbol(19);
+
+ screen._writePos.x = _innerBounds.left;
+ screen._writePos.y = _innerBounds.top;
+ screen._fontJustify = JUSTIFY_NONE;
+ screen._fontReduced = false;
+}
+
+void Window::frame() {
+ Screen &screen = *_vm->_screen;
+ int xCount = (_bounds.width() - 9) / FONT_WIDTH;
+ int yCount = (_bounds.height() - 9) / FONT_HEIGHT;
+
+ // Write the top line
+ screen._writePos = Common::Point(_bounds.left, _bounds.top);
+ screen.writeSymbol(0);
+
+ if (xCount > 0) {
+ int symbolId = 1;
+ for (int i = 0; i < xCount; ++i) {
+ screen.writeSymbol(symbolId);
+ if (++symbolId == 5)
+ symbolId = 1;
+ }
+ }
+
+ screen._writePos.x = _bounds.right - FONT_WIDTH;
+ screen.writeSymbol(5);
+
+ // Write the vertical edges
+ if (yCount > 0) {
+ int symbolId = 6;
+ for (int i = 0; i < yCount; ++i) {
+ screen._writePos.y += 8;
+
+ screen._writePos.x = _bounds.left;
+ screen.writeSymbol(symbolId);
+
+ screen._writePos.x = _bounds.right - FONT_WIDTH;
+ screen.writeSymbol(symbolId + 4);
+
+ if (++symbolId == 10)
+ symbolId = 6;
+ }
+ }
+
+ // Write the bottom line
+ screen._writePos = Common::Point(_bounds.left, _bounds.bottom - FONT_HEIGHT);
+ screen.writeSymbol(14);
+
+ if (xCount > 0) {
+ int symbolId = 15;
+ for (int i = 0; i < xCount; ++i) {
+ screen.writeSymbol(symbolId);
+ if (++symbolId == 19)
+ symbolId = 15;
+ }
+ }
+
+ screen._writePos.x = _bounds.right - FONT_WIDTH;
+ screen.writeSymbol(19);
+}
+
+void Window::close() {
+ Screen &screen = *_vm->_screen;
+
+ if (_enabled) {
+ // Update the window
+ update();
+
+ // Restore the saved original content
+ screen.copyRectToSurface(_savedArea, _bounds.left, _bounds.top,
+ Common::Rect(0, 0, _bounds.width(), _bounds.height()));
+ addDirtyRect(_bounds);
+
+ // Remove the window from the stack and flag it as now disabled
+ for (uint i = 0; i < _vm->_screen->_windowStack.size(); ++i) {
+ if (_vm->_screen->_windowStack[i] == this)
+ _vm->_screen->_windowStack.remove_at(i);
+ }
+
+ _enabled = false;
+ }
+
+ if (_vm->_mode == MODE_9) {
+ warning("TODO: copyFileToMemory");
+ }
+}
+
+void Window::update() {
+ // Since all window drawing is done on the screen surface anyway,
+ // there's nothing that needs to be updated here
+}
+
+void Window::addDirtyRect(const Common::Rect &r) {
+ _vm->_screen->addDirtyRect(r);
+}
+
+void Window::fill() {
+ fillRect(_innerBounds, _vm->_screen->_bgColor);
+}
+
+const char *Window::writeString(const Common::String &s) {
+ return _vm->_screen->writeString(s, _innerBounds);
+}
+
+void Window::drawList(DrawStruct *items, int count) {
+ for (int i = 0; i < count; ++i, ++items) {
+ if (items->_frame == -1 || items->_scale == -1 || items->_sprites == nullptr)
+ continue;
+
+ Common::Point pt(items->_x, items->_y);
+ pt.x += _innerBounds.left;
+ pt.y += _innerBounds.top;
+
+ items->_sprites->draw(*this, items->_frame, pt, items->_flags, items->_scale);
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+Screen::Screen(XeenEngine *vm) : _vm(vm) {
+ _fadeIn = false;
+ create(SCREEN_WIDTH, SCREEN_HEIGHT);
+ Common::fill(&_tempPalette[0], &_tempPalette[PALETTE_SIZE], 0);
+ Common::fill(&_mainPalette[0], &_mainPalette[PALETTE_SIZE], 0);
+
+ // Load font data for the screen
+ File f("fnt");
+ byte *data = new byte[f.size()];
+ f.read(data, f.size());
+ _fontData = data;
+}
+
+Screen::~Screen() {
+ delete[] _fontData;
+}
+
+void Screen::setupWindows() {
+ Window windows[40] = {
+ Window(_vm, Common::Rect(0, 0, 320, 200), 0, 0, 0, 0, 320, 200),
+ Window(_vm, Common::Rect(237, 9, 317, 74), 0, 0, 237, 12, 307, 68),
+ Window(_vm, Common::Rect(225, 1, 319, 73), 1, 8, 225, 1, 319, 73),
+ Window(_vm, Common::Rect(0, 0, 230, 149), 0, 0, 9, 8, 216, 140),
+ Window(_vm, Common::Rect(235, 148, 309, 189), 2, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(70, 20, 250, 183), 3, 8, 80, 38, 240, 166),
+ Window(_vm, Common::Rect(52, 149, 268, 197), 4, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(108, 0, 200, 200), 5, 0, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(232, 9, 312, 74), 0, 0, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(103, 156, 217, 186), 6, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(226, 0, 319, 146), 7, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(8, 8, 224, 140), 8, 8, 8, 8, 224, 200),
+ Window(_vm, Common::Rect(0, 143, 320, 199), 9, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(50, 103, 266, 139), 10, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(0, 7, 320, 138), 11, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(50, 71, 182, 129), 12, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(228, 106, 319, 146), 13, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(20, 142, 290, 199), 14, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(0, 20, 320, 180), 15, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(231, 48, 317, 141), 16, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(72, 37, 248, 163), 17, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(99, 59, 237, 141), 18, 8, 99, 59, 237, 0),
+ Window(_vm, Common::Rect(65, 23, 250, 163), 19, 8, 75, 36, 245, 141),
+ Window(_vm, Common::Rect(80, 28, 256, 148), 20, 8, 80, 28, 256, 172),
+ Window(_vm, Common::Rect(0, 0, 320, 146), 21, 8, 0, 0, 320, 148),
+ Window(_vm, Common::Rect(27, 6, 207, 142), 22, 8, 0, 0, 0, 146),
+ Window(_vm, Common::Rect(15, 15, 161, 91), 23, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(90, 45, 220, 157), 24, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(0, 0, 320, 200), 25, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(0, 101, 320, 146), 26, 8, 0, 101, 320, 0),
+ Window(_vm, Common::Rect(0, 0, 320, 108), 27, 8, 0, 0, 0, 45),
+ Window(_vm, Common::Rect(50, 112, 266, 148), 28, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(12, 11, 164, 94), 0, 0, 0, 0, 52, 0),
+ Window(_vm, Common::Rect(8, 147, 224, 192), 0, 8, 0, 0, 0, 94),
+ Window(_vm, Common::Rect(232, 74, 312, 138), 29, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(226, 26, 319, 146), 30, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(225, 74, 319, 154), 31, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(27, 6, 195, 142), 0, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(225, 140, 319, 199), 0, 8, 0, 0, 0, 0)
+ };
+
+ _windows = Common::Array<Window>(windows, 40);
+}
+
+void Screen::closeWindows() {
+ for (int i = (int)_windowStack.size() - 1; i >= 0; --i)
+ _windowStack[i]->close();
+ assert(_windowStack.size() == 0);
+}
+
+void Screen::update() {
+ // Merge the dirty rects
+ mergeDirtyRects();
+
+ // Loop through copying dirty areas to the physical screen
+ Common::List<Common::Rect>::iterator i;
+ for (i = _dirtyRects.begin(); i != _dirtyRects.end(); ++i) {
+ const Common::Rect &r = *i;
+ const byte *srcP = (const byte *)getBasePtr(r.left, r.top);
+ g_system->copyRectToScreen(srcP, this->pitch, r.left, r.top,
+ r.width(), r.height());
+ }
+
+ // Signal the physical screen to update
+ g_system->updateScreen();
+ _dirtyRects.clear();
+}
+
+void Screen::addDirtyRect(const Common::Rect &r) {
+ assert(r.isValidRect() && r.width() > 0 && r.height() > 0
+ && r.left >= 0 && r.top >= 0
+ && r.right <= SCREEN_WIDTH && r.bottom <= SCREEN_HEIGHT);
+ _dirtyRects.push_back(r);
+}
+
+void Screen::mergeDirtyRects() {
+ Common::List<Common::Rect>::iterator rOuter, rInner;
+
+ // Ensure dirty rect list has at least two entries
+ rOuter = _dirtyRects.begin();
+ for (int i = 0; i < 2; ++i, ++rOuter) {
+ if (rOuter == _dirtyRects.end())
+ return;
+ }
+
+ // Process the dirty rect list to find any rects to merge
+ for (rOuter = _dirtyRects.begin(); rOuter != _dirtyRects.end(); ++rOuter) {
+ rInner = rOuter;
+ while (++rInner != _dirtyRects.end()) {
+
+ if ((*rOuter).intersects(*rInner)) {
+ // these two rectangles overlap or
+ // are next to each other - merge them
+
+ unionRectangle(*rOuter, *rOuter, *rInner);
+
+ // remove the inner rect from the list
+ _dirtyRects.erase(rInner);
+
+ // move back to beginning of list
+ rInner = rOuter;
+ }
+ }
+ }
+}
+
+bool Screen::unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2) {
+ destRect = src1;
+ destRect.extend(src2);
+
+ return !destRect.isEmpty();
+}
+
+void Screen::loadPalette(const Common::String &name) {
+ File f(name);
+ for (int i = 0; i < PALETTE_SIZE; ++i)
+ _tempPalette[i] = f.readByte() << 2;
+}
+
+void Screen::loadBackground(const Common::String &name) {
+ File f(name);
+
+ assert(f.size() == (SCREEN_WIDTH * SCREEN_HEIGHT));
+ f.read((byte *)getPixels(), SCREEN_WIDTH * SCREEN_HEIGHT);
+ addDirtyRect(Common::Rect(0, 0, this->w, this->h));
+}
+
+void Screen::loadPage(int pageNum) {
+ assert(pageNum == 0 || pageNum == 1);
+ if (_pages[0].empty()) {
+ _pages[0].create(SCREEN_WIDTH, SCREEN_HEIGHT);
+ _pages[1].create(SCREEN_WIDTH, SCREEN_HEIGHT);
+ }
+
+ _pages[pageNum].blitFrom(*this);
+}
+
+void Screen::freePages() {
+ _pages[0].free();
+ _pages[1].free();
+}
+
+void Screen::horizMerge(int xp) {
+ if (_pages[0].empty())
+ return;
+
+ for (int y = 0; y < SCREEN_HEIGHT; ++y) {
+ byte *destP = (byte *)getBasePtr(0, y);
+ const byte *srcP = (const byte *)_pages[0].getBasePtr(0, y);
+ Common::copy(srcP, srcP + SCREEN_WIDTH - xp, destP);
+
+ if (xp != 0) {
+ srcP = (const byte *)_pages[1].getBasePtr(0, y);
+ Common::copy(srcP + SCREEN_WIDTH - xp, srcP + SCREEN_WIDTH, destP + SCREEN_WIDTH - xp);
+ }
+ }
+}
+
+void Screen::vertMerge(int yp) {
+ if (_pages[0].empty())
+ return;
+
+ for (int y = 0; y < SCREEN_HEIGHT - yp; ++y) {
+ const byte *srcP = (const byte *)_pages[0].getBasePtr(0, y);
+ byte *destP = (byte *)getBasePtr(0, y);
+ Common::copy(srcP, srcP + SCREEN_WIDTH, destP);
+ }
+
+ for (int y = SCREEN_HEIGHT - yp; y < SCREEN_HEIGHT; ++y) {
+ const byte *srcP = (const byte *)_pages[1].getBasePtr(0, y);
+ byte *destP = (byte *)getBasePtr(0, y);
+ Common::copy(srcP, srcP + SCREEN_WIDTH, destP);
+ }
+}
+
+void Screen::draw(void *data) {
+ // TODO: Figure out data structure that can be passed to method
+ assert(!data);
+ drawScreen();
+}
+
+void Screen::drawScreen() {
+ addDirtyRect(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+}
+
+void Screen::fadeIn(int step) {
+ _fadeIn = true;
+ fadeInner(step);
+}
+
+void Screen::fadeOut(int step) {
+ _fadeIn = false;
+ fadeInner(step);
+}
+
+void Screen::fadeInner(int step) {
+ for (int idx = 128; idx >= 0 && !_vm->shouldQuit(); idx -= step) {
+ int val = MAX(idx, 0);
+ bool flag = !_fadeIn;
+ if (!flag) {
+ val = -(val - 128);
+ flag = step != 0x81;
+ }
+
+ if (!flag) {
+ step = 0x80;
+ } else {
+ // Create a scaled palette from the temporary one
+ for (int i = 0; i < PALETTE_SIZE; ++i) {
+ _mainPalette[i] = (_tempPalette[i] * val * 2) >> 8;
+ }
+
+ updatePalette();
+ }
+
+ _vm->_events->pollEventsAndWait();
+ }
+}
+
+void Screen::updatePalette() {
+ updatePalette(_mainPalette, 0, 16);
+}
+
+void Screen::updatePalette(const byte *pal, int start, int count16) {
+ g_system->getPaletteManager()->setPalette(pal, start, count16 * 16);
+}
+
+void Screen::saveBackground(int slot) {
+ assert(slot > 0 && slot < 10);
+ _savedScreens[slot - 1].copyFrom(*this);
+}
+
+void Screen::restoreBackground(int slot) {
+ assert(slot > 0 && slot < 10);
+
+ blitFrom(_savedScreens[slot - 1]);
+}
+
+void Screen::frameWindow(uint bgType) {
+ if (bgType >= 4)
+ return;
+
+ if (bgType == 0) {
+ // Totally black background
+ _vm->_screen->fillRect(Common::Rect(8, 8, 224, 140), 0);
+ } else {
+ const byte *lookup = BACKGROUND_XLAT + bgType;
+ for (int yp = 8; yp < 140; ++yp) {
+ byte *destP = (byte *)_vm->_screen->getBasePtr(8, yp);
+ for (int xp = 8; xp < 224; ++xp, ++destP)
+ *destP = lookup[*destP];
+ }
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/screen.h b/engines/xeen/screen.h
new file mode 100644
index 0000000000..854a05a7cf
--- /dev/null
+++ b/engines/xeen/screen.h
@@ -0,0 +1,194 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_SCREEN_H
+#define XEEN_SCREEN_H
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/array.h"
+#include "common/keyboard.h"
+#include "common/rect.h"
+#include "xeen/font.h"
+#include "xeen/sprites.h"
+#include "xeen/xsurface.h"
+
+namespace Xeen {
+
+#define SCREEN_WIDTH 320
+#define SCREEN_HEIGHT 200
+#define PALETTE_COUNT 256
+#define PALETTE_SIZE (256 * 3)
+#define GAME_WINDOW 28
+
+class XeenEngine;
+class Screen;
+
+struct DrawStruct {
+ SpriteResource *_sprites;
+ int _frame;
+ int _x;
+ int _y;
+ int _scale;
+ int _flags;
+
+ DrawStruct(int frame, int x, int y, int scale = 0, int flags = 0) :
+ _sprites(nullptr), _frame(frame), _x(x), _y(y), _scale(scale), _flags(flags) {}
+ DrawStruct(): _sprites(nullptr), _frame(0), _x(0), _y(0), _scale(0), _flags(0) {}
+};
+
+class Window: public XSurface {
+private:
+ XeenEngine *_vm;
+ Common::Rect _bounds;
+ Common::Rect _innerBounds;
+ XSurface _savedArea;
+ int _a;
+ int _border;
+ int _xLo, _xHi;
+ int _ycL, _ycH;
+
+ void open2();
+public:
+ bool _enabled;
+public:
+ virtual void addDirtyRect(const Common::Rect &r);
+public:
+ Window();
+ Window(const Window &src);
+ Window(XeenEngine *vm, const Common::Rect &bounds, int a, int border,
+ int xLo, int ycL, int xHi, int ycH);
+
+ void setBounds(const Common::Rect &r);
+
+ const Common::Rect &getBounds() { return _bounds; }
+
+ void open();
+
+ void close();
+
+ /**
+ * Update the window
+ */
+ void update();
+
+ void frame();
+
+ /**
+ * Fill the content area of a window with the current background color
+ */
+ void fill();
+
+ const char *writeString(const Common::String &s);
+
+ void drawList(DrawStruct *items, int count);
+
+ int getString(Common::String &line, uint maxLen, int maxWidth);
+};
+
+class Screen: public FontSurface {
+private:
+ XeenEngine *_vm;
+ Common::List<Common::Rect> _dirtyRects;
+ byte _mainPalette[PALETTE_SIZE];
+ byte _tempPalette[PALETTE_SIZE];
+ XSurface _pages[2];
+ XSurface _savedScreens[10];
+ bool _fadeIn;
+
+ void mergeDirtyRects();
+
+ bool unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2);
+
+ /**
+ * Mark the entire screen for drawing
+ */
+ void drawScreen();
+
+ void fadeInner(int step);
+
+ void updatePalette();
+
+ void updatePalette(const byte *pal, int start, int count16);
+public:
+ /**
+ * Adds an area that requires redrawing on the next frame update
+ */
+ virtual void addDirtyRect(const Common::Rect &r);
+public:
+ Common::Array<Window> _windows;
+
+ Common::Array<Window *> _windowStack;
+public:
+ Screen(XeenEngine *vm);
+
+ virtual ~Screen();
+
+ void setupWindows();
+
+ void closeWindows();
+
+ void update();
+
+ /**
+ * Load a palette resource into the temporary palette
+ */
+ void loadPalette(const Common::String &name);
+
+ /**
+ * Load a background resource into memory
+ */
+ void loadBackground(const Common::String &name);
+
+ /**
+ * Copy a loaded background into a display page
+ */
+ void loadPage(int pageNum);
+
+ void freePages();
+
+ /**
+ * Merge the two pages along a horizontal split point
+ */
+ void horizMerge(int xp);
+
+ /**
+ * Merge the two pages along a vertical split point
+ */
+ void vertMerge(int yp);
+
+ void draw(void *data = nullptr);
+
+ void fadeIn(int step);
+
+ void fadeOut(int step);
+
+ void saveBackground(int slot = 1);
+
+ void restoreBackground(int slot = 1);
+
+ void frameWindow(uint bgType);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_SCREEN_H */
diff --git a/engines/xeen/scripts.cpp b/engines/xeen/scripts.cpp
new file mode 100644
index 0000000000..0e752529d5
--- /dev/null
+++ b/engines/xeen/scripts.cpp
@@ -0,0 +1,1770 @@
+/* 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.
+ *
+ */
+
+#include "common/config-manager.h"
+#include "xeen/scripts.h"
+#include "xeen/dialogs_input.h"
+#include "xeen/dialogs_whowill.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/party.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+MazeEvent::MazeEvent() : _direction(DIR_ALL), _line(-1), _opcode(OP_None) {
+}
+
+void MazeEvent::synchronize(Common::Serializer &s) {
+ int len = 5 + _parameters.size();
+ s.syncAsByte(len);
+
+ s.syncAsByte(_position.x);
+ s.syncAsByte(_position.y);
+ s.syncAsByte(_direction);
+ s.syncAsByte(_line);
+ s.syncAsByte(_opcode);
+
+ len -= 5;
+ if (s.isLoading())
+ _parameters.resize(len);
+ for (int i = 0; i < len; ++i)
+ s.syncAsByte(_parameters[i]);
+}
+
+/*------------------------------------------------------------------------*/
+
+void MazeEvents::synchronize(XeenSerializer &s) {
+ MazeEvent e;
+
+ if (s.isLoading()) {
+ clear();
+ while (!s.finished()) {
+ e.synchronize(s);
+ push_back(e);
+ }
+ } else {
+ for (uint i = 0; i < size(); ++i)
+ (*this).operator[](i).synchronize(s);
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+bool MirrorEntry::synchronize(Common::SeekableReadStream &s) {
+ if (s.pos() >= s.size())
+ return false;
+
+ char buffer[28];
+ s.read(buffer, 28);
+ buffer[27] = '\0';
+
+ _name = Common::String(buffer);
+ _mapId = s.readByte();
+ _position.x = s.readSByte();
+ _position.y = s.readSByte();
+ _direction = s.readSByte();
+ return true;
+}
+
+/*------------------------------------------------------------------------*/
+
+Scripts::Scripts(XeenEngine *vm) : _vm(vm) {
+ _whoWill = 0;
+ _itemType = 0;
+ _treasureItems = 0;
+ _lineNum = 0;
+ _charIndex = 0;
+ _v2 = 0;
+ _nEdamageType = 0;
+ _animCounter = 0;
+ _eventSkipped = false;
+ _mirrorId = -1;
+ _refreshIcons = false;
+ _scriptResult = false;
+ _scriptExecuted = false;
+ _var50 = false;
+ _redrawDone = false;
+ _windowIndex = -1;
+}
+
+int Scripts::checkEvents() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Town &town = *_vm->_town;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ _refreshIcons = false;
+ _itemType = 0;
+ _scriptExecuted = false;
+ _var50 = false;
+ _whoWill = 0;
+ Mode oldMode = _vm->_mode;
+ Common::fill(&intf._charFX[0], &intf._charFX[MAX_ACTIVE_PARTY], 0);
+ //int items = _treasureItems;
+
+ if (party._treasure._gold & party._treasure._gems) {
+ // Backup any current treasure data
+ party._savedTreasure = party._treasure;
+ party._treasure._hasItems = false;
+ party._treasure._gold = 0;
+ party._treasure._gems = 0;
+ } else {
+ party._savedTreasure._hasItems = false;
+ party._savedTreasure._gold = 0;
+ party._savedTreasure._gems = 0;
+ }
+
+ do {
+ _lineNum = 0;
+ _scriptResult = false;
+ _animCounter = 0;
+ _redrawDone = false;
+ _currentPos = party._mazePosition;
+ _charIndex = 1;
+ _v2 = 1;
+ _nEdamageType = 0;
+// int var40 = -1;
+
+ while (!_vm->shouldQuit() && _lineNum >= 0) {
+ // Break out of the events if there's an attacking monster
+ if (combat._attackMonsters[0] != -1) {
+ _eventSkipped = true;
+ break;
+ }
+
+ _eventSkipped = false;
+ uint eventIndex;
+ for (eventIndex = 0; eventIndex < map._events.size(); ++eventIndex) {
+ MazeEvent &event = map._events[eventIndex];
+
+ if (event._position == _currentPos && party._mazeDirection !=
+ (_currentPos.x | _currentPos.y) && event._line == _lineNum) {
+ if (event._direction == party._mazeDirection || event._direction == DIR_ALL) {
+ _vm->_mode = MODE_9;
+ _scriptExecuted = true;
+ doOpcode(event);
+ break;
+ } else {
+ _var50 = true;
+ }
+ }
+ }
+ if (eventIndex == map._events.size())
+ _lineNum = -1;
+ }
+ } while (!_vm->shouldQuit() && !_eventSkipped && _lineNum != -1);
+
+ intf._face1State = intf._face2State = 2;
+ if (_refreshIcons) {
+ screen.closeWindows();
+ intf.drawParty(true);
+ }
+
+ party.checkPartyDead();
+ if (party._treasure._hasItems || party._treasure._gold || party._treasure._gems)
+ party.giveTreasure();
+
+ if (_animCounter > 0 && intf._objNumber) {
+ MazeObject &selectedObj = map._mobData._objects[intf._objNumber - 1];
+
+ if (selectedObj._spriteId == (isDarkCc ? 15 : 16)) {
+ for (uint idx = 0; idx < 16; ++idx) {
+ MazeObject &obj = map._mobData._objects[idx];
+ if (obj._spriteId == (isDarkCc ? 62 : 57)) {
+ selectedObj._id = idx;
+ selectedObj._spriteId = isDarkCc ? 62 : 57;
+ break;
+ }
+ }
+ } else if (selectedObj._spriteId == 73) {
+ for (uint idx = 0; idx < 16; ++idx) {
+ MazeObject &obj = map._mobData._objects[idx];
+ if (obj._spriteId == 119) {
+ selectedObj._id = idx;
+ selectedObj._spriteId = 119;
+ break;
+ }
+ }
+ }
+ }
+
+ _animCounter = 0;
+ _vm->_mode = oldMode;
+ screen.closeWindows();
+
+ if (_scriptExecuted || !intf._objNumber || _var50) {
+ if (_var50 && !_scriptExecuted && intf._objNumber && !map._currentIsEvent) {
+ sound.playFX(21);
+ }
+ } else {
+ Window &w = screen._windows[38];
+ w.open();
+ w.writeString(NOTHING_HERE);
+ w.update();
+
+ do {
+ intf.draw3d(true);
+ events.updateGameCounter();
+ events.wait(1, true);
+ } while (!events.isKeyMousePressed());
+ events.clearEvents();
+
+ w.close();
+ }
+
+ // Restore saved treasure
+ if (party._savedTreasure._hasItems || party._savedTreasure._gold ||
+ party._savedTreasure._gems) {
+ party._treasure = party._savedTreasure;
+ }
+
+ // Clear any town loaded sprites
+ town.clearSprites();
+
+ _v2 = 1;
+ Common::fill(&intf._charFX[0], &intf._charFX[6], 0);
+
+ return _scriptResult;
+}
+
+void Scripts::openGrate(int wallVal, int action) {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ if ((wallVal != 13 || map._currentGrateUnlocked) && (!isDarkCc || wallVal != 9 ||
+ map.mazeData()._wallKind != 2)) {
+ if (wallVal != 9 && !map._currentGrateUnlocked) {
+ int charIndex = WhoWill::show(_vm, 13, action, false) - 1;
+ if (charIndex < 0) {
+ intf.draw3d(true);
+ return;
+ }
+
+ // There is a 1 in 4 chance the character will receive damage
+ if (_vm->getRandomNumber(1, 4) == 1) {
+ combat.giveCharDamage(map.mazeData()._trapDamage,
+ (DamageType)_vm->getRandomNumber(0, 6), charIndex);
+ }
+
+ // Check whether character can unlock the door
+ Character &c = party._activeParty[charIndex];
+ if ((c.getThievery() + _vm->getRandomNumber(1, 20)) <
+ map.mazeData()._difficulties._unlockDoor)
+ return;
+
+ c._experience += map.mazeData()._difficulties._unlockDoor * c.getCurrentLevel();
+ }
+
+ // Flag the grate as unlocked, and the wall the grate is on
+ map.setCellSurfaceFlags(party._mazePosition, 0x80);
+ map.setWall(party._mazePosition, party._mazeDirection, wallVal);
+
+ // Set the grate as opened and the wall on the other side of the grate
+ Common::Point pt = party._mazePosition;
+ Direction dir = (Direction)((int)party._mazeDirection ^ 2);
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ pt.y++;
+ break;
+ case DIR_EAST:
+ pt.x++;
+ break;
+ case DIR_SOUTH:
+ pt.y--;
+ break;
+ case DIR_WEST:
+ pt.x--;
+ break;
+ default:
+ break;
+ }
+
+ map.setCellSurfaceFlags(pt, 0x80);
+ map.setWall(pt, dir, wallVal);
+
+ sound.playFX(10);
+ intf.draw3d(true);
+ }
+}
+
+typedef void(Scripts::*ScriptMethodPtr)(Common::Array<byte> &);
+
+void Scripts::doOpcode(MazeEvent &event) {
+ static const ScriptMethodPtr COMMAND_LIST[] = {
+ nullptr, &Scripts::cmdDisplay1, &Scripts::cmdDoorTextSml,
+ &Scripts::cmdDoorTextLrg, &Scripts::cmdSignText,
+ &Scripts::cmdNPC, &Scripts::cmdPlayFX, &Scripts::cmdTeleport,
+ &Scripts::cmdIf, &Scripts::cmdIf, &Scripts::cmdIf,
+ &Scripts::cmdMoveObj, &Scripts::cmdTakeOrGive, &Scripts::cmdNoAction,
+ &Scripts::cmdRemove, &Scripts::cmdSetChar, &Scripts::cmdSpawn,
+ &Scripts::cmdDoTownEvent, &Scripts::cmdExit, &Scripts::cmdAlterMap,
+ &Scripts::cmdGiveExtended, &Scripts::cmdConfirmWord, &Scripts::cmdDamage,
+ &Scripts::cmdJumpRnd, &Scripts::cmdAlterEvent, &Scripts::cmdCallEvent,
+ &Scripts::cmdReturn, &Scripts::cmdSetVar, &Scripts::cmdTakeOrGive,
+ &Scripts::cmdTakeOrGive, &Scripts::cmdCutsceneEndClouds,
+ &Scripts::cmdTeleport, &Scripts::cmdWhoWill,
+ &Scripts::cmdRndDamage, &Scripts::cmdMoveWallObj, &Scripts::cmdAlterCellFlag,
+ &Scripts::cmdAlterHed, &Scripts::cmdDisplayStat, &Scripts::cmdTakeOrGive,
+ &Scripts::cmdSeatTextSml, &Scripts::cmdPlayEventVoc, &Scripts::cmdDisplayBottom,
+ &Scripts::cmdIfMapFlag, &Scripts::cmdSelRndChar, &Scripts::cmdGiveEnchanted,
+ &Scripts::cmdItemType, &Scripts::cmdMakeNothingHere, &Scripts::cmdCheckProtection,
+ &Scripts::cmdChooseNumeric, &Scripts::cmdDisplayBottomTwoLines,
+ &Scripts::cmdDisplayLarge, &Scripts::cmdExchObj, &Scripts::cmdFallToMap,
+ &Scripts::cmdDisplayMain, &Scripts::cmdGoto, &Scripts::cmdConfirmWord,
+ &Scripts::cmdGotoRandom, &Scripts::cmdCutsceneEndDarkside,
+ &Scripts::cmdCutsceneEdWorld, &Scripts::cmdFlipWorld, &Scripts::cmdPlayCD
+ };
+
+ _event = &event;
+ (this->*COMMAND_LIST[event._opcode])(event._parameters);
+}
+
+void Scripts::cmdDisplay1(Common::Array<byte> &params) {
+ Screen &screen = *_vm->_screen;
+ Common::String paramText = _vm->_map->_events._text[_event->_parameters[0]];
+ Common::String msg = Common::String::format("\r\x03""c%s", paramText.c_str());
+
+ screen._windows[12].close();
+ if (screen._windows[38]._enabled)
+ screen._windows[38].open();
+ screen._windows[38].writeString(msg);
+ screen._windows[38].update();
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDoorTextSml(Common::Array<byte> &params) {
+ Interface &intf = *_vm->_interface;
+
+ Common::String paramText = _vm->_map->_events._text[_event->_parameters[0]];
+ intf._screenText = Common::String::format("\x02\f""08\x03""c\t116\v025%s\x03""l\fd""\x01",
+ paramText.c_str());
+ intf._upDoorText = true;
+ intf.draw3d(true);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDoorTextLrg(Common::Array<byte> &params) {
+ Interface &intf = *_vm->_interface;
+
+ Common::String paramText = _vm->_map->_events._text[_event->_parameters[0]];
+ intf._screenText = Common::String::format("\f04\x03""c\t116\v030%s\x03""l\fd",
+ paramText.c_str());
+ intf._upDoorText = true;
+ intf.draw3d(true);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdSignText(Common::Array<byte> &params) {
+ Interface &intf = *_vm->_interface;
+
+ Common::String paramText = _vm->_map->_events._text[_event->_parameters[0]];
+ intf._screenText = Common::String::format("\f08\x03""c\t120\v088%s\x03""l\fd",
+ paramText.c_str());
+ intf._upDoorText = true;
+ intf.draw3d(true);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdNPC(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+
+ if (TownMessage::show(_vm, params[2], _message, map._events._text[params[1]],
+ params[3]))
+ _lineNum = params[4] - 1;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdPlayFX(Common::Array<byte> &params) {
+ _vm->_sound->playFX(params[0]);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdTeleport(Common::Array<byte> &params) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+
+ screen.closeWindows();
+
+ int mapId;
+ Common::Point pt;
+
+ if (params[0]) {
+ mapId = params[0];
+ pt = Common::Point((int8)params[1], (int8)params[2]);
+ } else {
+ assert(_mirrorId > 0);
+ MirrorEntry &me = _mirror[_mirrorId - 1];
+ mapId = me._mapId;
+ pt = me._position;
+ if (me._direction != -1)
+ party._mazeDirection = (Direction)me._direction;
+
+ if (pt.x == 0 && pt.y == 0)
+ pt.x = 999;
+
+ sound.playFX(51);
+ }
+
+ party._stepped = true;
+ if (mapId != party._mazeId) {
+ int spriteId = (intf._objNumber == 0) ? -1 :
+ map._mobData._objects[intf._objNumber - 1]._spriteId;
+
+ switch (spriteId) {
+ case 47:
+ sound.playFX(45);
+ break;
+ case 48:
+ sound.playFX(44);
+ break;
+ default:
+ break;
+ }
+
+ // Load the new map
+ map.load(mapId);
+ }
+
+ if (pt.x == 999) {
+ party.moveToRunLocation();
+ } else {
+ party._mazePosition = pt;
+ }
+
+ events.clearEvents();
+
+ if (_event->_opcode == OP_TeleportAndContinue) {
+ intf.draw3d(true);
+ _lineNum = 0;
+ } else {
+ cmdExit(params);
+ }
+}
+
+void Scripts::cmdIf(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+ uint32 mask;
+ int newLineNum;
+
+ switch (params[0]) {
+ case 16:
+ case 34:
+ case 100:
+ mask = (params[4] << 24) | (params[3] << 16) | (params[2] << 8) | params[1];
+ newLineNum = params[5];
+ break;
+ case 25:
+ case 35:
+ case 101:
+ case 106:
+ mask = (params[2] << 8) | params[1];
+ newLineNum = params[3];
+ break;
+ default:
+ mask = params[1];
+ newLineNum = params[2];
+ break;
+ }
+
+ bool result;
+ if ((_charIndex != 0 && _charIndex != 8) || params[0] == 44) {
+ result = ifProc(params[0], mask, _event->_opcode - 8, _charIndex - 1);
+ } else {
+ result = false;
+ for (int idx = 0; idx < (int)party._activeParty.size() && !result; ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && (int)idx != _v2)) {
+ result = ifProc(params[0], mask, _event->_opcode - 8, idx);
+ }
+ }
+ }
+
+ if (result)
+ _lineNum = newLineNum - 1;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdMoveObj(Common::Array<byte> &params) {
+ MazeObject &mazeObj = _vm->_map->_mobData._objects[params[0]];
+
+ if (mazeObj._position.x == params[1] && mazeObj._position.y == params[2]) {
+ // Already in position, so simply flip it
+ mazeObj._flipped = !mazeObj._flipped;
+ } else {
+ mazeObj._position.x = params[1];
+ mazeObj._position.y = params[2];
+ }
+}
+
+void Scripts::cmdTakeOrGive(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ int mode1, mode2, mode3;
+ uint32 mask1, mask2, mask3;
+ byte *extraP;
+ // TODO: See if this needs to maintain values set in other opcodes
+ int param2 = 0;
+
+ mode1 = params[0];
+ switch (mode1) {
+ case 16:
+ case 34:
+ case 100:
+ mask1 = (params[4] << 24) | (params[3] << 16) | (params[2] << 8) | params[1];
+ extraP = &params[5];
+ break;
+ case 25:
+ case 35:
+ case 101:
+ case 106:
+ mask1 = (params[2] << 8) | params[1];
+ extraP = &params[3];
+ break;
+ default:
+ mask1 = params[1];
+ extraP = &params[2];
+ break;
+ }
+
+ mode2 = *extraP++;
+ switch (mode2) {
+ case 16:
+ case 34:
+ case 100:
+ mask2 = (extraP[3] << 24) | (extraP[2] << 16) | (extraP[1] << 8) | extraP[0];
+ extraP += 4;
+ break;
+ case 25:
+ case 35:
+ case 101:
+ case 106:
+ mask2 = (extraP[1] << 8) | extraP[0];
+ extraP += 2;
+ break;
+ default:
+ mask2 = extraP[0];
+ extraP++;
+ break;
+ }
+
+ mode3 = *extraP++;
+ switch (mode3) {
+ case 16:
+ case 34:
+ case 100:
+ mask3 = (extraP[3] << 24) | (extraP[2] << 16) | (extraP[1] << 8) | extraP[0];
+ break;
+ case 25:
+ case 35:
+ case 101:
+ case 106:
+ mask3 = (extraP[1] << 8) | extraP[0];
+ break;
+ default:
+ mask3 = extraP[0];
+ break;
+ }
+
+ if (mode2)
+ screen.closeWindows();
+
+ switch (_event->_opcode) {
+ case OP_TakeOrGive_2:
+ if (_charIndex == 0 || _charIndex == 8) {
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && (int)idx != _v2)) {
+ if (ifProc(params[0], mask1, _event->_opcode == OP_TakeOrGive_4 ? 2 : 1, idx)) {
+ party.giveTake(0, 0, mode2, mask2, idx);
+ if (mode2 == 82)
+ break;
+ }
+ }
+ }
+ } else if (ifProc(params[0], mask1, _event->_opcode == OP_TakeOrGive_4 ? 2 : 1, _charIndex - 1)) {
+ party.giveTake(0, 0, mode2, mask2, _charIndex - 1);
+ }
+ break;
+
+ case OP_TakeOrGive_3:
+ if (_charIndex == 0 || _charIndex == 8) {
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && (int)idx != _v2)) {
+ if (ifProc(params[0], mask1, 1, idx) && ifProc(mode2, mask2, 1, idx)) {
+ party.giveTake(0, 0, mode2, mask3, idx);
+ if (mode2 == 82)
+ break;
+ }
+ }
+ }
+ } else if (ifProc(params[0], mask1, 1, _charIndex - 1) &&
+ ifProc(mode2, mask2, 1, _charIndex - 1)) {
+ party.giveTake(0, 0, mode2, mask3, _charIndex - 1);
+ }
+ break;
+
+ case OP_TakeOrGive_4:
+ if (_charIndex == 0 || _charIndex == 8) {
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && (int)idx != _v2)) {
+ if (ifProc(params[0], mask1, _event->_opcode == OP_TakeOrGive_4 ? 2 : 1, idx)) {
+ party.giveTake(0, 0, mode2, mask2, idx);
+ if (mode2 == 82)
+ break;
+ }
+ }
+ }
+ } else if (ifProc(params[0], mask1, 1, _charIndex - 1)) {
+ party.giveTake(0, 0, mode2, mask2, _charIndex - 1);
+ }
+ break;
+
+ default:
+ if (_charIndex == 0 || _charIndex == 8) {
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && (int)idx != _v2)) {
+ party.giveTake(mode1, mask1, mode1, mask2, idx);
+
+ switch (mode1) {
+ case 8:
+ mode1 = 0;
+ // Deliberate fall-through
+ case 21:
+ case 66:
+ if (param2) {
+ switch (mode2) {
+ case 82:
+ mode1 = 0;
+ // Deliberate fall-through
+ case 21:
+ case 34:
+ case 35:
+ case 65:
+ case 66:
+ case 100:
+ case 101:
+ case 106:
+ if (param2)
+ continue;
+
+ // Break out of character loop
+ idx = party._activeParty.size();
+ break;
+ }
+ }
+ break;
+
+ case 34:
+ case 35:
+ case 65:
+ case 100:
+ case 101:
+ case 106:
+ if (param2) {
+ _lineNum = -1;
+ return;
+ }
+
+ // Break out of character loop
+ idx = party._activeParty.size();
+ break;
+
+ default:
+ switch (mode2) {
+ case 82:
+ mode1 = 0;
+ // Deliberate fall-through
+ case 21:
+ case 34:
+ case 35:
+ case 65:
+ case 66:
+ case 100:
+ case 101:
+ case 106:
+ if (param2)
+ continue;
+
+ // Break out of character loop
+ idx = party._activeParty.size();
+ break;
+ }
+ break;
+ }
+ }
+ }
+ } else {
+ if (!party.giveTake(mode1, mask1, mode2, mask2, _charIndex - 1)) {
+ if (mode2 == 79)
+ screen.closeWindows();
+ }
+ }
+ break;
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdNoAction(Common::Array<byte> &params) {
+ // Move to next line
+ _lineNum = _vm->_party->_partyDead ? -1 : _lineNum + 1;
+}
+
+void Scripts::cmdRemove(Common::Array<byte> &params) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+
+ if (intf._objNumber) {
+ // Give the active object a completely way out of bounds position
+ MazeObject &obj = map._mobData._objects[intf._objNumber - 1];
+ obj._position = Common::Point(128, 128);
+ }
+
+ cmdMakeNothingHere(params);
+}
+
+void Scripts::cmdSetChar(Common::Array<byte> &params) {
+ if (params[0] != 7) {
+ _charIndex = WhoWill::show(_vm, 22, 3, false);
+ if (_charIndex == 0) {
+ cmdExit(params);
+ return;
+ }
+ } else {
+ _charIndex = _vm->getRandomNumber(1, _vm->_party->_activeParty.size());
+ }
+
+ _v2 = 1;
+ cmdNoAction(params);
+}
+
+void Scripts::cmdSpawn(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ if (params[0] >= map._mobData._monsters.size())
+ map._mobData._monsters.resize(params[0] + 1);
+
+ MazeMonster &monster = _vm->_map->_mobData._monsters[params[0]];
+ MonsterStruct &monsterData = _vm->_map->_monsterData[monster._spriteId];
+ monster._monsterData = &monsterData;
+ monster._position.x = params[1];
+ monster._position.y = params[2];
+ monster._frame = _vm->getRandomNumber(7);
+ monster._damageType = 0;
+ monster._isAttacking = params[1] != 0;
+ monster._hp = monsterData._hp;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDoTownEvent(Common::Array<byte> &params) {
+ _scriptResult = _vm->_town->townAction(params[0]);
+ _vm->_party->_stepped = true;
+ _refreshIcons = true;
+
+ cmdExit(params);
+}
+
+void Scripts::cmdExit(Common::Array<byte> &params) {
+ _lineNum = -1;
+}
+
+void Scripts::cmdAlterMap(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+
+ if (params[2] == DIR_ALL) {
+ for (int dir = DIR_NORTH; dir <= DIR_WEST; ++dir)
+ map.setWall(Common::Point(params[0], params[1]), (Direction)dir, params[3]);
+ } else {
+ map.setWall(Common::Point(params[0], params[1]), (Direction)params[2], params[3]);
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdGiveExtended(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+ uint32 mask;
+ int newLineNum;
+ bool result;
+
+ switch (params[0]) {
+ case 16:
+ case 34:
+ case 100:
+ mask = (params[4] << 24) | params[3] | (params[2] << 8) | (params[1] << 16);
+ newLineNum = params[5];
+ break;
+ case 25:
+ case 35:
+ case 101:
+ case 106:
+ mask = (params[2] << 8) | params[1];
+ newLineNum = params[3];
+ break;
+ default:
+ mask = params[1];
+ newLineNum = params[2];
+ break;
+ }
+
+ if ((_charIndex != 0 && _charIndex != 8) || params[0] == 44) {
+ result = ifProc(params[0], mask, _event->_opcode - OP_If1, _charIndex - 1);
+ } else {
+ result = false;
+ for (int idx = 0; idx < (int)party._activeParty.size() && !result; ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && _v2 != idx)) {
+ result = ifProc(params[0], mask, _event->_opcode - OP_If1, idx);
+ }
+ }
+ }
+
+ if (result)
+ _lineNum = newLineNum - 1;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdConfirmWord(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Common::String msg1 = params[2] ? map._events._text[params[2]] :
+ _vm->_interface->_interfaceText;
+ Common::String msg2;
+
+ if (_event->_opcode == OP_ConfirmWord_2) {
+ msg2 = map._events._text[params[3]];
+ } else if (params[3]) {
+ msg2 = "";
+ } else {
+ msg2 = WHATS_THE_PASSWORD;
+ }
+
+ int result = StringInput::show(_vm, params[0], msg1, msg2,_event->_opcode);
+ if (result) {
+ if (result == 33 && _vm->_files->_isDarkCc) {
+ doEndGame2();
+ } else if (result == 34 && _vm->_files->_isDarkCc) {
+ doWorldEnd();
+ } else if (result == 35 && _vm->_files->_isDarkCc &&
+ _vm->getGameID() == GType_WorldOfXeen) {
+ doEndGame();
+ } else if (result == 40 && !_vm->_files->_isDarkCc) {
+ doEndGame();
+ } else if (result == 60 && !_vm->_files->_isDarkCc) {
+ doEndGame2();
+ }
+ else if (result == 61 && !_vm->_files->_isDarkCc) {
+ doWorldEnd();
+ } else {
+ if (result == 59 && !_vm->_files->_isDarkCc) {
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ XeenItem &item = party._treasure._weapons[idx];
+ if (!item._id) {
+ item._id = 34;
+ item._material = 0;
+ item._bonusFlags = 0;
+ party._treasure._hasItems = true;
+
+ cmdExit(params);
+ return;
+ }
+ }
+ }
+
+ _lineNum = result == -1 ? params[3] : params[1];
+
+ return;
+ }
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDamage(Common::Array<byte> &params) {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+
+ if (!_redrawDone) {
+ intf.draw3d(true);
+ _redrawDone = true;
+ }
+
+ int damage = (params[1] << 8) | params[0];
+ combat.giveCharDamage(damage, (DamageType)params[2], _charIndex);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdJumpRnd(Common::Array<byte> &params) {
+ int v = _vm->getRandomNumber(1, params[0]);
+ if (v == params[1])
+ _lineNum = params[2] - 1;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdAlterEvent(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+
+ for (uint idx = 0; idx < map._events.size(); ++idx) {
+ MazeEvent &evt = map._events[idx];
+ if (evt._position == party._mazePosition &&
+ (evt._direction == DIR_ALL || evt._direction == party._mazeDirection) &&
+ evt._line == params[0]) {
+ evt._opcode = (Opcode)params[1];
+ }
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdCallEvent(Common::Array<byte> &params) {
+ _stack.push(StackEntry(_currentPos, _lineNum));
+ _currentPos = Common::Point(params[0], params[1]);
+ _lineNum = params[2] - 1;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdReturn(Common::Array<byte> &params) {
+ StackEntry &se = _stack.top();
+ _currentPos = se;
+ _lineNum = se.line;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdSetVar(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+ uint val;
+ _refreshIcons = true;
+
+ switch (params[0]) {
+ case 25:
+ case 35:
+ case 101:
+ case 106:
+ val = (params[2] << 8) | params[1];
+ break;
+ case 16:
+ case 34:
+ case 100:
+ val = (params[4] << 24) | (params[3] << 16) | (params[2] << 8) | params[3];
+ break;
+ default:
+ val = params[1];
+ break;
+ }
+
+ if (_charIndex != 0 && _charIndex != 8) {
+ party._activeParty[_charIndex - 1].setValue(params[0], val);
+ } else {
+ // Set value for entire party
+ for (int idx = 0; idx < (int)party._activeParty.size(); ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && _v2 != idx)) {
+ party._activeParty[idx].setValue(params[0], val);
+ }
+ }
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdCutsceneEndClouds(Common::Array<byte> &params) { error("TODO"); }
+
+void Scripts::cmdWhoWill(Common::Array<byte> &params) {
+ _charIndex = WhoWill::show(_vm, params[0], params[1], true);
+
+ if (_charIndex == 0)
+ cmdExit(params);
+ else
+ cmdNoAction(params);
+}
+
+void Scripts::cmdRndDamage(Common::Array<byte> &params) {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+
+ if (!_redrawDone) {
+ intf.draw3d(true);
+ _redrawDone = true;
+ }
+
+ combat.giveCharDamage(_vm->getRandomNumber(1, params[1]), (DamageType)params[0], _charIndex);
+ cmdNoAction(params);
+}
+
+void Scripts::cmdMoveWallObj(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+
+ map._mobData._wallItems[params[0]]._position = Common::Point(params[1], params[2]);
+ cmdNoAction(params);
+}
+
+void Scripts::cmdAlterCellFlag(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ Common::Point pt(params[0], params[1]);
+ map.cellFlagLookup(pt);
+
+ if (map._isOutdoors) {
+ MazeWallLayers &wallData = map.mazeDataCurrent()._wallData[pt.y][pt.x];
+ wallData._data = (wallData._data & 0xFFF0) | params[2];
+ } else {
+ pt.x &= 0xF;
+ pt.y &= 0xF;
+ MazeCell &cell = map.mazeDataCurrent()._cells[pt.y][pt.x];
+ cell._surfaceId = params[2];
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdAlterHed(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+
+ HeadData::HeadEntry &he = map._headData[party._mazePosition.y][party._mazePosition.x];
+ he._left = params[0];
+ he._right = params[1];
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDisplayStat(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+ Window &w = _vm->_screen->_windows[12];
+ Character &c = party._activeParty[_charIndex - 1];
+
+ if (!w._enabled)
+ w.open();
+ w.writeString(Common::String::format(_message.c_str(), c._name.c_str()));
+ w.update();
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdSeatTextSml(Common::Array<byte> &params) {
+ Interface &intf = *_vm->_interface;
+
+ intf._screenText = Common::String::format("\x2\f08\x3""c\t116\v090%s\x3l\fd\x1",
+ _message.c_str());
+ intf._upDoorText = true;
+ intf.draw3d(true);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdPlayEventVoc(Common::Array<byte> &params) {
+ SoundManager &sound = *_vm->_sound;
+ sound.playSample(nullptr, 0);
+ File f(EVENT_SAMPLES[params[0]]);
+ sound.playSample(&f, 1);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDisplayBottom(Common::Array<byte> &params) {
+ _windowIndex = 12;
+
+ display(false, 0);
+ cmdNoAction(params);
+}
+
+void Scripts::cmdIfMapFlag(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ MazeMonster &monster = map._mobData._monsters[params[0]];
+
+ if (monster._position.x >= 32 || monster._position.y >= 32) {
+ _lineNum = params[1] - 1;
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdSelRndChar(Common::Array<byte> &params) {
+ _charIndex = _vm->getRandomNumber(1, _vm->_party->_activeParty.size());
+ cmdNoAction(params);
+}
+
+void Scripts::cmdGiveEnchanted(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+
+ if (params[0] >= 35) {
+ if (params[0] < 49) {
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ XeenItem &item = party._treasure._armor[idx];
+ if (!item.empty()) {
+ item._id = params[0] - 35;
+ item._material = params[1];
+ item._bonusFlags = params[2];
+ party._treasure._hasItems = true;
+ break;
+ }
+ }
+
+ cmdNoAction(params);
+ return;
+ } else if (params[0] < 60) {
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ XeenItem &item = party._treasure._accessories[idx];
+ if (!item.empty()) {
+ item._id = params[0] - 49;
+ item._material = params[1];
+ item._bonusFlags = params[2];
+ party._treasure._hasItems = true;
+ break;
+ }
+ }
+
+ cmdNoAction(params);
+ return;
+ } else if (params[0] < 82) {
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ XeenItem &item = party._treasure._misc[idx];
+ if (!item.empty()) {
+ item._id = params[0];
+ item._material = params[1];
+ item._bonusFlags = params[2];
+ party._treasure._hasItems = true;
+ break;
+ }
+ }
+
+ cmdNoAction(params);
+ return;
+ } else {
+ party._gameFlags[6 + params[0]] = true;
+ }
+ }
+
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ XeenItem &item = party._treasure._weapons[idx];
+ if (!item.empty()) {
+ item._id = params[0];
+ item._material = params[1];
+ item._bonusFlags = params[2];
+ party._treasure._hasItems = true;
+ break;
+ }
+ }
+}
+
+void Scripts::cmdItemType(Common::Array<byte> &params) {
+ _itemType = params[0];
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdMakeNothingHere(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+
+ // Scan through the event list and mark the opcodes for all the lines of any scripts
+ // on the party's current cell as having no operation, effectively disabling them
+ for (uint idx = 0; idx < map._events.size(); ++idx) {
+ MazeEvent &evt = map._events[idx];
+ if (evt._position == party._mazePosition)
+ evt._opcode = OP_None;
+ }
+
+ cmdExit(params);
+}
+
+void Scripts::cmdCheckProtection(Common::Array<byte> &params) {
+ if (copyProtectionCheck())
+ cmdNoAction(params);
+ else
+ cmdExit(params);
+}
+
+void Scripts::cmdChooseNumeric(Common::Array<byte> &params) {
+ int choice = Choose123::show(_vm, params[0]);
+ if (choice) {
+ _lineNum = params[choice] - 1;
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDisplayBottomTwoLines(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ Window &w = _vm->_screen->_windows[12];
+
+ warning("TODO: cmdDisplayBottomTwoLines");
+ Common::String msg = Common::String::format("\r\x03c\t000\v007%s\n\n%s",
+ "",
+ map._events._text[params[1]].c_str());
+ w.close();
+ w.open();
+ w.writeString(msg);
+ w.update();
+
+ YesNo::show(_vm, true);
+ _lineNum = -1;
+}
+
+void Scripts::cmdDisplayLarge(Common::Array<byte> &params) {
+ error("TODO: Implement event text loading");
+
+ display(true, 0);
+ cmdNoAction(params);
+}
+
+void Scripts::cmdExchObj(Common::Array<byte> &params) {
+ MazeObject &obj1 = _vm->_map->_mobData._objects[params[0]];
+ MazeObject &obj2 = _vm->_map->_mobData._objects[params[1]];
+
+ Common::Point pt = obj1._position;
+ obj1._position = obj2._position;
+ obj2._position = pt;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdFallToMap(Common::Array<byte> &params) {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ party._fallMaze = params[0];
+ party._fallPosition = Common::Point(params[1], params[2]);
+ party._fallDamage = params[3];
+ intf.startFalling(true);
+
+ _lineNum = -1;
+}
+
+void Scripts::cmdDisplayMain(Common::Array<byte> &params) {
+ display(false, 0);
+ cmdNoAction(params);
+}
+
+void Scripts::cmdGoto(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ map.getCell(1);
+ if (params[0] == map._currentSurfaceId)
+ _lineNum = params[1] - 1;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdGotoRandom(Common::Array<byte> &params) {
+ _lineNum = params[_vm->getRandomNumber(1, params[0])] - 1;
+ cmdNoAction(params);
+}
+
+void Scripts::cmdCutsceneEndDarkside(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+ _vm->_saves->_wonDarkSide = true;
+ party._questItems[53] = 1;
+ party._darkSideEnd = true;
+ party._mazeId = 29;
+ party._mazeDirection = DIR_NORTH;
+ party._mazePosition = Common::Point(25, 21);
+
+ doEndGame2();
+}
+
+void Scripts::cmdCutsceneEdWorld(Common::Array<byte> &params) {
+ _vm->_saves->_wonWorld = true;
+ _vm->_party->_worldEnd = true;
+ doWorldEnd();
+}
+
+void Scripts::cmdFlipWorld(Common::Array<byte> &params) {
+ _vm->_map->_loadDarkSide = params[0] != 0;
+}
+
+void Scripts::cmdPlayCD(Common::Array<byte> &params) { error("TODO"); }
+
+void Scripts::doEndGame() {
+ doEnding("ENDGAME", 0);
+}
+
+void Scripts::doEndGame2() {
+ Party &party = *_vm->_party;
+ int v2 = 0;
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ Character &player = party._activeParty[idx];
+ if (player.hasAward(77)) {
+ v2 = 2;
+ break;
+ }
+ else if (player.hasAward(76)) {
+ v2 = 1;
+ break;
+ }
+ }
+
+ doEnding("ENDGAME2", v2);
+}
+
+void Scripts::doWorldEnd() {
+
+}
+
+void Scripts::doEnding(const Common::String &endStr, int v2) {
+ _vm->_saves->saveChars();
+
+ warning("TODO: doEnding");
+}
+
+bool Scripts::ifProc(int action, uint32 mask, int mode, int charIndex) {
+ Party &party = *_vm->_party;
+ Character &ps = party._activeParty[charIndex];
+ uint v = 0;
+
+ switch (action) {
+ case 3:
+ // Player sex
+ v = (uint)ps._sex;
+ break;
+ case 4:
+ // Player race
+ v = (uint)ps._race;
+ break;
+ case 5:
+ // Player class
+ v = (uint)ps._class;
+ break;
+ case 8:
+ // Current health points
+ v = (uint)ps._currentHp;
+ break;
+ case 9:
+ // Current spell points
+ v = (uint)ps._currentSp;
+ break;
+ case 10:
+ // Get armor class
+ v = (uint)ps.getArmorClass(false);
+ break;
+ case 11:
+ // Level bonus (extra beyond base)
+ v = ps._level._temporary;
+ break;
+ case 12:
+ // Current age, including unnatural aging
+ v = ps.getAge(false);
+ break;
+ case 13:
+ assert(mask < 18);
+ if (ps._skills[mask])
+ v = mask;
+ break;
+ case 15:
+ // Award
+ assert(mask < 128);
+ if (ps.hasAward(mask))
+ v = mask;
+ break;
+ case 16:
+ // Experience
+ v = ps._experience;
+ break;
+ case 17:
+ // Party poison resistence
+ v = party._poisonResistence;
+ break;
+ case 18:
+ // Condition
+ assert(mask < 16);
+ if (!ps._conditions[mask] && !(mask & 0x10))
+ v = mask;
+ break;
+ case 19: {
+ // Can player cast a given spell
+
+ // Get the type of character
+ int category;
+ switch (ps._class) {
+ case CLASS_KNIGHT:
+ case CLASS_ARCHER:
+ category = 0;
+ break;
+ case CLASS_PALADIN:
+ case CLASS_CLERIC:
+ category = 1;
+ break;
+ case CLASS_BARBARIAN:
+ case CLASS_DRUID:
+ category = 2;
+ break;
+ default:
+ category = 0;
+ break;
+ }
+
+ // Check if the character class can cast the particular spell
+ for (int idx = 0; idx < 39; ++idx) {
+ if (SPELLS_ALLOWED[category][idx] == mask) {
+ // Can cast it. Check if the player has it in their spellbook
+ if (ps._spells[idx])
+ v = mask;
+ break;
+ }
+ }
+ break;
+ }
+ case 20:
+ if (_vm->_files->_isDarkCc)
+ mask += 0x100;
+ assert(mask < 0x200);
+ v = party._gameFlags[mask] ? mask : 0xffffffff;
+ break;
+ case 21:
+ // Scans inventories for given item number
+ v = 0xFFFFFFFF;
+ if (mask < 82) {
+ for (int idx = 0; idx < 9; ++idx) {
+ if (mask == 35) {
+ if (ps._weapons[idx]._id == mask) {
+ v = mask;
+ break;
+ }
+ } else if (mask < 49) {
+ if (ps._armor[idx]._id == (mask - 35)) {
+ v = mask;
+ break;
+ }
+ } else if (mask < 60) {
+ if (ps._accessories[idx]._id == (mask - 49)) {
+ v = mask;
+ break;
+ }
+ } else {
+ if (ps._misc[idx]._id == (mask - 60)) {
+ v = mask;
+ break;
+ }
+ }
+ }
+ } else {
+ int baseFlag = 8 * (6 + mask);
+ for (int idx = 0; idx < 8; ++idx) {
+ if (party._gameFlags[baseFlag + idx]) {
+ v = mask;
+ break;
+ }
+ }
+ }
+ break;
+ case 25:
+ // Returns number of minutes elapsed in the day (0-1440)
+ v = party._minutes;
+ break;
+ case 34:
+ // Current party gold
+ v = party._gold;
+ break;
+ case 35:
+ // Current party gems
+ v = party._gems;
+ break;
+ case 37:
+ // Might bonus (extra beond base)
+ v = ps._might._temporary;
+ break;
+ case 38:
+ // Intellect bonus (extra beyond base)
+ v = ps._intellect._temporary;
+ break;
+ case 39:
+ // Personality bonus (extra beyond base)
+ v = ps._personality._temporary;
+ break;
+ case 40:
+ // Endurance bonus (extra beyond base)
+ v = ps._endurance._temporary;
+ break;
+ case 41:
+ // Speed bonus (extra beyond base)
+ v = ps._speed._temporary;
+ break;
+ case 42:
+ // Accuracy bonus (extra beyond base)
+ v = ps._accuracy._temporary;
+ break;
+ case 43:
+ // Luck bonus (extra beyond base)
+ v = ps._luck._temporary;
+ break;
+ case 44:
+ v = YesNo::show(_vm, mask);
+ v = (!v && !mask) ? 2 : mask;
+ break;
+ case 45:
+ // Might base (before bonus)
+ v = ps._might._permanent;
+ break;
+ case 46:
+ // Intellect base (before bonus)
+ v = ps._intellect._permanent;
+ break;
+ case 47:
+ // Personality base (before bonus)
+ v = ps._personality._permanent;
+ break;
+ case 48:
+ // Endurance base (before bonus)
+ v = ps._endurance._permanent;
+ break;
+ case 49:
+ // Speed base (before bonus)
+ v = ps._speed._permanent;
+ break;
+ case 50:
+ // Accuracy base (before bonus)
+ v = ps._accuracy._permanent;
+ break;
+ case 51:
+ // Luck base (before bonus)
+ v = ps._luck._permanent;
+ break;
+ case 52:
+ // Fire resistence (before bonus)
+ v = ps._fireResistence._permanent;
+ break;
+ case 53:
+ // Elecricity resistence (before bonus)
+ v = ps._electricityResistence._permanent;
+ break;
+ case 54:
+ // Cold resistence (before bonus)
+ v = ps._coldResistence._permanent;
+ break;
+ case 55:
+ // Poison resistence (before bonus)
+ v = ps._poisonResistence._permanent;
+ break;
+ case 56:
+ // Energy reistence (before bonus)
+ v = ps._energyResistence._permanent;
+ break;
+ case 57:
+ // Energy resistence (before bonus)
+ v = ps._magicResistence._permanent;
+ break;
+ case 58:
+ // Fire resistence (extra beyond base)
+ v = ps._fireResistence._temporary;
+ break;
+ case 59:
+ // Electricity resistence (extra beyond base)
+ v = ps._electricityResistence._temporary;
+ break;
+ case 60:
+ // Cold resistence (extra beyond base)
+ v = ps._coldResistence._temporary;
+ break;
+ case 61:
+ // Poison resistence (extra beyod base)
+ v = ps._poisonResistence._temporary;
+ break;
+ case 62:
+ // Energy resistence (extra beyond base)
+ v = ps._energyResistence._temporary;
+ break;
+ case 63:
+ // Magic resistence (extra beyond base)
+ v = ps._magicResistence._temporary;
+ break;
+ case 64:
+ // Level (before bonus)
+ v = ps._level._permanent;
+ break;
+ case 65:
+ // Total party food
+ v = party._food;
+ break;
+ case 69:
+ // Test for Levitate being active
+ v = party._levitateActive ? 1 : 0;
+ break;
+ case 70:
+ // Amount of light
+ v = party._lightCount;
+ break;
+ case 71:
+ // Party magical fire resistence
+ v = party._fireResistence;
+ break;
+ case 72:
+ // Party magical electricity resistence
+ v = party._electricityResistence;
+ break;
+ case 73:
+ // Party magical cold resistence
+ v = party._coldResistence;
+ break;
+ case 76:
+ // Day of the year (100 per year)
+ v = party._day;
+ break;
+ case 77:
+ // Armor class (extra beyond base)
+ v = ps._ACTemp;
+ break;
+ case 78:
+ // Test whether current Hp is equal to or exceeds the max HP
+ v = ps._currentHp >= ps.getMaxHP() ? 1 : 0;
+ break;
+ case 79:
+ // Test for Wizard Eye being active
+ v = party._wizardEyeActive ? 1 : 0;
+ break;
+ case 81:
+ // Test whether current Sp is equal to or exceeds the max SP
+ v = ps._currentSp >= ps.getMaxSP() ? 1 : 0;
+ break;
+ case 84:
+ // Current facing direction
+ v = (uint)party._mazeDirection;
+ break;
+ case 85:
+ // Current game year since start
+ v = party._year;
+ break;
+ case 86:
+ case 87:
+ case 88:
+ case 89:
+ case 90:
+ case 91:
+ case 92:
+ // Get a player stat
+ v = ps.getStat((Attribute)(action - 86), 0);
+ break;
+ case 93:
+ // Current day of the week (10 days per week)
+ v = party._day / 10;
+ break;
+ case 94:
+ // Test whether Walk on Water is currently active
+ v = party._walkOnWaterActive ? 1 : 0;
+ break;
+ case 99:
+ // Party skills check
+ v = party.checkSkill((Skill)mask) ? mask : 0xffffffff;
+ break;
+ case 102:
+ // Thievery skill
+ v = ps.getThievery();
+ break;
+ case 103:
+ // Get value of world flag
+ v = party._worldFlags[mask] ? mask : 0xffffffff;
+ break;
+ case 104:
+ // Get value of quest flag
+ v = party._quests[mask + (_vm->_files->_isDarkCc ? 30 : 0)] ?
+ mask : 0xffffffff;
+ break;
+ case 105:
+ // Test number of Megacredits in party. Only used by King's Engineer in Castle Burlock
+ v = party._questItems[26];
+ break;
+ case 107:
+ // Get value of character flag
+ error("Unused");
+ break;
+ default:
+ break;
+ }
+
+ switch (mode) {
+ case 0:
+ return mask >= v;
+ case 1:
+ return mask == v;
+ case 2:
+ return mask <= v;
+ default:
+ return false;
+ }
+}
+
+bool Scripts::copyProtectionCheck() {
+ // Only bother doing the protection check if it's been explicitly turned on
+ if (!ConfMan.getBool("copy_protection"))
+ return true;
+
+ // Currently not implemented
+ return true;
+}
+
+void Scripts::display(bool justifyFlag, int var46) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ Window &w = screen._windows[_windowIndex];
+
+ if (!_redrawDone) {
+ intf.draw3d(true);
+ _redrawDone = true;
+ }
+ screen._windows[38].close();
+
+ if (!justifyFlag)
+ _displayMessage = Common::String::format("\r\x3""c%s", _message.c_str());
+
+ if (!w._enabled)
+ w.open();
+
+ while (!_vm->shouldQuit()) {
+ _displayMessage = w.writeString(_displayMessage);
+ w.update();
+ if (_displayMessage.empty())
+ break;
+ events.clearEvents();
+
+ do {
+ events.updateGameCounter();
+ intf.draw3d(true);
+
+ events.wait(1, true);
+ } while (!_vm->shouldQuit() && !events.isKeyMousePressed());
+
+ w.writeString(justifyFlag ? "\r" : "\r\x3""c");
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/scripts.h b/engines/xeen/scripts.h
new file mode 100644
index 0000000000..f41be1b7c6
--- /dev/null
+++ b/engines/xeen/scripts.h
@@ -0,0 +1,341 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_SCRIPTS_H
+#define XEEN_SCRIPTS_H
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/serializer.h"
+#include "common/stack.h"
+#include "common/str-array.h"
+#include "xeen/files.h"
+#include "xeen/party.h"
+
+namespace Xeen {
+
+enum Opcode {
+ OP_None = 0x00,
+ OP_Display0x01 = 0x01,
+ OP_DoorTextSml = 0x02,
+ OP_DoorTextLrg = 0x03,
+ OP_SignText = 0x04,
+ OP_NPC = 0x05,
+ OP_PlayFX = 0x06,
+ OP_TeleportAndExit = 0x07,
+ OP_If1 = 0x08,
+ OP_If2 = 0x09,
+ OP_If3 = 0x0A,
+ OP_MoveObj = 0x0B,
+ OP_TakeOrGive = 0x0C,
+ OP_NoAction = 0x0D,
+ OP_Remove = 0x0E,
+ OP_SetChar = 0x0F,
+ OP_Spawn = 0x10,
+ OP_DoTownEvent = 0x11,
+ OP_Exit = 0x12,
+ OP_AfterMap = 0x13,
+ OP_GiveExtended = 0x14,
+ OP_ConfirmWord = 0x15,
+ OP_Damage = 0x16,
+ OP_JumpRnd = 0x17,
+ OP_AfterEvent = 0x18,
+ OP_CallEvent = 0x19,
+ OP_Return = 0x1A,
+ OP_SetVar = 0x1B,
+ OP_TakeOrGive_2 = 0x1C,
+ OP_TakeOrGive_3 = 0x1D,
+ OP_CutsceneEndClouds = 0x1E,
+ OP_TeleportAndContinue = 0x1F,
+ OP_WhoWill = 0x20,
+ OP_RndDamage = 0x21,
+ OP_MoveWallObj = 0x22,
+ OP_AlterCellFlag= 0x23,
+ OP_AlterHed = 0x24,
+ OP_DisplayStat = 0x25,
+ OP_TakeOrGive_4 = 0x26,
+ OP_SeatTextSml = 0x27,
+ OP_PlayEventVoc = 0x28,
+ OP_DisplayBottom = 0x29,
+ OP_IfMapFlag = 0x2A,
+ OP_SelRndChar = 0x2B,
+ OP_GiveEnchanted= 0x2C,
+ OP_ItemType = 0x2D,
+ OP_MakeNothingHere = 0x2E,
+ OP_NoAction_2 = 0x2F,
+ OP_ChooseNumeric= 0x30,
+ OP_DisplayBottomTwoLines = 0x31,
+ OP_DisplayLarge = 0x32,
+ OP_ExchObj = 0x33,
+ OP_FallToMap = 0x34,
+ OP_DisplayMain = 0x35,
+ OP_Goto = 0x36,
+ OP_ConfirmWord_2= 0x37,
+ OP_GotoRandom = 0x38,
+ OP_CutsceneEndDarkside = 0x39,
+ OP_CutsceneEdWorld = 0x3A,
+ OP_FlipWorld = 0x3B,
+ OP_PlayCD = 0x3C
+};
+
+class XeenEngine;
+
+class MazeEvent {
+public:
+ Common::Point _position;
+ int _direction;
+ int _line;
+ Opcode _opcode;
+ Common::Array<byte> _parameters;
+public:
+ MazeEvent();
+
+ void synchronize(Common::Serializer &s);
+};
+
+class MazeEvents : public Common::Array<MazeEvent> {
+public:
+ Common::StringArray _text;
+public:
+ void synchronize(XeenSerializer &s);
+};
+
+struct StackEntry : public Common::Point {
+ int line;
+
+ StackEntry(const Common::Point &pt, int l) : Common::Point(pt), line(l) {}
+};
+
+struct MirrorEntry {
+ Common::String _name;
+ int _mapId;
+ Common::Point _position;
+ int _direction;
+
+ MirrorEntry() : _mapId(0), _direction(DIR_ALL) {}
+
+ bool synchronize(Common::SeekableReadStream &s);
+};
+
+class Scripts {
+private:
+ XeenEngine *_vm;
+ int _treasureItems;
+ int _lineNum;
+ int _charIndex;
+ int _mirrorId;
+ int _refreshIcons;
+ int _scriptResult;
+ bool _scriptExecuted;
+ bool _var50;
+ int _windowIndex;
+ bool _redrawDone;
+ MazeEvent *_event;
+ Common::Point _currentPos;
+ Common::Stack<StackEntry> _stack;
+ Common::String _message;
+ Common::String _displayMessage;
+
+ /**
+ * Handles executing a given script command
+ */
+ void doOpcode(MazeEvent &event);
+
+ /**
+ * Display a msesage on-screen
+ */
+ void cmdDisplay1(Common::Array<byte> &params);
+
+ /**
+ * Displays a door text message using the small font
+ */
+ void cmdDoorTextSml(Common::Array<byte> &params);
+
+ /**
+ * Displays a door text message using the large font
+ */
+ void cmdDoorTextLrg(Common::Array<byte> &params);
+
+ /**
+ * Show a sign text on-screen
+ */
+ void cmdSignText(Common::Array<byte> &params);
+ void cmdNPC(Common::Array<byte> &params);
+
+ /**
+ * Play a sound FX
+ */
+ void cmdPlayFX(Common::Array<byte> &params);
+ void cmdTeleport(Common::Array<byte> &params);
+
+ /**
+ * Do a conditional check
+ */
+ void cmdIf(Common::Array<byte> &params);
+
+ /**
+ * Moves the position of an object
+ */
+ void cmdMoveObj(Common::Array<byte> &params);
+ void cmdTakeOrGive(Common::Array<byte> &params);
+
+ /**
+ * Move to the next line of the script
+ */
+ void cmdNoAction(Common::Array<byte> &params);
+ void cmdRemove(Common::Array<byte> &params);
+
+ /**
+ * Set the currently active character for other script operations
+ */
+ void cmdSetChar(Common::Array<byte> &params);
+
+ /**
+ * Spawn a monster
+ */
+ void cmdSpawn(Common::Array<byte> &params);
+ void cmdDoTownEvent(Common::Array<byte> &params);
+
+ /**
+ * Stop executing the script
+ */
+ void cmdExit(Common::Array<byte> &params);
+
+ /**
+ * Changes the value for the wall on a given cell
+ */
+ void cmdAlterMap(Common::Array<byte> &params);
+ void cmdGiveExtended(Common::Array<byte> &params);
+ void cmdConfirmWord(Common::Array<byte> &params);
+ void cmdDamage(Common::Array<byte> &params);
+
+ /**
+ * Jump if a random number matches a given value
+ */
+ void cmdJumpRnd(Common::Array<byte> &params);
+
+ /**
+ * Alter an existing event
+ */
+ void cmdAlterEvent(Common::Array<byte> &params);
+
+ /**
+ * Stores the current location and line for later resuming, and set up to execute
+ * a script at a given location
+ */
+ void cmdCallEvent(Common::Array<byte> &params);
+
+ /**
+ * Return from executing a script to the script location that previously
+ * called the script
+ */
+ void cmdReturn(Common::Array<byte> &params);
+ void cmdSetVar(Common::Array<byte> &params);
+ void cmdCutsceneEndClouds(Common::Array<byte> &params);
+ void cmdWhoWill(Common::Array<byte> &params);
+ void cmdRndDamage(Common::Array<byte> &params);
+ void cmdMoveWallObj(Common::Array<byte> &params);
+ void cmdAlterCellFlag(Common::Array<byte> &params);
+ void cmdAlterHed(Common::Array<byte> &params);
+ void cmdDisplayStat(Common::Array<byte> &params);
+ void cmdSeatTextSml(Common::Array<byte> &params);
+ void cmdPlayEventVoc(Common::Array<byte> &params);
+ void cmdDisplayBottom(Common::Array<byte> &params);
+ void cmdIfMapFlag(Common::Array<byte> &params);
+ void cmdSelRndChar(Common::Array<byte> &params);
+ void cmdGiveEnchanted(Common::Array<byte> &params);
+ void cmdItemType(Common::Array<byte> &params);
+
+ /**
+ * Disable all the scripts at the party's current position
+ */
+ void cmdMakeNothingHere(Common::Array<byte> &params);
+ void cmdCheckProtection(Common::Array<byte> &params);
+
+ /**
+ * Given a number of options, and a list of line numbers associated with
+ * those options, jumps to whichever line for the option the user selects
+ */
+ void cmdChooseNumeric(Common::Array<byte> &params);
+ void cmdDisplayBottomTwoLines(Common::Array<byte> &params);
+ void cmdDisplayLarge(Common::Array<byte> &params);
+
+ /**
+ * Exchange the positions of two objects in the maze
+ */
+ void cmdExchObj(Common::Array<byte> &params);
+ void cmdFallToMap(Common::Array<byte> &params);
+ void cmdDisplayMain(Common::Array<byte> &params);
+
+ /**
+ * Jumps to a given line number if the surface at relative cell position 1 matches
+ * a specified surface.
+ * @remarks This opcode is apparently never actually used
+ */
+ void cmdGoto(Common::Array<byte> &params);
+
+ /**
+ * Pick a random value from the parameter list and jump to that line number
+ */
+ void cmdGotoRandom(Common::Array<byte> &params);
+ void cmdCutsceneEndDarkside(Common::Array<byte> &params);
+ void cmdCutsceneEdWorld(Common::Array<byte> &params);
+ void cmdFlipWorld(Common::Array<byte> &params);
+ void cmdPlayCD(Common::Array<byte> &params);
+
+ int whoWill(int v1, int v2, int v3);
+
+ void doEndGame();
+
+ void doEndGame2();
+
+ void doWorldEnd();
+
+ void doEnding(const Common::String &endStr, int v2);
+
+ /**
+ * This monstrosity handles doing the various types of If checks on various data
+ */
+ bool ifProc(int action, uint32 mask, int mode, int charIndex);
+
+ bool copyProtectionCheck();
+
+ void display(bool justifyFlag, int var46);
+public:
+ int _animCounter;
+ bool _eventSkipped;
+ int _whoWill;
+ int _nEdamageType;
+ int _itemType;
+ int _v2;
+ Common::Array<MirrorEntry> _mirror;
+public:
+ Scripts(XeenEngine *vm);
+
+ int checkEvents();
+
+ void openGrate(int wallVal, int action);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_SCRIPTS_H */
diff --git a/engines/xeen/sound.cpp b/engines/xeen/sound.cpp
new file mode 100644
index 0000000000..f5cc40249d
--- /dev/null
+++ b/engines/xeen/sound.cpp
@@ -0,0 +1,100 @@
+/* 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.
+ *
+ */
+
+#include "audio/decoders/voc.h"
+#include "xeen/sound.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+SoundManager *Voc::_sound;
+
+Voc::Voc(const Common::String &name) {
+ if (!open(name))
+ error("Could not open - %s", name.c_str());
+}
+
+void Voc::init(XeenEngine *vm) {
+ _sound = vm->_sound;
+}
+
+void Voc::play() {
+ _sound->playSound(this, _soundHandle);
+}
+
+void Voc::stop() {
+ _sound->stopSound(_soundHandle);
+}
+
+/*------------------------------------------------------------------------*/
+
+SoundManager *Music::_sound;
+
+Music::Music(const Common::String &name) {
+ if (!open(name))
+ error("Could not open - %s", name.c_str());
+}
+
+void Music::init(XeenEngine *vm) {
+ _sound = vm->_sound;
+}
+
+void Music::play() {
+ _sound->playMusic(this, _soundHandle);
+}
+
+void Music::stop() {
+ _sound->stopSound(_soundHandle);
+}
+
+/*------------------------------------------------------------------------*/
+
+SoundManager::SoundManager(XeenEngine *vm, Audio::Mixer *mixer): _mixer(mixer) {
+}
+
+void SoundManager::proc2(Common::SeekableReadStream &f) {
+ // TODO
+}
+
+void SoundManager::startMusic(int v1) {
+ // TODO
+}
+
+void SoundManager::stopMusic(int id) {
+ // TODO
+}
+
+void SoundManager::playSound(Common::SeekableReadStream *s, Audio::SoundHandle &soundHandle,
+ Audio::Mixer::SoundType soundType) {
+ Audio::SeekableAudioStream *stream = Audio::makeVOCStream(s, 0);
+ _mixer->playStream(soundType, &soundHandle, stream);
+}
+
+void SoundManager::playMusic(Common::SeekableReadStream *s, Audio::SoundHandle &soundHandle) {
+ // TODO
+}
+
+void SoundManager::stopSound(Audio::SoundHandle &soundHandle) {
+ _mixer->stopHandle(soundHandle);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/sound.h b/engines/xeen/sound.h
new file mode 100644
index 0000000000..954d324e8d
--- /dev/null
+++ b/engines/xeen/sound.h
@@ -0,0 +1,119 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_SOUND_H
+#define XEEN_SOUND_H
+
+#include "audio/mixer.h"
+#include "audio/audiostream.h"
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "xeen/files.h"
+
+namespace Xeen {
+
+class SoundManager;
+
+class Voc: public Common::File {
+private:
+ static SoundManager *_sound;
+ Audio::SoundHandle _soundHandle;
+public:
+ Voc() {}
+ Voc(const Common::String &name);
+ virtual ~Voc() { stop(); }
+ static void init(XeenEngine *vm);
+
+ /**
+ * Start playing the sound
+ */
+ void play();
+
+ /**
+ * Stop playing the sound
+ */
+ void stop();
+};
+
+class Music : public Common::File {
+private:
+ static SoundManager *_sound;
+ Audio::SoundHandle _soundHandle;
+public:
+ Music() {}
+ Music(const Common::String &name);
+ virtual ~Music() { stop(); }
+ static void init(XeenEngine *vm);
+
+ /**
+ * Start playing the sound
+ */
+ void play();
+
+ /**
+ * Stop playing the sound
+ */
+ void stop();
+};
+
+class SoundManager {
+private:
+ Audio::Mixer *_mixer;
+public:
+ SoundManager(XeenEngine *vm, Audio::Mixer *mixer);
+
+ void proc2(Common::SeekableReadStream &f);
+
+ void loadMusic(const Common::String &name, int v2) {}
+
+ void startMusic(int v1);
+
+ void stopMusic(int id);
+
+ void playSong(Common::SeekableReadStream &f) {}
+
+ /**
+ * Play a given sound
+ */
+ void playSound(Common::SeekableReadStream *s, Audio::SoundHandle &soundHandle,
+ Audio::Mixer::SoundType soundType = Audio::Mixer::kSFXSoundType);
+
+ /**
+ * Play a given music
+ */
+ void playMusic(Common::SeekableReadStream *s, Audio::SoundHandle &soundHandle);
+
+ /**
+ * Stop playing a sound
+ */
+ void stopSound(Audio::SoundHandle &soundHandle);
+
+ void playSample(const Common::SeekableReadStream *stream, int v2 = 1) {}
+
+ bool playSample(int v1, int v2) { return false; }
+
+ void playFX(int id) {}
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_SOUND_H */
diff --git a/engines/xeen/spells.cpp b/engines/xeen/spells.cpp
new file mode 100644
index 0000000000..ee3085d052
--- /dev/null
+++ b/engines/xeen/spells.cpp
@@ -0,0 +1,1329 @@
+/* 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.
+ *
+ */
+
+#include "xeen/spells.h"
+#include "xeen/dialogs_items.h"
+#include "xeen/dialogs_spells.h"
+#include "xeen/files.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+Spells::Spells(XeenEngine *vm) : _vm(vm) {
+ _lastCaster = 0;
+
+ load();
+}
+
+void Spells::load() {
+ File f1("spells.xen");
+ while (f1.pos() < f1.size())
+ _spellNames.push_back(f1.readString());
+ f1.close();
+}
+
+int Spells::calcSpellCost(int spellId, int expenseFactor) const {
+ int amount = SPELL_COSTS[spellId];
+ return (amount >= 0) ? (amount * 100) << expenseFactor :
+ (amount * -500) << expenseFactor;
+}
+
+int Spells::calcSpellPoints(int spellId, int expenseFactor) const {
+ int amount = SPELL_COSTS[spellId];
+ return (amount >= 0) ? amount : amount * -1 * expenseFactor;
+}
+
+typedef void(Spells::*SpellMethodPtr)();
+
+void Spells::executeSpell(MagicSpell spellId) {
+ static const SpellMethodPtr SPELL_LIST[76] = {
+ &Spells::acidSpray, &Spells::awaken, &Spells::beastMaster,
+ &Spells::bless, &Spells::clairvoyance, &Spells::coldRay,
+ &Spells::createFood, &Spells::cureDisease, &Spells::cureParalysis,
+ &Spells::curePoison, &Spells::cureWounds, &Spells::dancingSword,
+ &Spells::dayOfProtection, &Spells::dayOfSorcery, &Spells::deadlySwarm,
+ &Spells::detectMonster, &Spells::divineIntervention, &Spells::dragonSleep,
+ &Spells::elementalStorm, &Spells::enchantItem, &Spells::energyBlast,
+ &Spells::etherialize, &Spells::fantasticFreeze, &Spells::fieryFlail,
+ &Spells::fingerOfDeath, &Spells::fireball, &Spells::firstAid,
+ &Spells::flyingFist, &Spells::frostbite, &Spells::golemStopper,
+ &Spells::heroism, &Spells::holyBonus, &Spells::holyWord,
+ &Spells::hypnotize, &Spells::identifyMonster, &Spells::implosion,
+ &Spells::incinerate, &Spells::inferno, &Spells::insectSpray,
+ &Spells::itemToGold, &Spells::jump, &Spells::levitate,
+ &Spells::light, &Spells::lightningBolt, &Spells::lloydsBeacon,
+ &Spells::magicArrow, &Spells::massDistortion, &Spells::megaVolts,
+ &Spells::moonRay, &Spells::naturesCure, &Spells::pain,
+ &Spells::poisonVolley, &Spells::powerCure, &Spells::powerShield,
+ &Spells::prismaticLight, &Spells::protectionFromElements, &Spells::raiseDead,
+ &Spells::rechargeItem, &Spells::resurrection, &Spells::revitalize,
+ &Spells::shrapMetal, &Spells::sleep, &Spells::sparks,
+ &Spells::starBurst, &Spells::stoneToFlesh, &Spells::sunRay,
+ &Spells::superShelter, &Spells::suppressDisease, &Spells::suppressPoison,
+ &Spells::teleport, &Spells::timeDistortion, &Spells::townPortal,
+ &Spells::toxicCloud, &Spells::turnUndead, &Spells::walkOnWater,
+ &Spells::wizardEye
+ };
+
+ (this->*SPELL_LIST[spellId])();
+}
+
+void Spells::spellFailed() {
+ ErrorScroll::show(_vm, SPELL_FAILED, WT_NONFREEZED_WAIT);
+}
+
+void Spells::castItemSpell(int itemSpellId) {
+ switch (itemSpellId) {
+ case 15:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_Jump);
+ return;
+ }
+ break;
+ case 20:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_WizardEye);
+ return;
+ }
+ break;
+ case 27:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_LloydsBeacon);
+ return;
+ }
+ break;
+ case 32:
+ frostbite2();
+ break;
+ case 41:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_Teleport);
+ return;
+ }
+ break;
+ case 47:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_SuperShelter);
+ return;
+ }
+ break;
+ case 54:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_TownPortal);
+ return;
+ }
+ break;
+ case 57:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_Etheralize);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ static const MagicSpell spells[73] = {
+ MS_Light, MS_Awaken, MS_MagicArrow, MS_FirstAid, MS_FlyingFist,
+ MS_EnergyBlast, MS_Sleep, MS_Revitalize, MS_CureWounds, MS_Sparks,
+ MS_Shrapmetal, MS_InsectSpray, MS_ToxicCloud, MS_ProtFromElements, MS_Pain,
+ MS_Jump, MS_BeastMaster, MS_Clairvoyance, MS_TurnUndead, MS_Levitate,
+ MS_WizardEye, MS_Bless, MS_IdentifyMonster, MS_LightningBolt, MS_HolyBonus,
+ MS_PowerCure, MS_NaturesCure, MS_LloydsBeacon, MS_PowerShield, MS_Heroism,
+ MS_Hynotize, MS_WalkOnWater, NO_SPELL, MS_DetectMonster, MS_Fireball,
+ MS_ColdRay, MS_CurePoison, MS_AcidSpray, MS_TimeDistortion, MS_DragonSleep,
+ MS_CureDisease, MS_Teleport, MS_FingerOfDeath, MS_CureParalysis, MS_GolemStopper,
+ MS_PoisonVolley, MS_DeadlySwarm, MS_SuperShelter, MS_DayOfProtection, MS_DayOfProtection,
+ MS_CreateFood, MS_FieryFlail, MS_RechargeItem, MS_FantasticFreeze, MS_TownPortal,
+ MS_StoneToFlesh, MS_RaiseDead, MS_Etheralize, MS_DancingSword, MS_MoonRay,
+ MS_MassDistortion, MS_PrismaticLight, MS_EnchantItem, MS_Incinerate, MS_HolyWord,
+ MS_Resurrection, MS_ElementalStorm, MS_MegaVolts, MS_Inferno, MS_SunRay,
+ MS_Implosion, MS_StarBurst, MS_DivineIntervention
+ };
+
+ executeSpell(spells[itemSpellId]);
+}
+
+int Spells::castSpell(Character *c, MagicSpell spellId) {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+ int oldTillMove = intf._tillMove;
+ int result = 1;
+ combat._oldCharacter = c;
+
+ // Try and subtract the SP and gem requirements for the spell
+ int resultError = subSpellCost(*c, spellId);
+ if (resultError) {
+ CantCast::show(_vm, spellId, resultError);
+ result = -1;
+ } else {
+ // Some spells have special handling
+ switch (spellId) {
+ case MS_EnchantItem:
+ case MS_Etheralize:
+ case MS_Jump:
+ case MS_LloydsBeacon:
+ case MS_SuperShelter:
+ case MS_Teleport:
+ case MS_TownPortal:
+ case MS_WizardEye:
+ if (_vm->_mode != MODE_COMBAT) {
+ executeSpell(spellId);
+ } else {
+ // Return the spell costs and flag that another spell can be selected
+ addSpellCost(*c, spellId);
+ NotWhileEngaged::show(_vm, spellId);
+ result = -1;
+ }
+ break;
+
+ default:
+ executeSpell(spellId);
+ break;
+ }
+ }
+
+ combat._moveMonsters = 1;
+ intf._tillMove = oldTillMove;
+ return result;
+}
+
+int Spells::subSpellCost(Character &c, int spellId) {
+ Party &party = *_vm->_party;
+ int gemCost = SPELL_GEM_COST[spellId];
+ int spCost = SPELL_COSTS[spellId];
+
+ // Negative SP costs indicate it's dependent on the character's level
+ if (spCost <= 0) {
+ spCost = c.getCurrentLevel() * (spCost * -1);
+ }
+
+ if (spCost > c._currentSp)
+ // Not enough SP
+ return 1;
+ if (gemCost > (int)party._gems)
+ // Not enough gems
+ return 2;
+
+ c._currentSp -= spCost;
+ party._gems -= gemCost;
+ return 0;
+}
+
+void Spells::addSpellCost(Character &c, int spellId) {
+ Party &party = *_vm->_party;
+ int gemCost = SPELL_GEM_COST[spellId];
+ int spCost = SPELL_COSTS[spellId];
+
+ if (spCost < 1)
+ spCost *= -1 * c.getCurrentLevel();
+
+ c._currentSp += spCost;
+ party._gems += gemCost;
+}
+
+void Spells::acidSpray() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 15;
+ combat._damageType = DT_POISON;
+ combat._rangeType = RT_ALL;
+ sound.playFX(17);
+ combat.multiAttack(10);
+}
+
+void Spells::awaken() {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ Character &c = party._activeParty[idx];
+ c._conditions[ASLEEP] = 0;
+ if (c._currentHp > 0)
+ c._conditions[UNCONSCIOUS] = 0;
+ }
+
+ intf.drawParty(true);
+ sound.playFX(30);
+}
+
+void Spells::beastMaster() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_BEASTMASTER;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(7);
+}
+
+void Spells::bless() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ sound.playFX(30);
+ party._blessed = combat._oldCharacter->getCurrentLevel();
+}
+
+void Spells::clairvoyance() {
+ _vm->_party->_clairvoyanceActive = true;
+ _vm->_sound->playFX(20);
+}
+
+void Spells::coldRay() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = _vm->getRandomNumber(2, 4) * combat._oldCharacter->getCurrentLevel();
+ combat._damageType = DT_COLD;
+ combat._rangeType = RT_ALL;
+ sound.playFX(15);
+ combat.multiAttack(8);
+}
+
+void Spells::createFood() {
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ party._food += party._activeParty.size();
+ sound.playFX(20);
+}
+
+void Spells::cureDisease() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_CureDisease);
+ if (!c)
+ return;
+
+ sound.playFX(30);
+ c->addHitPoints(0);
+ c->_conditions[DISEASED] = 0;
+ intf.drawParty(true);
+}
+
+void Spells::cureParalysis() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_CureParalysis);
+ if (!c)
+ return;
+
+ sound.playFX(30);
+ c->addHitPoints(0);
+ c->_conditions[PARALYZED] = 0;
+ intf.drawParty(true);
+}
+
+void Spells::curePoison() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_CurePoison);
+ if (!c)
+ return;
+
+ sound.playFX(30);
+ c->addHitPoints(0);
+ c->_conditions[POISONED] = 0;
+ intf.drawParty(true);
+}
+
+void Spells::cureWounds() {
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_CureWounds);
+ if (!c)
+ return;
+
+ if (c->isDead()) {
+ spellFailed();
+ } else {
+ sound.playFX(30);
+ c->addHitPoints(15);
+ }
+}
+
+void Spells::dancingSword() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = _vm->getRandomNumber(6, 14) * combat._oldCharacter->getCurrentLevel();
+ combat._damageType = DT_PHYSICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(14);
+}
+
+void Spells::dayOfProtection() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ int lvl = combat._oldCharacter->getCurrentLevel();
+ party._walkOnWaterActive = true;
+ party._heroism = lvl;
+ party._holyBonus = lvl;
+ party._blessed = lvl;
+ party._poisonResistence = lvl;
+ party._coldResistence = lvl;
+ party._electricityResistence = lvl;
+ party._fireResistence = lvl;
+ party._lightCount = lvl;
+ sound.playFX(20);
+}
+
+void Spells::dayOfSorcery() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ int lvl = combat._oldCharacter->getCurrentLevel();
+ party._powerShield = lvl;
+ party._clairvoyanceActive = true;
+ party._wizardEyeActive = true;
+ party._levitateActive = true;
+ party._lightCount = lvl;
+ party._automapOn = false;
+ sound.playFX(20);
+}
+
+void Spells::deadlySwarm() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 40;
+ combat._damageType = DT_PHYSICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(13);
+ combat.multiAttack(15);
+}
+
+void Spells::detectMonster() {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Window &w = screen._windows[19];
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ int grid[7][7];
+
+ SpriteResource sprites(isDarkCc ? "detectmn.icn" : "detctmon.icn");
+ Common::fill(&grid[0][0], &grid[7][7], 0);
+
+ w.open();
+ w.writeString(DETECT_MONSTERS);
+ sprites.draw(w, 0, Common::Point(243, 80));
+
+ for (int yDiff = 3; yDiff >= -3; --yDiff) {
+ for (int xDiff = -3; xDiff <= 3; ++xDiff) {
+ for (uint monIndex = 0; monIndex < map._mobData._monsters.size(); ++monIndex) {
+ MazeMonster &monster = map._mobData._monsters[monIndex];
+ Common::Point pt = party._mazePosition + Common::Point(xDiff, yDiff);
+ if (monster._position == pt) {
+ if (++grid[yDiff][xDiff] > 3)
+ grid[yDiff][xDiff] = 3;
+
+ sprites.draw(w, grid[yDiff][xDiff], Common::Point(xDiff * 9 + 244,
+ yDiff * 7 + 81));
+ }
+ }
+ }
+ }
+
+ sprites.draw(w, party._mazeDirection + 1, Common::Point(270, 101));
+ sound.playFX(20);
+ w.update();
+
+ do {
+ events.updateGameCounter();
+ intf.draw3d(true);
+
+ events.wait(1);
+ } while (!events.isKeyMousePressed());
+
+ w.close();
+}
+
+void Spells::divineIntervention() {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ Character &castChar = *combat._oldCharacter;
+
+ if ((castChar._tempAge + 5) > 250) {
+ castChar._tempAge = 250;
+ } else {
+ castChar._tempAge += 5;
+ }
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ Character &c = party._activeParty[idx];
+ Common::fill(&c._conditions[CURSED], &c._conditions[ERADICATED], 0);
+ if (!c._conditions[ERADICATED])
+ c._currentHp = c.getMaxHP();
+ }
+
+ sound.playFX(20);
+ intf.drawParty(true);
+}
+
+void Spells::dragonSleep() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_DRAGONSLEEP;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(18);
+ combat.multiAttack(7);
+}
+
+void Spells::elementalStorm() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+ static const int STORM_FX_LIST[4] = { 13, 14, 15, 17 };
+ static const int STORM_MA_LIST[4] = { 0, 5, 9, 10 };
+
+ combat._monsterDamage = 150;
+ combat._damageType = (DamageType)_vm->getRandomNumber(DT_FIRE, DT_POISON);
+ combat._rangeType = RT_ALL;
+ sound.playFX(STORM_FX_LIST[combat._damageType]);
+ combat.multiAttack(STORM_MA_LIST[combat._damageType]);
+}
+
+void Spells::enchantItem() {
+ Mode oldMode = _vm->_mode;
+
+ Character *c = SpellOnWho::show(_vm, MS_EnchantItem);
+ if (!c)
+ return;
+
+ ItemsDialog::show(_vm, c, ITEMMODE_ENCHANT);
+
+ _vm->_mode = oldMode;
+}
+
+void Spells::energyBlast() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = combat._oldCharacter->getCurrentLevel() * 2;
+ combat._damageType = DT_ENERGY;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(16);
+ combat.multiAttack(13);
+}
+
+void Spells::etherialize() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ Common::Point pt = party._mazePosition + Common::Point(
+ SCREEN_POSITIONING_X[party._mazeDirection][7],
+ SCREEN_POSITIONING_Y[party._mazeDirection][7]
+ );
+
+ if ((map.mazeData()._mazeFlags & RESTRICTION_ETHERIALIZE) ||
+ map.mazeLookup(pt, 0, 0xffff) == INVALID_CELL) {
+ spellFailed();
+ } else {
+ party._mazePosition = pt;
+ sound.playFX(51);
+ }
+}
+
+void Spells::fantasticFreeze() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 40;
+ combat._damageType = DT_COLD;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(15);
+ combat.multiAttack(8);
+}
+
+void Spells::fieryFlail() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 100;
+ combat._damageType = DT_FIRE;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(13);
+ combat.multiAttack(2);
+}
+
+void Spells::fingerOfDeath() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_FINGEROFDEATH;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(14);
+}
+
+void Spells::fireball() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = _vm->getRandomNumber(3, 7) * combat._oldCharacter->getCurrentLevel();
+ combat._damageType = DT_FIRE;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(13);
+ combat.multiAttack(0);
+}
+
+void Spells::firstAid() {
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_FirstAid);
+ if (!c)
+ return;
+
+ if (c->isDead()) {
+ spellFailed();
+ } else {
+ sound.playFX(30);
+ c->addHitPoints(6);
+ }
+}
+
+void Spells::flyingFist() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 6;
+ combat._damageType = DT_PHYSICAL;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(18);
+ combat.multiAttack(14);
+}
+
+void Spells::frostbite() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 35;
+ combat._damageType = DT_COLD;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(8);
+ combat.multiAttack(8);
+}
+
+void Spells::golemStopper() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_GOLEMSTOPPER;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(16);
+ combat.multiAttack(6);
+}
+
+void Spells::heroism() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ sound.playFX(30);
+ party._heroism = combat._oldCharacter->getCurrentLevel();
+}
+
+void Spells::holyBonus() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ sound.playFX(30);
+ party._holyBonus = combat._oldCharacter->getCurrentLevel();
+}
+
+void Spells::holyWord() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_HOLYWORD;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(13);
+}
+
+void Spells::hypnotize() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_HYPNOTIZE;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(7);
+}
+
+void Spells::identifyMonster() {
+ Combat &combat = *_vm->_combat;
+
+ if (combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1
+ && combat._attackMonsters[2] == -1) {
+ spellFailed();
+ } else {
+ IdentifyMonster::show(_vm);
+ }
+}
+
+void Spells::implosion() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 1000;
+ combat._damageType = DT_ENERGY;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(18);
+ combat.multiAttack(6);
+}
+
+void Spells::incinerate() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 250;
+ combat._damageType = DT_FIRE;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(22);
+ combat.multiAttack(1);
+}
+
+void Spells::inferno() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 250;
+ combat._damageType = DT_FIRE;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(13);
+ combat.multiAttack(1);
+}
+
+void Spells::insectSpray() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_INSECT_SPRAY;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(17);
+ combat.multiAttack(10);
+}
+
+void Spells::itemToGold() {
+ Character *c = SpellOnWho::show(_vm, MS_ItemToGold);
+ if (!c)
+ return;
+
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_FF;
+
+ _vm->_screen->_windows[11].close();
+ ItemsDialog::show(_vm, c, ITEMMODE_TO_GOLD);
+
+ _vm->_mode = oldMode;
+}
+
+void Spells::jump() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ if (map._isOutdoors) {
+ map.getCell(7);
+ if (map._currentWall != 1) {
+ map.getCell(14);
+ if (map._currentSurfaceId != 0 && map._currentWall != 1) {
+ party._mazePosition += Common::Point(
+ SCREEN_POSITIONING_X[party._mazeDirection][14],
+ SCREEN_POSITIONING_Y[party._mazeDirection][14]
+ );
+ sound.playFX(51);
+ party._stepped = true;
+ return;
+ }
+ }
+ } else {
+ Common::Point pt = party._mazePosition + Common::Point(
+ SCREEN_POSITIONING_X[party._mazeDirection][7],
+ SCREEN_POSITIONING_Y[party._mazeDirection][7]);
+ if (!map.mazeLookup(party._mazePosition, MONSTER_GRID_BITMASK[party._mazeDirection]) &&
+ !map.mazeLookup(pt, MONSTER_GRID_BITMASK[party._mazeDirection])) {
+ party._mazePosition += Common::Point(
+ SCREEN_POSITIONING_X[party._mazeDirection][14],
+ SCREEN_POSITIONING_Y[party._mazeDirection][14]
+ );
+ sound.playFX(51);
+ party._stepped = true;
+ return;
+ }
+ }
+
+ spellFailed();
+}
+
+void Spells::levitate() {
+ _vm->_party->_levitateActive = true;
+ _vm->_sound->playFX(20);
+}
+
+void Spells::light() {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ ++party._lightCount;
+ if (intf._intrIndex1)
+ party._stepped = true;
+ sound.playFX(39);
+}
+
+void Spells::lightningBolt() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = _vm->getRandomNumber(4, 6) * combat._oldCharacter->getCurrentLevel();
+ combat._damageType = DT_ELECTRICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(14);
+ combat.multiAttack(3);
+}
+
+void Spells::lloydsBeacon() {
+ if (_vm->_map->mazeData()._mazeFlags & RESTRICTION_LLOYDS_BEACON) {
+ spellFailed();
+ } else {
+ if (!LloydsBeacon::show(_vm))
+ spellFailed();
+ }
+}
+
+void Spells::magicArrow() {
+ Combat &combat = *_vm->_combat;
+ combat._monsterDamage = 0;
+ combat._damageType = DT_MAGIC_ARROW;
+ combat._rangeType = RT_SINGLE;
+ combat.multiAttack(11);
+}
+
+void Spells::massDistortion() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_MASS_DISTORTION;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(6);
+}
+
+void Spells::megaVolts() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 150;
+ combat._damageType = DT_ELECTRICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(14);
+ combat.multiAttack(4);
+}
+
+void Spells::moonRay() {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 30;
+ combat._damageType = DT_ENERGY;
+ combat._rangeType = RT_ALL;
+ sound.playFX(16);
+ combat.multiAttack(13);
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ sound.playFX(30);
+ party._activeParty[idx].addHitPoints(_vm->getRandomNumber(1, 30));
+ }
+
+ intf.drawParty(true);
+}
+
+void Spells::naturesCure() {
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_NaturesCure);
+ if (!c)
+ return;
+
+ if (c->isDead()) {
+ spellFailed();
+ } else {
+ sound.playFX(30);
+ c->addHitPoints(25);
+ }
+}
+
+void Spells::pain() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_PHYSICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(14);
+}
+
+void Spells::poisonVolley() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 10;
+ combat._damageType = DT_POISON_VOLLEY;
+ combat._rangeType = RT_ALL;
+ sound.playFX(49);
+ combat.multiAttack(11);
+}
+
+void Spells::powerCure() {
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_PowerCure);
+ if (!c)
+ return;
+
+ if (c->isDead()) {
+ spellFailed();
+ } else {
+ sound.playFX(30);
+ c->addHitPoints(_vm->getRandomNumber(2, 12) * _vm->_combat->_oldCharacter->getCurrentLevel());
+ }
+}
+
+void Spells::powerShield() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ sound.playFX(20);
+ party._powerShield = combat._oldCharacter->getCurrentLevel();
+}
+
+void Spells::prismaticLight() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 80;
+ combat._damageType = (DamageType)_vm->getRandomNumber(DT_PHYSICAL, DT_ENERGY);
+ combat._rangeType = RT_ALL;
+ sound.playFX(18);
+ combat.multiAttack(14);
+}
+
+void Spells::protectionFromElements() {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ Character &c = *combat._oldCharacter;
+ int resist = MIN(c.getCurrentLevel() * 2 + 5, (uint)200);
+
+ int elementType = SelectElement::show(_vm, MS_ProtFromElements);
+ if (elementType != -1) {
+ switch (elementType) {
+ case DT_FIRE:
+ party._fireResistence = resist;
+ break;
+ case DT_ELECTRICAL:
+ party._fireResistence = resist;
+ break;
+ case DT_COLD:
+ party._coldResistence = resist;
+ break;
+ case DT_POISON:
+ party._poisonResistence = resist;
+ break;
+ default:
+ break;
+ }
+
+ sound.playFX(20);
+ intf.drawParty(true);
+ }
+}
+
+void Spells::raiseDead() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_RaiseDead);
+ if (!c)
+ return;
+
+ if (!c->_conditions[DEAD]) {
+ spellFailed();
+ } else {
+ c->_conditions[DEAD] = 0;
+ c->_conditions[UNCONSCIOUS] = 0;
+ c->_currentHp = 0;
+ sound.playFX(30);
+ c->addHitPoints(1);
+ if (--c->_endurance._permanent < 1)
+ c->_endurance._permanent = 1;
+
+ intf.drawParty(true);
+ }
+}
+
+void Spells::rechargeItem() {
+ Mode oldMode = _vm->_mode;
+
+ Character *c = SpellOnWho::show(_vm, MS_RechargeItem);
+ if (!c)
+ return;
+
+ ItemsDialog::show(_vm, c, ITEMMODE_RECHARGE);
+ _vm->_mode = oldMode;
+}
+
+void Spells::resurrection() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_RaiseDead);
+ if (!c)
+ return;
+
+ if (!c->_conditions[ERADICATED]) {
+ spellFailed();
+ sound.playFX(30);
+ } else {
+ sound.playFX(30);
+ c->addHitPoints(0);
+ c->_conditions[ERADICATED] = 0;
+
+ if (--c->_endurance._permanent < 1)
+ c->_endurance._permanent = 1;
+ if ((c->_tempAge + 5) >= 250)
+ c->_tempAge = 250;
+ else
+ c->_tempAge += 5;
+
+ intf.drawParty(true);
+ }
+}
+
+void Spells::revitalize() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_Revitalize);
+ if (!c)
+ return;
+
+ sound.playFX(30);
+ c->addHitPoints(0);
+ c->_conditions[WEAK] = 0;
+ intf.drawParty(true);
+}
+
+void Spells::shrapMetal() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = combat._oldCharacter->getCurrentLevel() * 2;
+ combat._damageType = DT_PHYSICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(16);
+ combat.multiAttack(15);
+}
+
+void Spells::sleep() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_SLEEP;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(7);
+}
+
+void Spells::sparks() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = combat._oldCharacter->getCurrentLevel() * 2;
+ combat._damageType = DT_ELECTRICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(14);
+ combat.multiAttack(5);
+}
+
+void Spells::starBurst() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 500;
+ combat._damageType = DT_FIRE;
+ combat._rangeType = RT_ALL;
+ sound.playFX(13);
+ combat.multiAttack(15);
+}
+
+void Spells::stoneToFlesh() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_StoneToFlesh);
+ if (!c)
+ return;
+
+ sound.playFX(30);
+ c->addHitPoints(0);
+ c->_conditions[STONED] = 0;
+ intf.drawParty(true);
+}
+
+void Spells::sunRay() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 200;
+ combat._damageType = DT_ENERGY;
+ combat._rangeType = RT_ALL;
+ sound.playFX(16);
+ combat.multiAttack(13);
+}
+
+void Spells::superShelter() {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ SoundManager &sound = *_vm->_sound;
+
+ if (map.mazeData()._mazeFlags & RESTRICTION_SUPER_SHELTER) {
+ spellFailed();
+ } else {
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_12;
+ sound.playFX(30);
+ intf.rest();
+ _vm->_mode = oldMode;
+ }
+}
+
+void Spells::suppressDisease() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_SuppressDisease);
+ if (!c)
+ return;
+
+ if (c->_conditions[DISEASED]) {
+ if (c->_conditions[DISEASED] >= 4)
+ c->_conditions[DISEASED] -= 3;
+ else
+ c->_conditions[DISEASED] = 1;
+
+ sound.playFX(20);
+ c->addHitPoints(0);
+ intf.drawParty(true);
+ }
+}
+
+void Spells::suppressPoison() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_FirstAid);
+ if (!c)
+ return;
+
+ if (c->_conditions[POISONED]) {
+ if (c->_conditions[POISONED] >= 4) {
+ c->_conditions[POISONED] -= 2;
+ } else {
+ c->_conditions[POISONED] = 1;
+ }
+ }
+
+ sound.playFX(20);
+ c->addHitPoints(0);
+ intf.drawParty(1);
+}
+
+void Spells::teleport() {
+ Map &map = *_vm->_map;
+ SoundManager &sound = *_vm->_sound;
+
+ if (map.mazeData()._mazeFlags & RESTRICTION_TELPORT) {
+ spellFailed();
+ } else {
+ switch (Teleport::show(_vm)) {
+ case 0:
+ spellFailed();
+ break;
+ case 1:
+ sound.playFX(51);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void Spells::timeDistortion() {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ if (map.mazeData()._mazeFlags & RESTRICTION_TIME_DISTORTION) {
+ spellFailed();
+ } else {
+ party.moveToRunLocation();
+ sound.playFX(51);
+ intf.draw3d(true);
+ }
+}
+
+void Spells::townPortal() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ if (map.mazeData()._mazeFlags & RESTRICTION_TOWN_PORTAL) {
+ spellFailed();
+ return;
+ }
+
+ int townNumber = TownPortal::show(_vm);
+ if (!townNumber)
+ return;
+
+ sound.playFX(51);
+ map._loadDarkSide = map._sideTownPortal;
+ _vm->_files->_isDarkCc = map._sideTownPortal > 0;
+ map.load(TOWN_MAP_NUMBERS[map._sideTownPortal][townNumber - 1]);
+
+ if (!_vm->_files->_isDarkCc) {
+ party.moveToRunLocation();
+ } else {
+ switch (townNumber) {
+ case 1:
+ party._mazePosition = Common::Point(14, 11);
+ party._mazeDirection = DIR_SOUTH;
+ break;
+ case 2:
+ party._mazePosition = Common::Point(5, 12);
+ party._mazeDirection = DIR_WEST;
+ break;
+ case 3:
+ party._mazePosition = Common::Point(2, 15);
+ party._mazeDirection = DIR_EAST;
+ break;
+ case 4:
+ party._mazePosition = Common::Point(8, 11);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ case 5:
+ party._mazePosition = Common::Point(2, 6);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void Spells::toxicCloud() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 10;
+ combat._damageType = DT_POISON;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(17);
+ combat.multiAttack(10);
+}
+
+void Spells::turnUndead() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_UNDEAD;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(13);
+}
+
+void Spells::walkOnWater() {
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ party._walkOnWaterActive = true;
+ sound.playFX(20);
+}
+
+void Spells::wizardEye() {
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ party._wizardEyeActive = true;
+ party._automapOn = false;
+ sound.playFX(20);
+}
+
+void Spells::frostbite2() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 35;
+ combat._damageType = DT_COLD;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(15);
+ combat.multiAttack(9);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/spells.h b/engines/xeen/spells.h
new file mode 100644
index 0000000000..a05e81c56d
--- /dev/null
+++ b/engines/xeen/spells.h
@@ -0,0 +1,191 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_SPELLS_H
+#define XEEN_SPELLS_H
+
+#include "common/scummsys.h"
+#include "common/str-array.h"
+
+namespace Xeen {
+
+class XeenEngine;
+class Character;
+
+#define MAX_SPELLS_PER_CLASS 40
+
+enum MagicSpell {
+ MS_AcidSpray = 0, MS_Awaken = 1, MS_BeastMaster = 2, MS_Bless = 3,
+ MS_Clairvoyance = 4, MS_ColdRay = 5, MS_CreateFood = 6,
+ MS_CureDisease = 7, MS_CureParalysis = 8, MS_CurePoison = 9,
+ MS_CureWounds = 10, MS_DancingSword = 11, MS_DayOfProtection = 12,
+ MS_DayOfSorcery = 13, MS_DeadlySwarm = 14, MS_DetectMonster = 15,
+ MS_DivineIntervention = 16, MS_DragonSleep = 17, MS_ElementalStorm = 18,
+ MS_EnchantItem = 19, MS_EnergyBlast = 20, MS_Etheralize = 21,
+ MS_FantasticFreeze = 22, MS_FieryFlail = 23, MS_FingerOfDeath = 24,
+ MS_Fireball = 25, MS_FirstAid = 26, MS_FlyingFist = 27,
+ MS_FrostBite = 28, MS_GolemStopper = 29, MS_Heroism = 30,
+ MS_HolyBonus = 31, MS_HolyWord = 32, MS_Hynotize = 33,
+ MS_IdentifyMonster = 34, MS_Implosion = 35, MS_Incinerate = 36,
+ MS_Inferno = 37, MS_InsectSpray = 38, MS_ItemToGold = 39,
+ MS_Jump = 40, MS_Levitate = 41, MS_Light = 42, MS_LightningBolt = 43,
+ MS_LloydsBeacon = 44, MS_MagicArrow = 45, MS_MassDistortion = 46,
+ MS_MegaVolts = 47, MS_MoonRay = 48, MS_NaturesCure = 49, MS_Pain = 50,
+ MS_PoisonVolley = 51, MS_PowerCure = 52, MS_PowerShield = 53,
+ MS_PrismaticLight = 54, MS_ProtFromElements = 55, MS_RaiseDead = 56,
+ MS_RechargeItem = 57, MS_Resurrection = 58, MS_Revitalize = 59,
+ MS_Shrapmetal = 60, MS_Sleep = 61, MS_Sparks = 62, MS_StarBurst = 63,
+ MS_StoneToFlesh = 64, MS_SunRay = 65, MS_SuperShelter = 66,
+ MS_SuppressDisease = 67, MS_SuppressPoison = 68, MS_Teleport = 69,
+ MS_TimeDistortion = 70, MS_TownPortal = 71, MS_ToxicCloud = 72,
+ MS_TurnUndead = 73, MS_WalkOnWater = 74, MS_WizardEye = 75,
+ NO_SPELL = 76
+};
+
+class Spells {
+private:
+ XeenEngine *_vm;
+
+ void load();
+
+ void executeSpell(MagicSpell spellId);
+
+ /**
+ * Spell being cast failed
+ */
+ void spellFailed();
+
+ // Spell list
+ void acidSpray();
+ void awaken();
+ void beastMaster();
+ void bless();
+ void clairvoyance();
+ void coldRay();
+ void createFood();
+ void cureDisease();
+ void cureParalysis();
+ void curePoison();
+ void cureWounds();
+ void dancingSword();
+ void dayOfProtection();
+ void dayOfSorcery();
+ void deadlySwarm();
+ void detectMonster();
+ void divineIntervention();
+ void dragonSleep();
+ void elementalStorm();
+ void enchantItem();
+ void energyBlast();
+ void etherialize();
+ void fantasticFreeze();
+ void fieryFlail();
+ void fingerOfDeath();
+ void fireball();
+ void firstAid();
+ void flyingFist();
+ void frostbite();
+ void golemStopper();
+ void heroism();
+ void holyBonus();
+ void holyWord();
+ void hypnotize();
+ void identifyMonster();
+ void implosion();
+ void incinerate();
+ void inferno();
+ void insectSpray();
+ void itemToGold();
+ void jump();
+ void levitate();
+ void light();
+ void lightningBolt();
+ void lloydsBeacon();
+ void magicArrow();
+ void massDistortion();
+ void megaVolts();
+ void moonRay();
+ void naturesCure();
+ void pain();
+ void poisonVolley();
+ void powerCure();
+ void powerShield();
+ void prismaticLight();
+ void protectionFromElements();
+ void raiseDead();
+ void rechargeItem();
+ void resurrection();
+ void revitalize();
+ void shrapMetal();
+ void sleep();
+ void sparks();
+ void starBurst();
+ void stoneToFlesh();
+ void sunRay();
+ void superShelter();
+ void suppressDisease();
+ void suppressPoison();
+ void teleport();
+ void timeDistortion();
+ void townPortal();
+ void toxicCloud();
+ void turnUndead();
+ void walkOnWater();
+ void wizardEye();
+
+ void frostbite2();
+public:
+ Common::StringArray _spellNames;
+ int _lastCaster;
+public:
+ Spells(XeenEngine *vm);
+
+ int calcSpellCost(int spellId, int expenseFactor) const;
+
+ int calcSpellPoints(int spellId, int expenseFactor) const;
+
+ /**
+ * Cast a spell associated with an item
+ */
+ void castItemSpell(int itemSpellId);
+
+ /**
+ * Cast a given spell
+ */
+ int castSpell(Character *c, MagicSpell spellId);
+
+ /**
+ * Subtract the requirements for a given spell if available, returning
+ * true if there was sufficient
+ */
+ int subSpellCost(Character &c, int spellId);
+
+ /**
+ * Add the SP and gem requirements for a given spell to the given
+ * character and party
+ */
+ void addSpellCost(Character &c, int spellId);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_SPELLS_H */
diff --git a/engines/xeen/sprites.cpp b/engines/xeen/sprites.cpp
new file mode 100644
index 0000000000..4804f0aa66
--- /dev/null
+++ b/engines/xeen/sprites.cpp
@@ -0,0 +1,334 @@
+/* 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.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/archive.h"
+#include "common/memstream.h"
+#include "common/textconsole.h"
+#include "xeen/xeen.h"
+#include "xeen/screen.h"
+#include "xeen/sprites.h"
+
+namespace Xeen {
+
+#define SCENE_CLIP_LEFT 8
+#define SCENE_CLIP_RIGHT 223
+
+SpriteResource::SpriteResource() {
+ _filesize = 0;
+ _data = nullptr;
+ _scaledWidth = _scaledHeight = 0;
+ Common::fill(&_lineDist[0], &_lineDist[SCREEN_WIDTH], false);
+}
+
+SpriteResource::SpriteResource(const Common::String &filename) {
+ _data = nullptr;
+ _scaledWidth = _scaledHeight = 0;
+ Common::fill(&_lineDist[0], &_lineDist[SCREEN_WIDTH], false);
+ load(filename);
+}
+
+SpriteResource::~SpriteResource() {
+ clear();
+}
+
+SpriteResource &SpriteResource::operator=(const SpriteResource &src) {
+ delete[] _data;
+ _index.clear();
+
+ _filesize = src._filesize;
+ _data = new byte[_filesize];
+ Common::copy(src._data, src._data + _filesize, _data);
+
+ _index.resize(src._index.size());
+ for (uint i = 0; i < src._index.size(); ++i)
+ _index[i] = src._index[i];
+
+ return *this;
+}
+
+void SpriteResource::load(const Common::String &filename) {
+ File f(filename);
+ load(f);
+}
+
+void SpriteResource::load(const Common::String &filename, Common::Archive &archive) {
+ File f(filename, archive);
+ load(f);
+}
+
+void SpriteResource::load(Common::SeekableReadStream &f) {
+ // Read in a copy of the file
+ _filesize = f.size();
+ delete[] _data;
+ _data = new byte[_filesize];
+ f.read(_data, _filesize);
+
+ // Read in the index
+ f.seek(0);
+ int count = f.readUint16LE();
+ _index.resize(count);
+
+ for (int i = 0; i < count; ++i) {
+ _index[i]._offset1 = f.readUint16LE();
+ _index[i]._offset2 = f.readUint16LE();
+ }
+}
+
+void SpriteResource::clear() {
+ delete[] _data;
+ _data = nullptr;
+ _filesize = 0;
+}
+
+void SpriteResource::drawOffset(XSurface &dest, uint16 offset, const Common::Point &pt,
+ const Common::Rect &bounds, int flags, int scale) {
+ static const uint SCALE_TABLE[] = {
+ 0xFFFF, 0xFFEF, 0xEFEF, 0xEFEE, 0xEEEE, 0xEEAE, 0xAEAE, 0xAEAA,
+ 0xAAAA, 0xAA8A, 0x8A8A, 0x8A88, 0x8888, 0x8880, 0x8080, 0x8000
+ };
+ static const int PATTERN_STEPS[] = { 0, 1, 1, 1, 2, 2, 3, 3, 0, -1, -1, -1, -2, -2, -3, -3 };
+
+ uint16 scaleMask = SCALE_TABLE[scale & 0x7fff];
+ uint16 scaleMaskX = scaleMask, scaleMaskY = scaleMask;
+ bool flipped = (flags & SPRFLAG_HORIZ_FLIPPED) != 0;
+ int xInc = flipped ? -1 : 1;
+ bool enlarge = (scale & 0x8000) != 0;
+
+ // Get cell header
+ Common::MemoryReadStream f(_data, _filesize);
+ f.seek(offset);
+ int xOffset = f.readUint16LE();
+ int width = f.readUint16LE();
+ int yOffset = f.readUint16LE();
+ int height = f.readUint16LE();
+
+ // Figure out drawing x, y
+ Common::Point destPos;
+ destPos.x = pt.x + getScaledVal(xOffset, scaleMaskX);
+ destPos.x += (width - getScaledVal(width, scaleMaskX)) / 2;
+
+ destPos.y = pt.y + getScaledVal(yOffset, scaleMaskY);
+
+ // If the flags allow the dest surface to be resized, ensure dest surface is big enough
+ if (flags & SPRFLAG_RESIZE) {
+ if (dest.w < (xOffset + width) || dest.h < (yOffset + height))
+ dest.create(xOffset + width, yOffset + height);
+ }
+
+ uint16 scaleMaskXCopy = scaleMaskX;
+ Common::Rect drawBounds;
+ drawBounds.left = SCREEN_WIDTH;
+ drawBounds.top = SCREEN_HEIGHT;
+ drawBounds.right = drawBounds.bottom = 0;
+
+ // Main loop
+ for (int yCtr = height; yCtr > 0; --yCtr) {
+ // The number of bytes in this scan line
+ int lineLength = f.readByte();
+
+ if (lineLength == 0) {
+ // Skip the specified number of scan lines
+ int numLines = f.readByte();
+ destPos.y += getScaledVal(numLines, scaleMaskY);
+ yCtr -= numLines;
+ continue;
+ }
+
+ // Roll the scale mask
+ uint bit = (scaleMaskY >> 15) & 1;
+ scaleMaskY = ((scaleMaskY & 0x7fff) << 1) + bit;
+
+ if (!bit) {
+ // Not a line to be drawn due to scaling down
+ f.skip(lineLength);
+ } else if (destPos.y < bounds.top || destPos.y >= bounds.bottom) {
+ // Skip over the bytes of the line
+ f.skip(lineLength);
+ destPos.y++;
+ } else {
+ scaleMaskX = scaleMaskXCopy;
+ xOffset = f.readByte();
+
+ // Initialize the array to hold the temporary data for the line. We do this to make it simpler
+ // to handle both deciding which pixels to draw in a scaled image, as well as when images
+ // have been horizontally flipped
+ int tempLine[SCREEN_WIDTH];
+ Common::fill(&tempLine[0], &tempLine[SCREEN_WIDTH], -1);
+ int *lineP = flipped ? &tempLine[width - 1 - xOffset] : &tempLine[xOffset];
+
+ // Build up the line
+ int byteCount, opr1, opr2;
+ int32 pos;
+ for (byteCount = 1; byteCount < lineLength; ) {
+ // The next byte is an opcode that determines what operators are to follow and how to interpret them.
+ int opcode = f.readByte(); ++byteCount;
+
+ // Decode the opcode
+ int len = opcode & 0x1F;
+ int cmd = (opcode & 0xE0) >> 5;
+
+ switch (cmd) {
+ case 0: // The following len + 1 bytes are stored as indexes into the color table.
+ case 1: // The following len + 33 bytes are stored as indexes into the color table.
+ for (int i = 0; i < opcode + 1; ++i, ++byteCount) {
+ byte b = f.readByte();
+ *lineP = b;
+ lineP += xInc;
+ }
+ break;
+
+ case 2: // The following byte is an index into the color table, draw it len + 3 times.
+ opr1 = f.readByte(); ++byteCount;
+ for (int i = 0; i < len + 3; ++i) {
+ *lineP = opr1;
+ lineP += xInc;
+ }
+ break;
+
+ case 3: // Stream copy command.
+ opr1 = f.readUint16LE(); byteCount += 2;
+ pos = f.pos();
+ f.seek(-opr1, SEEK_CUR);
+
+ for (int i = 0; i < len + 4; ++i) {
+ *lineP = f.readByte();
+ lineP += xInc;
+ }
+
+ f.seek(pos, SEEK_SET);
+ break;
+
+ case 4: // The following two bytes are indexes into the color table, draw the pair len + 2 times.
+ opr1 = f.readByte(); ++byteCount;
+ opr2 = f.readByte(); ++byteCount;
+ for (int i = 0; i < len + 2; ++i) {
+ *lineP = opr1;
+ lineP += xInc;
+ *lineP = opr2;
+ lineP += xInc;
+ }
+ break;
+
+ case 5: // Skip len + 1 pixels
+ lineP += (len + 1) * xInc;
+ break;
+
+ case 6: // Pattern command.
+ case 7:
+ // The pattern command has a different opcode format
+ len = opcode & 0x07;
+ cmd = (opcode >> 2) & 0x0E;
+
+ opr1 = f.readByte(); ++byteCount;
+ for (int i = 0; i < len + 3; ++i) {
+ *lineP = opr1;
+ lineP += xInc;
+ opr1 += PATTERN_STEPS[cmd + (i % 2)];
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ assert(byteCount == lineLength);
+
+ drawBounds.top = MIN(drawBounds.top, destPos.y);
+ drawBounds.bottom = MAX(drawBounds.bottom, destPos.y);
+
+ // Handle drawing out the line
+ byte *destP = (byte *)dest.getBasePtr(destPos.x, destPos.y);
+ int16 xp = destPos.x;
+ lineP = &tempLine[0];
+
+ for (int xCtr = 0; xCtr < width; ++xCtr, ++lineP) {
+ bit = (scaleMaskX >> 15) & 1;
+ scaleMaskX = ((scaleMaskX & 0x7fff) << 1) + bit;
+
+ if (bit) {
+ // Check whether there's a pixel to write, and we're within the allowable bounds. Note that for
+ // the SPRFLAG_SCENE_CLIPPED or when scale == 0x8000, we also have an extra horizontal bounds check
+ if (*lineP != -1 && xp >= bounds.left && xp < bounds.right &&
+ ((!(flags & SPRFLAG_SCENE_CLIPPED) && !enlarge) || (xp >= SCENE_CLIP_LEFT && xp < SCENE_CLIP_RIGHT))) {
+ drawBounds.left = MIN(drawBounds.left, xp);
+ drawBounds.right = MAX(drawBounds.right, xp);
+ *destP = (byte)*lineP;
+ if (enlarge)
+ *(destP + SCREEN_WIDTH) = (byte)*lineP;
+ }
+
+ ++destP;
+ ++xp;
+ }
+ }
+
+ ++destPos.y;
+ if (enlarge)
+ ++destPos.y;
+ }
+ }
+
+ if (drawBounds.isValidRect()) {
+ drawBounds.clip(Common::Rect(0, 0, dest.w, dest.h));
+ if (!drawBounds.isEmpty())
+ dest.addDirtyRect(drawBounds);
+ }
+}
+
+void SpriteResource::draw(XSurface &dest, int frame, const Common::Point &destPos,
+ int flags, int scale) {
+ draw(dest, frame, destPos, Common::Rect(0, 0, dest.w, dest.h), flags, scale);
+}
+
+void SpriteResource::draw(Window &dest, int frame, const Common::Point &destPos,
+ int flags, int scale) {
+ draw(dest, frame, destPos, dest.getBounds(), flags, scale);
+}
+
+void SpriteResource::draw(XSurface &dest, int frame, const Common::Point &destPos,
+ const Common::Rect &bounds, int flags, int scale) {
+
+ drawOffset(dest, _index[frame]._offset1, destPos, bounds, flags, scale);
+ if (_index[frame]._offset2)
+ drawOffset(dest, _index[frame]._offset2, destPos, bounds, flags, scale);
+}
+
+void SpriteResource::draw(XSurface &dest, int frame) {
+ draw(dest, frame, Common::Point());
+}
+
+uint SpriteResource::getScaledVal(int xy, uint16 &scaleMask) {
+ if (!xy)
+ return 0;
+
+ uint result = 0;
+ for (int idx = 0; idx < xy; ++idx) {
+ uint bit = (scaleMask >> 15) & 1;
+ scaleMask = ((scaleMask & 0x7fff) << 1) + bit;
+ result += bit;
+ }
+
+ return result;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/sprites.h b/engines/xeen/sprites.h
new file mode 100644
index 0000000000..9a241c2100
--- /dev/null
+++ b/engines/xeen/sprites.h
@@ -0,0 +1,116 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_SPRITES_H
+#define XEEN_SPRITES_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/file.h"
+#include "graphics/surface.h"
+#include "xeen/xsurface.h"
+
+namespace Xeen {
+
+class XeenEngine;
+class Window;
+
+enum SpriteFlags { SPRFLAG_SCENE_CLIPPED = 0x2000, SPRFLAG_4000 = 0x4000,
+ SPRFLAG_HORIZ_FLIPPED = 0x8000, SPRFLAG_RESIZE = 0x10000 };
+
+class SpriteResource {
+private:
+ struct IndexEntry {
+ uint16 _offset1, _offset2;
+ };
+ Common::Array<IndexEntry> _index;
+ int32 _filesize;
+ byte *_data;
+ bool _lineDist[320];
+ int _scaledWidth, _scaledHeight;
+
+ /**
+ * Load a sprite resource from a stream
+ */
+ void load(Common::SeekableReadStream &f);
+
+ /**
+ * Draw the sprite onto the given surface
+ */
+ void draw(XSurface &dest, int frame, const Common::Point &destPos,
+ const Common::Rect &bounds, int flags = 0, int scale = 0);
+
+ /**
+ * Draw a sprite frame based on a passed offset into the data stream
+ */
+ void drawOffset(XSurface &dest, uint16 offset, const Common::Point &pt,
+ const Common::Rect &bounds, int flags, int scale);
+
+ /**
+ * Scale a co-ordinate value based on the passed scaling mask
+ */
+ static uint getScaledVal(int xy, uint16 &scaleMask);
+public:
+ SpriteResource();
+ SpriteResource(const Common::String &filename);
+
+ virtual ~SpriteResource();
+
+ /**
+ * Copy operator for duplicating a sprite resource
+ */
+ SpriteResource &operator=(const SpriteResource &src);
+
+ /**
+ * Load a sprite resource from a given file
+ */
+ void load(const Common::String &filename);
+
+ /**
+ * Load a sprite resource from a given file and archive
+ */
+ void load(const Common::String &filename, Common::Archive &archive);
+
+ /**
+ * Clears the sprite resource
+ */
+ void clear();
+
+ void draw(XSurface &dest, int frame, const Common::Point &destPos,
+ int flags = 0, int scale = 0);
+
+ void draw(Window &dest, int frame, const Common::Point &destPos,
+ int flags = 0, int scale = 0);
+
+ /**
+ * Draw the sprite onto the given surface
+ */
+ void draw(XSurface &dest, int frame);
+
+ int size() const { return _index.size(); }
+
+ bool empty() const { return _index.size() == 0; }
+};
+
+} // End of namespace Xeen
+
+#endif /* MADS_SPRITES_H */
diff --git a/engines/xeen/town.cpp b/engines/xeen/town.cpp
new file mode 100644
index 0000000000..cc83563de2
--- /dev/null
+++ b/engines/xeen/town.cpp
@@ -0,0 +1,1310 @@
+/* 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.
+ *
+ */
+
+#include "xeen/town.h"
+#include "xeen/dialogs_input.h"
+#include "xeen/dialogs_items.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/dialogs_spells.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+Town::Town(XeenEngine *vm) : _vm(vm) {
+ Common::fill(&_arr1[0], &_arr1[6], 0);
+ _townMaxId = 0;
+ _townActionId = 0;
+ _drawFrameIndex = 0;
+ _currentCharLevel = 0;
+ _v1 = 0;
+ _v2 = 0;
+ _donation = 0;
+ _healCost = 0;
+ _v5 = _v6 = 0;
+ _v10 = _v11 = 0;
+ _v12 = _v13 = 0;
+ _v14 = 0;
+ _v20 = 0;
+ _v21 = 0;
+ _v22 = 0;
+ _v23 = 0;
+ _v24 = 0;
+ _dayOfWeek = 0;
+ _uncurseCost = 0;
+ _flag1 = false;
+ _experienceToNextLevel = 0;
+ _drawCtr1 = _drawCtr2 = 0;
+}
+
+void Town::loadStrings(const Common::String &name) {
+ File f(name);
+ _textStrings.clear();
+ while (f.pos() < f.size())
+ _textStrings.push_back(f.readString());
+ f.close();
+}
+
+int Town::townAction(int actionId) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ if (actionId == 12) {
+ pyramidEvent();
+ return 0;
+ }
+
+ _townMaxId = TOWN_MAXES[_vm->_files->_isDarkCc][actionId];
+ _townActionId = actionId;
+ _drawFrameIndex = 0;
+ _v1 = 0;
+ _townPos = Common::Point(8, 8);
+ intf._overallFrame = 0;
+
+ // This area sets up the GUI buttos and startup sample to play for the
+ // given town action
+ Common::String vocName = "hello1.voc";
+ clearButtons();
+ _icons1.clear();
+ _icons2.clear();
+
+ switch (actionId) {
+ case 0:
+ // Bank
+ _icons1.load("bank.icn");
+ _icons2.load("bank2.icn");
+ addButton(Common::Rect(234, 108, 259, 128), Common::KEYCODE_d, &_icons1);
+ addButton(Common::Rect(261, 108, 285, 128), Common::KEYCODE_w, &_icons1);
+ addButton(Common::Rect(288, 108, 312, 128), Common::KEYCODE_ESCAPE, &_icons1);
+ intf._overallFrame = 1;
+
+ sound.playSample(nullptr, 0);
+ vocName = isDarkCc ? "bank1.voc" : "banker.voc";
+ break;
+
+ case 1:
+ // Blacksmith
+ _icons1.load("esc.icn");
+ addButton(Common::Rect(261, 100, 285, 120), Common::KEYCODE_ESCAPE, &_icons1);
+ addButton(Common::Rect(234, 54, 308, 62), 0);
+ addButton(Common::Rect(234, 64, 308, 72), Common::KEYCODE_b);
+ addButton(Common::Rect(234, 74, 308, 82), 0);
+ addButton(Common::Rect(234, 84, 308, 92), 0);
+
+ sound.playSample(nullptr, 0);
+ vocName = isDarkCc ? "see2.voc" : "whaddayo.voc";
+ break;
+
+ case 2:
+ // Guild
+ loadStrings("spldesc.bin");
+ _icons1.load("esc.icn");
+ addButton(Common::Rect(261, 100, 285, 120), Common::KEYCODE_ESCAPE, &_icons1);
+ addButton(Common::Rect(234, 54, 308, 62), 0);
+ addButton(Common::Rect(234, 64, 308, 72), Common::KEYCODE_b);
+ addButton(Common::Rect(234, 74, 308, 82), Common::KEYCODE_s);
+ addButton(Common::Rect(234, 84, 308, 92), 0);
+ _vm->_mode = MODE_17;
+
+ sound.playSample(nullptr, 0);
+ vocName = isDarkCc ? "parrot1.voc" : "guild10.voc";
+ break;
+
+ case 3:
+ // Tavern
+ loadStrings("tavern.bin");
+ _icons1.load("tavern.icn");
+ addButton(Common::Rect(281, 108, 305, 128), Common::KEYCODE_ESCAPE, &_icons1);
+ addButton(Common::Rect(242, 108, 266, 128), Common::KEYCODE_s, &_icons1);
+ addButton(Common::Rect(234, 54, 308, 62), Common::KEYCODE_d);
+ addButton(Common::Rect(234, 64, 308, 72), Common::KEYCODE_f);
+ addButton(Common::Rect(234, 74, 308, 82), Common::KEYCODE_t);
+ addButton(Common::Rect(234, 84, 308, 92), Common::KEYCODE_r);
+ _vm->_mode = MODE_17;
+
+ sound.playSample(nullptr, 0);
+ vocName = isDarkCc ? "hello1.voc" : "hello.voc";
+ break;
+
+ case 4:
+ // Temple
+ _icons1.load("esc.icn");
+ addButton(Common::Rect(261, 100, 285, 120), Common::KEYCODE_ESCAPE, &_icons1);
+ addButton(Common::Rect(234, 54, 308, 62), Common::KEYCODE_h);
+ addButton(Common::Rect(234, 64, 308, 72), Common::KEYCODE_d);
+ addButton(Common::Rect(234, 74, 308, 82), Common::KEYCODE_u);
+ addButton(Common::Rect(234, 84, 308, 92), 0);
+
+ sound.playSample(nullptr, 0);
+ vocName = isDarkCc ? "help2.voc" : "maywe2.voc";
+ break;
+
+ case 5:
+ // Training
+ Common::fill(&_arr1[0], &_arr1[6], 0);
+ _v2 = 0;
+
+ _icons1.load("train.icn");
+ addButton(Common::Rect(281, 108, 305, 128), Common::KEYCODE_ESCAPE, &_icons1);
+ addButton(Common::Rect(242, 108, 266, 128), Common::KEYCODE_t);
+
+ sound.playSample(nullptr, 0);
+ vocName = isDarkCc ? "training.voc" : "youtrn1.voc";
+ break;
+
+ case 6:
+ // Arena event
+ arenaEvent();
+ return false;
+
+ case 8:
+ // Reaper event
+ reaperEvent();
+ return false;
+
+ case 9:
+ // Golem event
+ golemEvent();
+ return false;
+
+ case 10:
+ case 13:
+ dwarfEvent();
+ return false;
+
+ case 11:
+ sphinxEvent();
+ return false;
+
+ default:
+ break;
+ }
+
+ sound.loadMusic(TOWN_ACTION_MUSIC[actionId], 223);
+
+ _townSprites.resize(TOWN_ACTION_FILES[isDarkCc][actionId]);
+ for (uint idx = 0; idx < _townSprites.size(); ++idx) {
+ Common::String shapesName = Common::String::format("%s%d.twn",
+ TOWN_ACTION_SHAPES[actionId], idx + 1);
+ _townSprites[idx].load(shapesName);
+ }
+
+ Character *charP = &party._activeParty[0];
+ Common::String title = createTownText(*charP);
+ intf._face1UIFrame = intf._face2UIFrame = 0;
+ intf._dangerSenseUIFrame = 0;
+ intf._spotDoorsUIFrame = 0;
+ intf._levitateUIFrame = 0;
+
+ _townSprites[_drawFrameIndex / 8].draw(screen, _drawFrameIndex % 8, _townPos);
+ if (actionId == 0 && isDarkCc) {
+ _townSprites[4].draw(screen, _vm->getRandomNumber(13, 18),
+ Common::Point(8, 30));
+ }
+
+ intf.assembleBorder();
+
+ // Open up the window and write the string
+ screen._windows[10].open();
+ screen._windows[10].writeString(title);
+ drawButtons(&screen);
+
+ screen._windows[0].update();
+ intf.highlightChar(0);
+ drawTownAnim(1);
+
+ if (actionId == 0)
+ intf._overallFrame = 2;
+
+ File voc(vocName);
+ sound.playSample(&voc, 1);
+
+ do {
+ townWait();
+ charP = doTownOptions(charP);
+ screen._windows[10].writeString(title);
+ drawButtons(&screen);
+ } while (!_vm->shouldQuit() && _buttonValue != Common::KEYCODE_ESCAPE);
+
+ switch (actionId) {
+ case 1:
+ // Leave blacksmith
+ if (isDarkCc) {
+ sound.playSample(nullptr, 0);
+ File f("come1.voc");
+ sound.playSample(&f, 1);
+ }
+ break;
+
+ case 3: {
+ // Leave Tavern
+ sound.playSample(nullptr, 0);
+ File f(isDarkCc ? "gdluck1.voc" : "goodbye.voc");
+ sound.playSample(&f, 1);
+
+ map.mazeData()._mazeNumber = party._mazeId;
+ break;
+ }
+ default:
+ break;
+ }
+
+ int result;
+ if (party._mazeId != 0) {
+ map.load(party._mazeId);
+ _v1 += 1440;
+ party.addTime(_v1);
+ result = 0;
+ } else {
+ _vm->_saves->saveChars();
+ result = 2;
+ }
+
+ for (uint idx = 0; idx < _townSprites.size(); ++idx)
+ _townSprites[idx].clear();
+ intf.mainIconsPrint();
+ _buttonValue = 0;
+
+ return result;
+}
+
+int Town::townWait() {
+ EventsManager &events = *_vm->_events;
+
+ _buttonValue = 0;
+ while (!_vm->shouldQuit() && !_buttonValue) {
+ events.updateGameCounter();
+ while (!_vm->shouldQuit() && !_buttonValue && events.timeElapsed() < 3) {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ }
+ if (!_buttonValue)
+ drawTownAnim(!_vm->_screen->_windows[11]._enabled);
+ }
+
+ return _buttonValue;
+}
+
+void Town::pyramidEvent() {
+ error("TODO: pyramidEvent");
+}
+
+void Town::arenaEvent() {
+ error("TODO: arenaEvent");
+}
+
+void Town::reaperEvent() {
+ error("TODO: repearEvent");
+}
+
+void Town::golemEvent() {
+ error("TODO: golemEvent");
+}
+
+void Town::sphinxEvent() {
+ error("TODO: sphinxEvent");
+}
+
+void Town::dwarfEvent() {
+ error("TODO: dwarfEvent");
+}
+
+Common::String Town::createTownText(Character &ch) {
+ Party &party = *_vm->_party;
+ Common::String msg;
+
+ switch (_townActionId) {
+ case 0:
+ // Bank
+ return Common::String::format(BANK_TEXT,
+ XeenEngine::printMil(party._bankGold).c_str(),
+ XeenEngine::printMil(party._bankGems).c_str(),
+ XeenEngine::printMil(party._gold).c_str(),
+ XeenEngine::printMil(party._gems).c_str());
+ case 1:
+ // Blacksmith
+ return Common::String::format(BLACKSMITH_TEXT,
+ XeenEngine::printMil(party._gold).c_str());
+
+ case 2:
+ // Guild
+ return !ch.guildMember() ? GUILD_NOT_MEMBER_TEXT :
+ Common::String::format(GUILD_TEXT, ch._name.c_str());
+
+ case 3:
+ // Tavern
+ return Common::String::format(TAVERN_TEXT, ch._name.c_str(),
+ FOOD_AND_DRINK, XeenEngine::printMil(party._gold).c_str());
+
+ case 4:
+ // Temple
+ _donation = 0;
+ _uncurseCost = 0;
+ _v5 = 0;
+ _v6 = 0;
+ _healCost = 0;
+
+ if (party._mazeId == (_vm->_files->_isDarkCc ? 29 : 28)) {
+ _v10 = _v11 = _v12 = _v13 = 0;
+ _v14 = 10;
+ } else if (party._mazeId == (_vm->_files->_isDarkCc ? 31 : 30)) {
+ _v13 = 10;
+ _v12 = 50;
+ _v11 = 500;
+ _v10 = 100;
+ _v14 = 25;
+ } else if (party._mazeId == (_vm->_files->_isDarkCc ? 37 : 73)) {
+ _v13 = 20;
+ _v12 = 100;
+ _v11 = 1000;
+ _v10 = 200;
+ _v14 = 50;
+ } else if (_vm->_files->_isDarkCc || party._mazeId == 49) {
+ _v13 = 100;
+ _v12 = 500;
+ _v11 = 5000;
+ _v10 = 300;
+ _v14 = 100;
+ }
+
+ _currentCharLevel = ch.getCurrentLevel();
+ if (ch._currentHp < ch.getMaxHP()) {
+ _healCost = _currentCharLevel * 10 + _v13;
+ }
+
+ for (int attrib = HEART_BROKEN; attrib <= UNCONSCIOUS; ++attrib) {
+ if (ch._conditions[attrib])
+ _healCost += _currentCharLevel * 10;
+ }
+
+ _v6 = 0;
+ if (ch._conditions[DEAD]) {
+ _v6 += (_currentCharLevel * 100) + (ch._conditions[DEAD] * 50) + _v12;
+ }
+ if (ch._conditions[STONED]) {
+ _v6 += (_currentCharLevel * 100) + (ch._conditions[STONED] * 50) + _v12;
+ }
+ if (ch._conditions[ERADICATED]) {
+ _v5 = (_currentCharLevel * 1000) + (ch._conditions[ERADICATED] * 500) + _v11;
+ }
+
+ for (int idx = 0; idx < 9; ++idx) {
+ _uncurseCost |= ch._weapons[idx]._bonusFlags & 0x40;
+ _uncurseCost |= ch._armor[idx]._bonusFlags & 0x40;
+ _uncurseCost |= ch._accessories[idx]._bonusFlags & 0x40;
+ _uncurseCost |= ch._misc[idx]._bonusFlags & 0x40;
+ }
+
+ if (_uncurseCost || ch._conditions[CURSED])
+ _v5 = (_currentCharLevel * 20) + _v10;
+
+ _donation = _flag1 ? 0 : _v14;
+ _healCost += _v6 + _v5;
+
+ return Common::String::format(TEMPLE_TEXT, ch._name.c_str(),
+ _healCost, _donation, XeenEngine::printK(_uncurseCost).c_str(),
+ XeenEngine::printMil(party._gold).c_str());
+
+ case 5:
+ // Training
+ if (_vm->_files->_isDarkCc) {
+ switch (party._mazeId) {
+ case 29:
+ _v20 = 30;
+ break;
+ case 31:
+ _v20 = 50;
+ break;
+ case 37:
+ _v20 = 200;
+ break;
+ default:
+ _v20 = 100;
+ break;
+ }
+ } else {
+ switch (party._mazeId) {
+ case 28:
+ _v20 = 10;
+ break;
+ case 30:
+ _v20 = 15;
+ break;
+ default:
+ _v20 = 20;
+ break;
+ }
+ }
+
+ _experienceToNextLevel = ch.experienceToNextLevel();
+
+ if (_experienceToNextLevel >= 0x10000 && ch._level._permanent < _v20) {
+ int nextLevel = ch._level._permanent + 1;
+ return Common::String::format(EXPERIENCE_FOR_LEVEL,
+ ch._name.c_str(), _experienceToNextLevel, nextLevel);
+ } else if (ch._level._permanent >= 20) {
+ _experienceToNextLevel = 1;
+ msg = Common::String::format(LEARNED_ALL, ch._name.c_str());
+ } else {
+ msg = Common::String::format(ELIGIBLE_FOR_LEVEL,
+ ch._name.c_str(), ch._level._permanent + 1);
+ }
+
+ return Common::String::format(TRAINING_TEXT,
+ XeenEngine::printMil(party._gold).c_str());
+
+ default:
+ return "";
+ }
+}
+
+Character *Town::doTownOptions(Character *c) {
+ switch (_townActionId) {
+ case 0:
+ // Bank
+ c = doBankOptions(c);
+ break;
+ case 1:
+ // Blacksmith
+ c = doBlacksmithOptions(c);
+ break;
+ case 2:
+ // Guild
+ c = doGuildOptions(c);
+ break;
+ case 3:
+ // Tavern
+ c = doTavernOptions(c);
+ break;
+ case 4:
+ // Temple
+ c = doTempleOptions(c);
+ case 5:
+ // Training
+ c = doTrainingOptions(c);
+ break;
+ default:
+ break;
+ }
+
+ return c;
+}
+
+Character *Town::doBankOptions(Character *c) {
+ if (_buttonValue == Common::KEYCODE_d)
+ _buttonValue = 0;
+ else if (_buttonValue == Common::KEYCODE_w)
+ _buttonValue = 1;
+ else
+ return c;
+
+ depositWithdrawl(_buttonValue);
+ return c;
+}
+
+Character *Town::doBlacksmithOptions(Character *c) {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+
+ if (_buttonValue >= Common::KEYCODE_F1 && _buttonValue <= Common::KEYCODE_F6) {
+ // Switch character
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ c = &party._activeParty[_buttonValue];
+ intf.highlightChar(_buttonValue);
+ }
+ }
+ else if (_buttonValue == Common::KEYCODE_b) {
+ c = ItemsDialog::show(_vm, c, ITEMMODE_BLACKSMITH);
+ _buttonValue = 0;
+ }
+
+ return c;
+}
+
+Character *Town::doGuildOptions(Character *c) {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ if (_buttonValue >= Common::KEYCODE_F1 && _buttonValue <= Common::KEYCODE_F6) {
+ // Switch character
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ c = &party._activeParty[_buttonValue];
+ intf.highlightChar(_buttonValue);
+
+ if (!c->guildMember()) {
+ sound.playSample(nullptr, 0);
+ intf._overallFrame = 5;
+ File f(isDarkCc ? "skull1.voc" : "guild11.voc");
+ sound.playSample(&f, 1);
+ }
+ }
+ } else if (_buttonValue == Common::KEYCODE_s) {
+ if (c->guildMember())
+ c = SpellsDialog::show(_vm, nullptr, c, 0x80);
+ _buttonValue = 0;
+ } else if (_buttonValue == Common::KEYCODE_c) {
+ if (!c->noActions()) {
+ if (c->guildMember())
+ c = SpellsDialog::show(_vm, nullptr, c, 0);
+ _buttonValue = 0;
+ }
+ }
+
+ return c;
+}
+
+Character *Town::doTavernOptions(Character *c) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ Screen &screen = *_vm->_screen;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ int idx = 0;
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ // Switch character
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ c = &party._activeParty[_buttonValue];
+ intf.highlightChar(_buttonValue);
+ _v21 = 0;
+ }
+ break;
+ case Common::KEYCODE_d:
+ // Drink
+ if (!c->noActions()) {
+ if (party.subtract(0, 1, 0, WT_2)) {
+ sound.playSample(nullptr, 0);
+ File f("gulp.voc");
+ sound.playSample(&f, 0);
+ _v21 = 1;
+
+ screen._windows[10].writeString(Common::String::format(TAVERN_TEXT,
+ c->_name.c_str(), GOOD_STUFF,
+ XeenEngine::printMil(party._gold).c_str()));
+ drawButtons(&screen._windows[0]);
+ screen._windows[10].update();
+
+ if (_vm->getRandomNumber(100) < 26) {
+ ++c->_conditions[DRUNK];
+ intf.drawParty(true);
+ sound.playFX(28);
+ }
+
+ townWait();
+ }
+ }
+ break;
+ case Common::KEYCODE_f: {
+ // Food
+ if (party._mazeId == (isDarkCc ? 29 : 28)) {
+ _v22 = party._activeParty.size() * 15;
+ _v23 = 10;
+ idx = 0;
+ } else if (isDarkCc && party._mazeId == 31) {
+ _v22 = party._activeParty.size() * 60;
+ _v23 = 100;
+ idx = 1;
+ } else if (!isDarkCc && party._mazeId == 30) {
+ _v22 = party._activeParty.size() * 50;
+ _v23 = 50;
+ idx = 1;
+ } else if (isDarkCc) {
+ _v22 = party._activeParty.size() * 120;
+ _v23 = 250;
+ idx = 2;
+ } else if (party._mazeId == 49) {
+ _v22 = party._activeParty.size() * 120;
+ _v23 = 100;
+ idx = 2;
+ } else {
+ _v22 = party._activeParty.size() * 15;
+ _v23 = 10;
+ idx = 0;
+ }
+
+ Common::String msg = _textStrings[(isDarkCc ? 60 : 75) + idx];
+ screen._windows[10].close();
+ screen._windows[12].open();
+ screen._windows[12].writeString(msg);
+ screen._windows[12].update();
+
+ if (YesNo::show(_vm, false, true)) {
+ if (party._food >= _v22) {
+ ErrorScroll::show(_vm, FOOD_PACKS_FULL, WT_2);
+ } else if (party.subtract(0, _v23, 0, WT_2)) {
+ party._food = _v22;
+ sound.playSample(nullptr, 0);
+ File f(isDarkCc ? "thanks2.voc" : "thankyou.voc");
+ sound.playSample(&f, 1);
+ }
+ }
+
+ screen._windows[12].close();
+ screen._windows[10].open();
+ _buttonValue = 0;
+ break;
+ }
+
+ case Common::KEYCODE_r: {
+ // Rumors
+ if (party._mazeId == (isDarkCc ? 29 : 28)) {
+ idx = 0;
+ } else if (party._mazeId == (isDarkCc ? 31 : 30)) {
+ idx = 10;
+ } else if (isDarkCc || party._mazeId == 49) {
+ idx = 20;
+ }
+
+ Common::String msg = Common::String::format("\x03""c\x0B""012%s",
+ _textStrings[(party._day % 10) + idx].c_str());
+ Window &w = screen._windows[12];
+ w.open();
+ w.writeString(msg);
+ w.update();
+
+ townWait();
+ w.close();
+ break;
+ }
+
+ case Common::KEYCODE_s: {
+ // Sign In
+ idx = isDarkCc ? (party._mazeId - 29) >> 1 : party._mazeId - 28;
+ assert(idx >= 0);
+ party._mazePosition.x = TAVERN_EXIT_LIST[isDarkCc ? 1 : 0][_townActionId][idx][0];
+ party._mazePosition.y = TAVERN_EXIT_LIST[isDarkCc ? 1 : 0][_townActionId][idx][1];
+
+ if (!isDarkCc || party._mazeId == 29)
+ party._mazeDirection = DIR_WEST;
+ else if (party._mazeId == 31)
+ party._mazeDirection = DIR_EAST;
+ else
+ party._mazeDirection = DIR_SOUTH;
+
+ party._priorMazeId = party._mazeId;
+ for (idx = 0; idx < (int)party._activeParty.size(); ++idx) {
+ party._activeParty[idx]._savedMazeId = party._mazeId;
+ party._activeParty[idx]._xeenSide = map._loadDarkSide;
+ }
+
+ party.addTime(1440);
+ party._mazeId = 0;
+ _vm->_quitMode = 2;
+ break;
+ }
+
+ case Common::KEYCODE_t:
+ if (!c->noActions()) {
+ if (!_v21) {
+ screen._windows[10].writeString(Common::String::format(TAVERN_TEXT,
+ c->_name.c_str(), HAVE_A_DRINK,
+ XeenEngine::printMil(party._gold).c_str()));
+ drawButtons(&screen);
+ screen._windows[10].update();
+ townWait();
+ } else {
+ _v21 = 0;
+ if (c->_conditions[DRUNK]) {
+ screen._windows[10].writeString(Common::String::format(TAVERN_TEXT,
+ c->_name.c_str(), YOURE_DRUNK,
+ XeenEngine::printMil(party._gold).c_str()));
+ drawButtons(&screen);
+ screen._windows[10].update();
+ townWait();
+ } else if (party.subtract(0, 1, 0, WT_2)) {
+ sound.playSample(nullptr, 0);
+ File f(isDarkCc ? "thanks2.voc" : "thankyou.voc");
+ sound.playSample(&f, 1);
+
+ if (party._mazeId == (isDarkCc ? 29 : 28)) {
+ _v24 = 30;
+ } else if (isDarkCc && party._mazeId == 31) {
+ _v24 = 40;
+ } else if (!isDarkCc && party._mazeId == 45) {
+ _v24 = 45;
+ } else if (!isDarkCc && party._mazeId == 49) {
+ _v24 = 60;
+ } else if (isDarkCc) {
+ _v24 = 50;
+ }
+
+ Common::String msg = _textStrings[map.mazeData()._tavernTips + _v24];
+ map.mazeData()._tavernTips = (map.mazeData()._tavernTips + 1) /
+ (isDarkCc ? 10 : 15);
+
+ Window &w = screen._windows[12];
+ w.open();
+ w.writeString(Common::String::format("\x03""c\x0B""012%s", msg.c_str()));
+ w.update();
+ townWait();
+ w.close();
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return c;
+}
+
+Character *Town::doTempleOptions(Character *c) {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ // Switch character
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ c = &party._activeParty[_buttonValue];
+ intf.highlightChar(_buttonValue);
+ _dayOfWeek = 0;
+ }
+ break;
+
+ case Common::KEYCODE_d:
+ if (_donation && party.subtract(0, _donation, 0, WT_2)) {
+ sound.playSample(nullptr, 0);
+ File f("coina.voc");
+ sound.playSample(&f, 1);
+ _dayOfWeek = (_dayOfWeek + 1) / 10;
+
+ if (_dayOfWeek == (party._day / 10)) {
+ party._clairvoyanceActive = true;
+ party._lightCount = 1;
+
+ int amt = _dayOfWeek ? _dayOfWeek : 10;
+ party._heroism = amt;
+ party._holyBonus = amt;
+ party._powerShield = amt;
+ party._blessed = amt;
+
+ intf.drawParty(true);
+ sound.playSample(nullptr, 0);
+ Voc voc("ahh.voc");
+ voc.play();
+ _flag1 = true;
+ _donation = 0;
+ }
+ }
+ break;
+
+ case Common::KEYCODE_h:
+ if (_healCost && party.subtract(0, _healCost, 0, WT_2)) {
+ c->_magicResistence._temporary = 0;
+ c->_energyResistence._temporary = 0;
+ c->_poisonResistence._temporary = 0;
+ c->_electricityResistence._temporary = 0;
+ c->_coldResistence._temporary = 0;
+ c->_fireResistence._temporary = 0;
+ c->_ACTemp = 0;
+ c->_level._temporary = 0;
+ c->_luck._temporary = 0;
+ c->_accuracy._temporary = 0;
+ c->_speed._temporary = 0;
+ c->_endurance._temporary = 0;
+ c->_personality._temporary = 0;
+ c->_intellect._temporary = 0;
+ c->_might._temporary = 0;
+ c->_currentHp = c->getMaxHP();
+ Common::fill(&c->_conditions[HEART_BROKEN], &c->_conditions[NO_CONDITION], 0);
+
+ _v1 = 1440;
+ intf.drawParty(true);
+ sound.playSample(nullptr, 0);
+ File f("ahh.voc");
+ sound.playSample(&f, 1);
+ }
+ break;
+
+ case Common::KEYCODE_u:
+ if (_uncurseCost && party.subtract(0, _uncurseCost, 0, WT_2)) {
+ for (int idx = 0; idx < 9; ++idx) {
+ c->_weapons[idx]._bonusFlags &= ~ITEMFLAG_CURSED;
+ c->_armor[idx]._bonusFlags &= ~ITEMFLAG_CURSED;
+ c->_accessories[idx]._bonusFlags &= ~ITEMFLAG_CURSED;
+ c->_misc[idx]._bonusFlags &= ~ITEMFLAG_CURSED;
+ }
+
+ _v1 = 1440;
+ intf.drawParty(true);
+ sound.playSample(nullptr, 0);
+ File f("ahh.voc");
+ sound.playSample(&f, 1);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return c;
+}
+
+Character *Town::doTrainingOptions(Character *c) {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ // Switch character
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ _v2 = _buttonValue;
+ c = &party._activeParty[_buttonValue];
+ intf.highlightChar(_buttonValue);
+ }
+ break;
+
+ case Common::KEYCODE_t:
+ if (_experienceToNextLevel) {
+ sound.playSample(nullptr, 0);
+ _drawFrameIndex = 0;
+
+ Common::String name;
+ if (c->_level._permanent >= _v20) {
+ name = isDarkCc ? "gtlost.voc" : "trainin1.voc";
+ } else {
+ name = isDarkCc ? "gtlost.voc" : "trainin0.voc";
+ }
+
+ File f(name);
+ sound.playSample(&f);
+
+ } else if (!c->noActions()) {
+ if (party.subtract(0, (c->_level._permanent * c->_level._permanent) * 10, 0, WT_2)) {
+ _drawFrameIndex = 0;
+ sound.playSample(nullptr, 0);
+ File f(isDarkCc ? "prtygd.voc" : "trainin2.voc");
+ sound.playSample(&f, 1);
+
+ c->_experience -= c->nextExperienceLevel() -
+ (c->getCurrentExperience() - c->_experience);
+ c->_level._permanent++;
+
+ if (!_arr1[_v2]) {
+ party.addTime(1440);
+ _arr1[_v2] = 1;
+ }
+
+ party.resetTemps();
+ c->_currentHp = c->getMaxHP();
+ c->_currentSp = c->getMaxSP();
+ intf.drawParty(true);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return c;
+}
+
+void Town::depositWithdrawl(int choice) {
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ int gold, gems;
+
+ if (choice) {
+ gold = party._bankGold;
+ gems = party._bankGems;
+ } else {
+ gold = party._gold;
+ gems = party._gems;
+ }
+
+ for (uint idx = 0; idx < _buttons.size(); ++idx)
+ _buttons[idx]._sprites = &_icons2;
+ _buttons[0]._value = Common::KEYCODE_o;
+ _buttons[1]._value = Common::KEYCODE_e;
+ _buttons[2]._value = Common::KEYCODE_ESCAPE;
+
+ Common::String msg = Common::String::format(GOLD_GEMS,
+ DEPOSIT_WITHDRAWL[choice],
+ XeenEngine::printMil(gold).c_str(),
+ XeenEngine::printMil(gems).c_str());
+
+ screen._windows[35].open();
+ screen._windows[35].writeString(msg);
+ drawButtons(&screen._windows[35]);
+ screen._windows[35].update();
+
+ sound.playSample(nullptr, 0);
+ File voc("coina.voc");
+ bool flag = false;
+
+ do {
+ switch (townWait()) {
+ case Common::KEYCODE_o:
+ flag = false;
+ break;
+ case Common::KEYCODE_e:
+ flag = true;
+ break;
+ case Common::KEYCODE_ESCAPE:
+ break;
+ default:
+ continue;
+ }
+
+ if ((choice && !party._bankGems && flag) ||
+ (choice && !party._bankGold && !flag) ||
+ (!choice && !party._gems && flag) ||
+ (!choice && !party._gold && !flag)) {
+ party.notEnough(flag, choice, 1, WT_2);
+ } else {
+ screen._windows[35].writeString(AMOUNT);
+ int amount = NumericInput::show(_vm, 35, 10, 77);
+
+ if (amount) {
+ if (flag) {
+ if (party.subtract(true, amount, choice, WT_2)) {
+ if (choice) {
+ party._gems += amount;
+ } else {
+ party._bankGems += amount;
+ }
+ }
+ } else {
+ if (party.subtract(false, amount, choice, WT_2)) {
+ if (choice) {
+ party._gold += amount;
+ } else {
+ party._bankGold += amount;
+ }
+ }
+ }
+ }
+
+ if (choice) {
+ gold = party._bankGold;
+ gems = party._bankGems;
+ } else {
+ gold = party._gold;
+ gems = party._gems;
+ }
+
+ sound.playSample(&voc, 0);
+ msg = Common::String::format(GOLD_GEMS_2, DEPOSIT_WITHDRAWL[choice],
+ XeenEngine::printMil(gold).c_str(), XeenEngine::printMil(gems).c_str());
+ screen._windows[35].writeString(msg);
+ screen._windows[35].update();
+ }
+ // TODO
+ } while (!_vm->shouldQuit() && _buttonValue != Common::KEYCODE_ESCAPE);
+
+ for (uint idx = 0; idx < _buttons.size(); ++idx)
+ _buttons[idx]._sprites = &_icons1;
+ _buttons[0]._value = Common::KEYCODE_d;
+ _buttons[1]._value = Common::KEYCODE_w;
+ _buttons[2]._value = Common::KEYCODE_ESCAPE;
+}
+
+void Town::drawTownAnim(bool flag) {
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ if (_townActionId == 1) {
+ if (sound.playSample(1, 0)) {
+ if (isDarkCc) {
+ _townSprites[_drawFrameIndex / 8].draw(screen, _drawFrameIndex % 8, _townPos);
+ _townSprites[2].draw(screen, _vm->getRandomNumber(11) == 1 ? 9 : 10,
+ Common::Point(34, 33));
+ _townSprites[2].draw(screen, _vm->getRandomNumber(5) + 3,
+ Common::Point(34, 54));
+ }
+ } else {
+ _townSprites[_drawFrameIndex / 8].draw(screen, _drawFrameIndex % 8, _townPos);
+ if (isDarkCc) {
+ _townSprites[2].draw(screen, _vm->getRandomNumber(11) == 1 ? 9 : 10,
+ Common::Point(34, 33));
+ }
+ }
+ } else {
+ if (!isDarkCc || _townActionId != 5) {
+ if (!_townSprites[_drawFrameIndex / 8].empty())
+ _townSprites[_drawFrameIndex / 8].draw(screen, _drawFrameIndex % 8, _townPos);
+ }
+ }
+
+ switch (_townActionId) {
+ case 0:
+ if (sound.playSample(1, 0) || (isDarkCc && intf._overallFrame)) {
+ if (isDarkCc) {
+ if (sound.playSample(1, 0) || intf._overallFrame == 1) {
+ _townSprites[4].draw(screen, _vm->getRandomNumber(13, 18),
+ Common::Point(8, 30));
+ } else if (intf._overallFrame > 1) {
+ _townSprites[4].draw(screen, 13 - intf._overallFrame++,
+ Common::Point(8, 30));
+ if (intf._overallFrame > 14)
+ intf._overallFrame = 0;
+ }
+ } else {
+ _townSprites[2].draw(screen, _vm->getRandomNumber(7, 11), Common::Point(8, 8));
+ }
+ }
+ break;
+
+ case 2:
+ if (sound.playSample(1, 0)) {
+ if (isDarkCc) {
+ if (intf._overallFrame) {
+ intf._overallFrame ^= 1;
+ _townSprites[6].draw(screen, intf._overallFrame, Common::Point(8, 106));
+ } else {
+ _townSprites[6].draw(screen, _vm->getRandomNumber(3), Common::Point(16, 48));
+ }
+ }
+ }
+ break;
+
+ case 3:
+ if (sound.playSample(1, 0) && isDarkCc) {
+ _townSprites[4].draw(screen, _vm->getRandomNumber(7), Common::Point(153, 49));
+ }
+ break;
+ case 4:
+ if (sound.playSample(1, 0)) {
+ _townSprites[3].draw(screen, _vm->getRandomNumber(2, 4), Common::Point(8, 8));
+
+ }
+ break;
+
+ case 5:
+ if (sound.playSample(1, 0)) {
+ if (isDarkCc) {
+ _townSprites[_drawFrameIndex / 8].draw(screen, _drawFrameIndex % 8, _townPos);
+ }
+ } else {
+ if (isDarkCc) {
+ _townSprites[0].draw(screen, ++intf._overallFrame % 8, Common::Point(8, 8));
+ _townSprites[5].draw(screen, _vm->getRandomNumber(5), Common::Point(61, 74));
+ } else {
+ _townSprites[1].draw(screen, _vm->getRandomNumber(8, 12), Common::Point(8, 8));
+ }
+ }
+ }
+
+ if (flag) {
+ intf._face1UIFrame = 0;
+ intf._face2UIFrame = 0;
+ intf._dangerSenseUIFrame = 0;
+ intf._spotDoorsUIFrame = 0;
+ intf._levitateUIFrame = 0;
+
+ intf.assembleBorder();
+ }
+
+ if (screen._windows[11]._enabled) {
+ _drawCtr1 = (_drawCtr1 + 1) % 2;
+ if (!_drawCtr1 || !_drawCtr2) {
+ _drawFrameIndex = 0;
+ _drawCtr2 = 0;
+ } else {
+ _drawFrameIndex = _vm->getRandomNumber(3);
+ }
+ } else {
+ _drawFrameIndex = (_drawFrameIndex + 1) % _townMaxId;
+ }
+
+ if (isDarkCc) {
+ if (_townActionId == 1 && (_drawFrameIndex == 4 || _drawFrameIndex == 13))
+ sound.playFX(45);
+
+ if (_townActionId == 5 && _drawFrameIndex == 23) {
+ File f("spit1.voc");
+ sound.playSample(&f, 0);
+ }
+ } else {
+ if (_townMaxId == 32 && _drawFrameIndex == 0)
+ _drawFrameIndex = 17;
+ if (_townMaxId == 26 && _drawFrameIndex == 0)
+ _drawFrameIndex = 20;
+ if (_townActionId == 1 && (_drawFrameIndex == 3 || _drawFrameIndex == 9))
+ sound.playFX(45);
+ }
+
+ screen._windows[3].update();
+}
+
+bool Town::isActive() const {
+ return _townSprites.size() > 0 && !_townSprites[0].empty();
+}
+
+void Town::clearSprites() {
+ _townSprites.clear();
+}
+
+/*------------------------------------------------------------------------*/
+
+bool TownMessage::show(XeenEngine *vm, int portrait, const Common::String &name,
+ const Common::String &text, int confirm) {
+ TownMessage *dlg = new TownMessage(vm);
+ bool result = dlg->execute(portrait, name, text, confirm);
+ delete dlg;
+
+ return result;
+}
+
+bool TownMessage::execute(int portrait, const Common::String &name, const Common::String &text,
+ int confirm) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ Town &town = *_vm->_town;
+ Window &w = screen._windows[11];
+
+ town._townMaxId = 4;
+ town._townActionId = 7;
+ town._drawFrameIndex = 0;
+ town._townPos = Common::Point(23, 22);
+
+ if (!confirm)
+ loadButtons();
+
+ if (town._townSprites[0].empty()) {
+ town._townSprites[0].load(Common::String::format("face%02d.fac", portrait));
+ town._townSprites[1].load("frame.fac");
+ }
+
+ if (!w._enabled)
+ w.open();
+
+ int result = -1;
+ Common::String msgText = text;
+ for (;;) {
+ Common::String msg = Common::String::format("\r\v014\x03c\t125%s\t000\v054%s",
+ name.c_str(), msgText.c_str());
+ const char *msgEnd = w.writeString(msg.c_str());
+ int wordCount = 0;
+
+ for (const char *msgP = msg.c_str(); msgP < msgEnd; ++msgP) {
+ if (*msgP == ' ')
+ ++wordCount;
+ }
+
+ town._drawCtr2 = wordCount * 2;
+ town._townSprites[1].draw(screen, 0, Common::Point(16, 16));
+ town._townSprites[0].draw(screen, town._drawFrameIndex, Common::Point(23, 22));
+ w.update();
+
+ if (!msgEnd) {
+ // Doesn't look like the code here in original can ever be reached
+ assert(0);
+ }
+
+ if (confirm == 2) {
+ intf._face1State = intf._face2State = 2;
+ return false;
+ }
+
+ do {
+ events.clearEvents();
+ events.updateGameCounter();
+ if (msgEnd)
+ clearButtons();
+
+ do {
+ events.wait(3, true);
+ checkEvents(_vm);
+ if (_vm->shouldQuit())
+ return false;
+
+ town.drawTownAnim(false);
+ events.updateGameCounter();
+ } while (!_buttonValue);
+
+ if (msgEnd)
+ break;
+
+ if (!msgEnd) {
+ if (confirm || _buttonValue == Common::KEYCODE_ESCAPE ||
+ _buttonValue == Common::KEYCODE_n)
+ result = 0;
+ else if (_buttonValue == Common::KEYCODE_y)
+ result = 1;
+ }
+ } while (result == -1);
+
+ if (msgEnd) {
+ msgText = Common::String(msgEnd);
+ town._drawCtr2 = wordCount;
+ continue;
+ }
+ } while (result == -1);
+
+ intf._face1State = intf._face2State = 2;
+ if (!confirm)
+ intf.mainIconsPrint();
+
+ town._townSprites[0].clear();
+ town._townSprites[1].clear();
+ return result == 1;
+}
+
+void TownMessage::loadButtons() {
+ _iconSprites.load("confirm.icn");
+
+ addButton(Common::Rect(235, 75, 259, 95), Common::KEYCODE_y, &_iconSprites);
+ addButton(Common::Rect(260, 75, 284, 95), Common::KEYCODE_n, &_iconSprites);
+ addButton(Common::Rect(), Common::KEYCODE_ESCAPE);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/town.h b/engines/xeen/town.h
new file mode 100644
index 0000000000..f56f24ca73
--- /dev/null
+++ b/engines/xeen/town.h
@@ -0,0 +1,133 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_TOWN_H
+#define XEEN_TOWN_H
+
+#include "common/scummsys.h"
+#include "common/str-array.h"
+#include "xeen/dialogs.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/party.h"
+
+namespace Xeen {
+
+class XeenEngine;
+class TownMessage;
+
+class Town: public ButtonContainer {
+ friend class TownMessage;
+private:
+ XeenEngine *_vm;
+ SpriteResource _icons1, _icons2;
+ Common::StringArray _textStrings;
+ Common::Array<SpriteResource> _townSprites;
+ int _townMaxId;
+ int _townActionId;
+ int _v1, _v2;
+ int _donation;
+ int _healCost;
+ int _v5, _v6;
+ int _v10, _v11, _v12;
+ int _v13, _v14;
+ uint _v20;
+ int _v21;
+ uint _v22;
+ int _v23;
+ int _v24;
+ int _dayOfWeek;
+ int _uncurseCost;
+ Common::Point _townPos;
+ int _arr1[6];
+ int _currentCharLevel;
+ bool _flag1;
+ uint _experienceToNextLevel;
+ int _drawFrameIndex;
+ int _drawCtr1, _drawCtr2;
+
+ void loadStrings(const Common::String &name);
+
+ void pyramidEvent();
+
+ void arenaEvent();
+
+ void reaperEvent();
+
+ void golemEvent();
+
+ void sphinxEvent();
+
+ void dwarfEvent();
+
+ Common::String createTownText(Character &ch);
+
+ int townWait();
+
+ Character *doTownOptions(Character *c);
+
+ Character *doBankOptions(Character *c);
+
+ Character *doBlacksmithOptions(Character *c);
+
+ Character *doGuildOptions(Character *c);
+
+ Character *doTavernOptions(Character *c);
+
+ Character *doTempleOptions(Character *c);
+
+ Character *doTrainingOptions(Character *c);
+
+ void depositWithdrawl(int choice);
+public:
+ Town(XeenEngine *vm);
+
+ int townAction(int actionId);
+
+ void drawTownAnim(bool flag);
+
+ /**
+ * Returns true if a town location (bank, blacksmith, etc.) is currently active
+ */
+ bool isActive() const;
+
+ void clearSprites();
+};
+
+class TownMessage : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ TownMessage(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ bool execute(int portrait, const Common::String &name,
+ const Common::String &text, int confirm);
+
+ void loadButtons();
+public:
+ static bool show(XeenEngine *vm, int portrait, const Common::String &name,
+ const Common::String &text, int confirm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_SPELLS_H */
diff --git a/engines/xeen/worldofxeen/clouds_cutscenes.cpp b/engines/xeen/worldofxeen/clouds_cutscenes.cpp
new file mode 100644
index 0000000000..1d578bdbea
--- /dev/null
+++ b/engines/xeen/worldofxeen/clouds_cutscenes.cpp
@@ -0,0 +1,43 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#include "xeen/worldofxeen/clouds_cutscenes.h"
+#include "xeen/sound.h"
+
+namespace Xeen {
+
+bool CloudsCutscenes::showCloudsTitle() {
+ // TODO
+ return true;
+}
+
+bool CloudsCutscenes::showCloudsIntro() {
+ // TODO
+ return true;
+}
+
+bool CloudsCutscenes::showCloudsEnding() {
+ // TODO
+ return true;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/worldofxeen/clouds_cutscenes.h b/engines/xeen/worldofxeen/clouds_cutscenes.h
new file mode 100644
index 0000000000..2c2ed602ea
--- /dev/null
+++ b/engines/xeen/worldofxeen/clouds_cutscenes.h
@@ -0,0 +1,55 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#ifndef XEEN_WORLDOFXEEN_CLOUDS_CUTSCENES_H
+#define XEEN_WORLDOFXEEN_CLOUDS_CUTSCENES_H
+
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+class XeenEngine;
+
+class CloudsCutscenes {
+private:
+ XeenEngine *_vm;
+public:
+ CloudsCutscenes(XeenEngine *vm) : _vm(vm) {}
+
+ /**
+ * Shows the Clouds of Xeen title screen
+ */
+ bool showCloudsTitle();
+
+ /**
+ * Shows the Clouds of Xeen intro sequence
+ */
+ bool showCloudsIntro();
+
+ /**
+ * Shows the Clouds of Xeen ending sequence
+ */
+ bool showCloudsEnding();
+};
+} // End of namespace Xeen
+
+#endif /* XEEN_WORLDOFXEEN_CLOUDS_CUTSCENES_H */
diff --git a/engines/xeen/worldofxeen/darkside_cutscenes.cpp b/engines/xeen/worldofxeen/darkside_cutscenes.cpp
new file mode 100644
index 0000000000..bbbe138a58
--- /dev/null
+++ b/engines/xeen/worldofxeen/darkside_cutscenes.cpp
@@ -0,0 +1,258 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#include "xeen/worldofxeen/darkside_cutscenes.h"
+#include "xeen/worldofxeen/worldofxeen.h"
+#include "xeen/sound.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+bool DarkSideCutscenes::showDarkSideTitle() {
+ EventsManager &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+
+ // TODO: Starting method, and sound
+ //sub_28F40
+ screen.loadPalette("dark.pal");
+ SpriteResource nwc[4] = {
+ SpriteResource("nwc1.int"), SpriteResource("nwc2.int"),
+ SpriteResource("nwc3.int"), SpriteResource("nwc4.int")
+ };
+ Voc voc[3];
+ voc[0].open("dragon1.voc");
+ voc[1].open("dragon2.voc");
+ voc[2].open("dragon3.voc");
+
+ // Load backgrounds
+ screen.loadBackground("nwc1.raw");
+ screen.loadPage(0);
+ screen.loadBackground("nwc2.raw");
+ screen.loadPage(1);
+
+ // Draw the screen and fade it in
+ screen.horizMerge(0);
+ screen.draw();
+ screen.fadeIn(4);
+
+ // Initial loop for dragon roaring
+ int nwcIndex = 0, nwcFrame = 0;
+ for (int idx = 0; idx < 55 && !_vm->shouldQuit(); ++idx) {
+ // Render the next frame
+ events.updateGameCounter();
+ screen.vertMerge(0);
+ nwc[nwcIndex].draw(screen, nwcFrame);
+ screen.draw();
+
+ switch (idx) {
+ case 17:
+ voc[0].play();
+ break;
+ case 34:
+ case 44:
+ ++nwcIndex;
+ nwcFrame = 0;
+ break;
+ case 35:
+ voc[1].play();
+ break;
+ default:
+ ++nwcFrame;
+ }
+
+ if (events.wait(2, true))
+ return false;
+ }
+
+ // Loop for dragon using flyspray
+ for (int idx = 0; idx < 42 && !_vm->shouldQuit(); ++idx) {
+ events.updateGameCounter();
+ screen.vertMerge(SCREEN_HEIGHT);
+ nwc[3].draw(screen, idx);
+ screen.draw();
+
+ switch (idx) {
+ case 3:
+ sound.startMusic(40);
+ break;
+ case 11:
+ sound.startMusic(0);
+ case 27:
+ case 30:
+ sound.startMusic(3);
+ break;
+ case 31:
+ sound.proc2(voc[2]);
+ break;
+ case 33:
+ sound.startMusic(2);
+ break;
+ default:
+ break;
+ }
+
+ if (events.wait(2, true))
+ return false;
+ }
+
+ // Pause for a bit
+ if (events.wait(10, true))
+ return false;
+
+ voc[0].stop();
+ voc[1].stop();
+ voc[2].stop();
+ sound.stopMusic(95);
+
+ screen.loadBackground("jvc.raw");
+ screen.fadeOut(8);
+ screen.draw();
+ screen.fadeIn(4);
+
+ events.updateGameCounter();
+ events.wait(60, true);
+ return true;
+}
+
+bool DarkSideCutscenes::showDarkSideIntro() {
+ EventsManager &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ const int XLIST1[] = {
+ 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 58, 60, 62
+ };
+ const int YLIST1[] = {
+ 0, 5, 10, 15, 20, 25, 30, 35, 40, 40, 39, 37, 35, 33, 31
+ };
+ const int XLIST2[] = {
+ 160, 155, 150, 145, 140, 135, 130, 125, 120, 115, 110, 105, 98, 90, 82
+ };
+
+ screen.fadeOut(8);
+ screen.loadBackground("pyramid2.raw");
+ screen.loadPage(0);
+ screen.loadPage(1);
+ screen.loadBackground("pyramid3.raw");
+ screen.saveBackground(1);
+
+ SpriteResource sprites[3] = {
+ SpriteResource("title.int"), SpriteResource("pyratop.int"), SpriteResource("pyramid.int")
+ };
+ Voc voc[2];
+ voc[0].open("pharoh1a.voc");
+ voc[1].open("pharoh1b.voc");
+
+ screen.vertMerge(SCREEN_HEIGHT);
+ screen.loadPage(0);
+ screen.loadPage(1);
+
+ // Show Might and Magic Darkside of Xeen title, and gradualy scroll
+ // the background vertically down to show the Pharoah's base
+ int yp = 0;
+ int frameNum = 0;
+ int idx1 = 0;
+ bool skipElapsed = false;
+ uint32 timeExpired = 0;
+ bool fadeFlag = true;
+
+ for (int yCtr = SCREEN_HEIGHT; yCtr > 0; ) {
+ events.updateGameCounter();
+ screen.vertMerge(yp);
+
+ sprites[0].draw(screen, 0);
+ if (frameNum)
+ sprites[0].draw(screen, frameNum);
+
+ idx1 = (idx1 + 1) % 4;
+ if (!idx1)
+ frameNum = (frameNum + 1) % 10;
+
+ screen.draw();
+ if (!skipElapsed) {
+ timeExpired = MAX((int)events.timeElapsed() - 1, 1);
+ skipElapsed = true;
+ }
+
+ yCtr -= timeExpired;
+ yp = MIN((uint)(yp + timeExpired), (uint)200);
+
+ if (events.wait(1, true))
+ return false;
+
+ if (fadeFlag) {
+ screen.fadeIn(4);
+ fadeFlag = false;
+ }
+ }
+
+ screen.vertMerge(SCREEN_HEIGHT);
+ screen.saveBackground(1);
+ screen.draw();
+ screen.freePages();
+
+ events.updateGameCounter();
+ events.wait(30, true);
+
+ // Zoom into the Pharoah's base closeup view
+ for (int idx = 14; idx >= 0; --idx) {
+ events.updateGameCounter();
+ sprites[1].draw(screen, 0, Common::Point(XLIST1[idx], YLIST1[idx]));
+ sprites[1].draw(screen, 1, Common::Point(XLIST2[idx], YLIST1[idx]));
+ screen.draw();
+
+ if (idx == 2)
+ sound.stopMusic(48);
+ if (events.wait(2, true))
+ return false;
+ }
+
+ // TODO: More
+ sound.playSong(voc[0]);
+ sound.playSong(voc[1]);
+
+ return true;
+}
+
+bool DarkSideCutscenes::showDarkSideEnding() {
+ EventsManager &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+
+ Voc voc("ido2.voc");
+ Music newBright("newbrigh.m");
+ SpriteResource box("box.vga");
+
+ newBright.play();
+ screen.loadBackground("scene1.raw");
+ screen.loadPalette("endgame.pal");
+ screen.update();
+
+ screen.fadeIn(4);
+ events.updateGameCounter();
+
+ // TODO
+ events.wait(5000);
+ return true;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/worldofxeen/darkside_cutscenes.h b/engines/xeen/worldofxeen/darkside_cutscenes.h
new file mode 100644
index 0000000000..1b78e3ea2d
--- /dev/null
+++ b/engines/xeen/worldofxeen/darkside_cutscenes.h
@@ -0,0 +1,54 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_WORLDOFXEEN_DARKSIDE_CUTSCENES_H
+#define XEEN_WORLDOFXEEN_DARKSIDE_CUTSCENES_H
+
+namespace Xeen {
+
+class XeenEngine;
+
+class DarkSideCutscenes {
+private:
+ XeenEngine *_vm;
+public:
+ DarkSideCutscenes(XeenEngine *vm) : _vm(vm) {}
+
+ /**
+ * Shows the Dark Side of Xeen title screen
+ */
+ bool showDarkSideTitle();
+
+ /**
+ * Shows the Dark Side of Xeen intro sequence
+ */
+ bool showDarkSideIntro();
+
+ /**
+ * Shows the Dark Side of Xeen ending sequence
+ */
+ bool showDarkSideEnding();
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_WORLDOFXEEN_DARKSIDE_CUTSCENES_H */
diff --git a/engines/xeen/worldofxeen/worldofxeen.cpp b/engines/xeen/worldofxeen/worldofxeen.cpp
new file mode 100644
index 0000000000..86a6ee3da7
--- /dev/null
+++ b/engines/xeen/worldofxeen/worldofxeen.cpp
@@ -0,0 +1,46 @@
+/* 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.
+ *
+ */
+
+#include "xeen/worldofxeen/worldofxeen.h"
+#include "xeen/worldofxeen/darkside_cutscenes.h"
+#include "xeen/worldofxeen/clouds_cutscenes.h"
+#include "xeen/sound.h"
+
+namespace Xeen {
+
+WorldOfXeenEngine::WorldOfXeenEngine(OSystem *syst, const XeenGameDescription *gameDesc)
+ : XeenEngine(syst, gameDesc), CloudsCutscenes(this),
+ DarkSideCutscenes(this) {
+ _seenDarkSideIntro = false;
+}
+
+void WorldOfXeenEngine::showIntro() {
+ // **DEBUG**
+ if (gDebugLevel == 0)
+ return;
+
+ bool completed = showDarkSideTitle();
+ if (!_seenDarkSideIntro && completed)
+ showDarkSideIntro();
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/worldofxeen/worldofxeen.h b/engines/xeen/worldofxeen/worldofxeen.h
new file mode 100644
index 0000000000..68a83bb89d
--- /dev/null
+++ b/engines/xeen/worldofxeen/worldofxeen.h
@@ -0,0 +1,50 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_WORLDOFXEEN_WORLDOFXEEN_H
+#define XEEN_WORLDOFXEEN_WORLDOFXEEN_H
+
+#include "xeen/xeen.h"
+#include "xeen/worldofxeen/clouds_cutscenes.h"
+#include "xeen/worldofxeen/darkside_cutscenes.h"
+
+namespace Xeen {
+
+/**
+ * Implements a descendant of the base Xeen engine to handle
+ * Clouds of Xeen, Dark Side of Xeen, and Worlds of Xeen specific
+ * game code
+ */
+class WorldOfXeenEngine: public XeenEngine, public CloudsCutscenes,
+ public DarkSideCutscenes {
+protected:
+ virtual void showIntro();
+public:
+ bool _seenDarkSideIntro;
+public:
+ WorldOfXeenEngine(OSystem *syst, const XeenGameDescription *gameDesc);
+ virtual ~WorldOfXeenEngine() {}
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_WORLDOFXEEN_WORLDOFXEEN_H */
diff --git a/engines/xeen/xeen.cpp b/engines/xeen/xeen.cpp
new file mode 100644
index 0000000000..e549c8e845
--- /dev/null
+++ b/engines/xeen/xeen.cpp
@@ -0,0 +1,353 @@
+/* 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.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/config-manager.h"
+#include "common/debug-channels.h"
+#include "common/events.h"
+#include "engines/util.h"
+#include "graphics/scaler.h"
+#include "graphics/thumbnail.h"
+#include "xeen/xeen.h"
+#include "xeen/dialogs_options.h"
+#include "xeen/files.h"
+#include "xeen/resources.h"
+
+namespace Xeen {
+
+XeenEngine::XeenEngine(OSystem *syst, const XeenGameDescription *gameDesc)
+ : Engine(syst), _gameDescription(gameDesc), _randomSource("Xeen") {
+ _combat = nullptr;
+ _debugger = nullptr;
+ _events = nullptr;
+ _files = nullptr;
+ _interface = nullptr;
+ _map = nullptr;
+ _party = nullptr;
+ _resources = nullptr;
+ _saves = nullptr;
+ _screen = nullptr;
+ _scripts = nullptr;
+ _sound = nullptr;
+ _spells = nullptr;
+ _town = nullptr;
+ _eventData = nullptr;
+ _quitMode = 0;
+ _noDirectionSense = false;
+ _mode = MODE_0;
+ _startupWindowActive = false;
+}
+
+XeenEngine::~XeenEngine() {
+ delete _combat;
+ delete _debugger;
+ delete _events;
+ delete _interface;
+ delete _map;
+ delete _party;
+ delete _saves;
+ delete _screen;
+ delete _scripts;
+ delete _sound;
+ delete _spells;
+ delete _town;
+ delete _eventData;
+ delete _resources;
+ delete _files;
+}
+
+void XeenEngine::initialize() {
+ // Set up debug channels
+ DebugMan.addDebugChannel(kDebugPath, "Path", "Pathfinding debug level");
+ DebugMan.addDebugChannel(kDebugScripts, "scripts", "Game scripts");
+ DebugMan.addDebugChannel(kDebugGraphics, "graphics", "Graphics handling");
+ DebugMan.addDebugChannel(kDebugSound, "sound", "Sound and Music handling");
+
+ // Create sub-objects of the engine
+ _files = new FileManager(this);
+ _resources = new Resources();
+ _combat = new Combat(this);
+ _debugger = new Debugger(this);
+ _events = new EventsManager(this);
+ _interface = new Interface(this);
+ _map = new Map(this);
+ _party = new Party(this);
+ _saves = new SavesManager(this, *_party);
+ _screen = new Screen(this);
+ _scripts = new Scripts(this);
+ _screen->setupWindows();
+ _sound = new SoundManager(this, _mixer);
+ _spells = new Spells(this);
+ _town = new Town(this);
+ Voc::init(this);
+ Music::init(this);
+
+ File f("029.obj");
+ _eventData = f.readStream(f.size());
+
+ // Set graphics mode
+ initGraphics(320, 200, false);
+
+ // If requested, load a savegame instead of showing the intro
+ if (ConfMan.hasKey("save_slot")) {
+ int saveSlot = ConfMan.getInt("save_slot");
+ if (saveSlot >= 0 && saveSlot <= 999)
+ _loadSaveSlot = saveSlot;
+ }
+}
+
+Common::Error XeenEngine::run() {
+ initialize();
+
+ showIntro();
+ if (shouldQuit())
+ return Common::kNoError;
+
+ showMainMenu();
+ if (shouldQuit())
+ return Common::kNoError;
+
+ playGame();
+
+ return Common::kNoError;
+}
+
+int XeenEngine::getRandomNumber(int maxNumber) {
+ return _randomSource.getRandomNumber(maxNumber);
+}
+
+int XeenEngine::getRandomNumber(int minNumber, int maxNumber) {
+ return getRandomNumber(maxNumber - minNumber) + minNumber;
+}
+
+Common::Error XeenEngine::saveGameState(int slot, const Common::String &desc) {
+ Common::OutSaveFile *out = g_system->getSavefileManager()->openForSaving(
+ generateSaveName(slot));
+ if (!out)
+ return Common::kCreatingFileFailed;
+
+ XeenSavegameHeader header;
+ header._saveName = desc;
+ writeSavegameHeader(out, header);
+
+ Common::Serializer s(nullptr, out);
+ synchronize(s);
+
+ out->finalize();
+ delete out;
+
+ return Common::kNoError;
+}
+
+Common::Error XeenEngine::loadGameState(int slot) {
+ Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(
+ generateSaveName(slot));
+ if (!saveFile)
+ return Common::kReadingFailed;
+
+ Common::Serializer s(saveFile, nullptr);
+
+ // Load the savaegame header
+ XeenSavegameHeader header;
+ if (!readSavegameHeader(saveFile, header))
+ error("Invalid savegame");
+
+ if (header._thumbnail) {
+ header._thumbnail->free();
+ delete header._thumbnail;
+ }
+
+ // Load most of the savegame data
+ synchronize(s);
+ delete saveFile;
+
+ return Common::kNoError;
+}
+
+Common::String XeenEngine::generateSaveName(int slot) {
+ return Common::String::format("%s.%03d", _targetName.c_str(), slot);
+}
+
+bool XeenEngine::canLoadGameStateCurrently() {
+ return true;
+}
+
+bool XeenEngine::canSaveGameStateCurrently() {
+ return true;
+}
+
+void XeenEngine::synchronize(Common::Serializer &s) {
+ // TODO
+}
+
+const char *const SAVEGAME_STR = "XEEN";
+#define SAVEGAME_STR_SIZE 6
+
+bool XeenEngine::readSavegameHeader(Common::InSaveFile *in, XeenSavegameHeader &header) {
+ char saveIdentBuffer[SAVEGAME_STR_SIZE + 1];
+ header._thumbnail = nullptr;
+
+ // Validate the header Id
+ in->read(saveIdentBuffer, SAVEGAME_STR_SIZE + 1);
+ if (strncmp(saveIdentBuffer, SAVEGAME_STR, SAVEGAME_STR_SIZE))
+ return false;
+
+ header._version = in->readByte();
+ if (header._version > XEEN_SAVEGAME_VERSION)
+ return false;
+
+ // Read in the string
+ header._saveName.clear();
+ char ch;
+ while ((ch = (char)in->readByte()) != '\0')
+ header._saveName += ch;
+
+ // Get the thumbnail
+ header._thumbnail = Graphics::loadThumbnail(*in);
+ if (!header._thumbnail)
+ return false;
+
+ // Read in save date/time
+ header._year = in->readSint16LE();
+ header._month = in->readSint16LE();
+ header._day = in->readSint16LE();
+ header._hour = in->readSint16LE();
+ header._minute = in->readSint16LE();
+ header._totalFrames = in->readUint32LE();
+
+ return true;
+}
+
+void XeenEngine::writeSavegameHeader(Common::OutSaveFile *out, XeenSavegameHeader &header) {
+ // Write out a savegame header
+ out->write(SAVEGAME_STR, SAVEGAME_STR_SIZE + 1);
+
+ out->writeByte(XEEN_SAVEGAME_VERSION);
+
+ // Write savegame name
+ out->writeString(header._saveName);
+ out->writeByte('\0');
+
+ // Write a thumbnail of the screen
+/*
+ uint8 thumbPalette[768];
+ _screen->getPalette(thumbPalette);
+ Graphics::Surface saveThumb;
+ ::createThumbnail(&saveThumb, (const byte *)_screen->getPixels(),
+ _screen->w, _screen->h, thumbPalette);
+ Graphics::saveThumbnail(*out, saveThumb);
+ saveThumb.free();
+*/
+ // Write out the save date/time
+ TimeDate td;
+ g_system->getTimeAndDate(td);
+ out->writeSint16LE(td.tm_year + 1900);
+ out->writeSint16LE(td.tm_mon + 1);
+ out->writeSint16LE(td.tm_mday);
+ out->writeSint16LE(td.tm_hour);
+ out->writeSint16LE(td.tm_min);
+// out->writeUint32LE(_events->getFrameCounter());
+}
+
+void XeenEngine::showMainMenu() {
+ //OptionsMenu::show(this);
+}
+
+void XeenEngine::playGame() {
+ _saves->reset();
+ play();
+}
+
+/*
+ * Secondary method for handling the actual gameplay
+ */
+void XeenEngine::play() {
+ // TODO: Init variables
+ _quitMode = 0;
+
+ _interface->setup();
+ _screen->loadBackground("back.raw");
+ _screen->loadPalette("mm4.pal");
+
+ if (getGameID() != GType_WorldOfXeen && !_map->_loadDarkSide) {
+ _map->_loadDarkSide = true;
+ _party->_mazeId = 29;
+ _party->_mazeDirection = DIR_NORTH;
+ _party->_mazePosition.x = 25;
+ _party->_mazePosition.y = 21;
+ }
+
+ _map->load(_party->_mazeId);
+
+ _interface->startup();
+ if (_mode == MODE_0) {
+// _screen->fadeOut(4);
+ }
+
+ _screen->_windows[0].update();
+ _interface->mainIconsPrint();
+ _screen->_windows[0].update();
+ _events->setCursor(0);
+
+ _combat->_moveMonsters = true;
+ if (_mode == MODE_0) {
+ _mode = MODE_1;
+ _screen->fadeIn(4);
+ }
+
+ _combat->_moveMonsters = true;
+
+ gameLoop();
+}
+
+void XeenEngine::gameLoop() {
+ // Main game loop
+ while (!shouldQuit()) {
+ _map->cellFlagLookup(_party->_mazePosition);
+ if (_map->_currentIsEvent) {
+ _quitMode = _scripts->checkEvents();
+ if (shouldQuit() || _quitMode)
+ return;
+ }
+ _party->giveTreasure();
+
+ // Main user interface handler for waiting for and processing user input
+ _interface->perform();
+ }
+}
+
+Common::String XeenEngine::printMil(uint value) {
+ return (value >= 1000000) ? Common::String::format("%u mil", value / 1000000) :
+ Common::String::format("%u", value);
+}
+
+Common::String XeenEngine::printK(uint value) {
+ return (value > 9999) ? Common::String::format("%uk", value / 1000) :
+ Common::String::format("%u", value);
+}
+
+Common::String XeenEngine::printK2(uint value) {
+ return (value > 999) ? Common::String::format("%uk", value / 1000) :
+ Common::String::format("%u", value);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/xeen.h b/engines/xeen/xeen.h
new file mode 100644
index 0000000000..389d73dcc3
--- /dev/null
+++ b/engines/xeen/xeen.h
@@ -0,0 +1,213 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_XEEN_H
+#define XEEN_XEEN_H
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/error.h"
+#include "common/random.h"
+#include "common/savefile.h"
+#include "common/serializer.h"
+#include "common/util.h"
+#include "engines/engine.h"
+#include "xeen/combat.h"
+#include "xeen/debugger.h"
+#include "xeen/dialogs.h"
+#include "xeen/events.h"
+#include "xeen/files.h"
+#include "xeen/interface.h"
+#include "xeen/map.h"
+#include "xeen/party.h"
+#include "xeen/resources.h"
+#include "xeen/saves.h"
+#include "xeen/screen.h"
+#include "xeen/scripts.h"
+#include "xeen/sound.h"
+#include "xeen/spells.h"
+#include "xeen/town.h"
+
+/**
+ * This is the namespace of the Xeen engine.
+ *
+ * Status of this engine: In Development
+ *
+ * Games using this engine:
+ * - Might & Magic 4: Clouds of Xeen
+ * - Might & Magic 5: Dark Side of Xeen
+ * - Might & Magic: World of Xeen
+ * - Might & Magic: Swords of Xeen
+ */
+namespace Xeen {
+
+enum {
+ GType_Clouds = 1,
+ GType_DarkSide = 2,
+ GType_WorldOfXeen = 3,
+ GType_Swords = 4
+};
+
+enum XeenDebugChannels {
+ kDebugPath = 1 << 0,
+ kDebugScripts = 1 << 1,
+ kDebugGraphics = 1 << 2,
+ kDebugSound = 1 << 3
+};
+
+enum Mode {
+ MODE_FF = -1,
+ MODE_0 = 0,
+ MODE_1 = 1,
+ MODE_COMBAT = 2,
+ MODE_3 = 3,
+ MODE_4 = 4,
+ MODE_SLEEPING = 5,
+ MODE_6 = 6,
+ MODE_7 = 7,
+ MODE_8 = 8,
+ MODE_9 = 9,
+ MODE_CHARACTER_INFO = 10,
+ MODE_12 = 12,
+ MODE_DIALOG_123 = 13,
+ MODE_17 = 17,
+ MODE_86 = 86
+};
+
+struct XeenGameDescription;
+
+#define XEEN_SAVEGAME_VERSION 1
+#define GAME_FRAME_TIME 50
+
+class XeenEngine : public Engine {
+private:
+ const XeenGameDescription *_gameDescription;
+ Common::RandomSource _randomSource;
+ int _loadSaveSlot;
+
+ void showMainMenu();
+
+ void play();
+
+ void pleaseWait();
+
+ void gameLoop();
+protected:
+ virtual void showIntro() = 0;
+
+ /**
+ * Play the game
+ */
+ virtual void playGame();
+private:
+ void initialize();
+
+ /**
+ * Synchronize savegame data
+ */
+ void synchronize(Common::Serializer &s);
+
+ /**
+ * Support method that generates a savegame name
+ * @param slot Slot number
+ */
+ Common::String generateSaveName(int slot);
+
+ // Engine APIs
+ virtual Common::Error run();
+ virtual bool hasFeature(EngineFeature f) const;
+public:
+ Combat *_combat;
+ Debugger *_debugger;
+ EventsManager *_events;
+ FileManager *_files;
+ Interface *_interface;
+ Map *_map;
+ Party *_party;
+ Resources *_resources;
+ SavesManager *_saves;
+ Screen *_screen;
+ Scripts *_scripts;
+ SoundManager *_sound;
+ Spells *_spells;
+ Town *_town;
+ Mode _mode;
+ GameEvent _gameEvent;
+ Common::SeekableReadStream *_eventData;
+ int _quitMode;
+ bool _noDirectionSense;
+ bool _startupWindowActive;
+public:
+ XeenEngine(OSystem *syst, const XeenGameDescription *gameDesc);
+ virtual ~XeenEngine();
+
+ uint32 getFeatures() const;
+ Common::Language getLanguage() const;
+ Common::Platform getPlatform() const;
+ uint16 getVersion() const;
+ uint32 getGameID() const;
+ uint32 getGameFeatures() const;
+
+ int getRandomNumber(int maxNumber);
+
+ int getRandomNumber(int minNumber, int maxNumber);
+
+ /**
+ * Load a savegame
+ */
+ virtual Common::Error loadGameState(int slot);
+
+ /**
+ * Save the game
+ */
+ virtual Common::Error saveGameState(int slot, const Common::String &desc);
+
+ /**
+ * Returns true if a savegame can currently be loaded
+ */
+ bool canLoadGameStateCurrently();
+
+ /**
+ * Returns true if the game can currently be saved
+ */
+ bool canSaveGameStateCurrently();
+
+ /**
+ * Read in a savegame header
+ */
+ static bool readSavegameHeader(Common::InSaveFile *in, XeenSavegameHeader &header);
+
+ /**
+ * Write out a savegame header
+ */
+ void writeSavegameHeader(Common::OutSaveFile *out, XeenSavegameHeader &header);
+
+ static Common::String printMil(uint value);
+
+ static Common::String printK(uint value);
+
+ static Common::String printK2(uint value);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_XEEN_H */
diff --git a/engines/xeen/xsurface.cpp b/engines/xeen/xsurface.cpp
new file mode 100644
index 0000000000..5f10a83dc6
--- /dev/null
+++ b/engines/xeen/xsurface.cpp
@@ -0,0 +1,31 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ */
+
+#include "common/algorithm.h"
+#include "common/util.h"
+#include "xeen/xsurface.h"
+#include "xeen/resources.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+} // End of namespace Xeen
diff --git a/engines/xeen/xsurface.h b/engines/xeen/xsurface.h
new file mode 100644
index 0000000000..40d4611419
--- /dev/null
+++ b/engines/xeen/xsurface.h
@@ -0,0 +1,53 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_XSURFACE_H
+#define XEEN_XSURFACE_H
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/rect.h"
+#include "graphics/managed_surface.h"
+
+namespace Xeen {
+
+class BaseSurface: public Graphics::ManagedSurface {
+public:
+ virtual void addDirtyRect(const Common::Rect &r) {
+ Graphics::ManagedSurface::addDirtyRect(r);
+ }
+public:
+ BaseSurface() : Graphics::ManagedSurface() {}
+ BaseSurface(int width, int height) : Graphics::ManagedSurface(width, height) {}
+ virtual ~BaseSurface() {}
+};
+
+class XSurface : public BaseSurface {
+public:
+ XSurface() : BaseSurface() {}
+ XSurface(int width, int height) : BaseSurface(width, height) {}
+ virtual ~XSurface() {}
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_XSURFACE_H */