/* 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() {
	ObjectInfoIndex objectInfo[180] = { // @ G0237_as_Graphic559_ObjectInfo
		/* { Type, ObjectAspectIndex, ActionSetIndex, AllowedSlots } */
		ObjectInfoIndex(30,  1,  0, 0x0500),   /* COMPASS        Pouch/Chest */
		ObjectInfoIndex(144,  0,  0, 0x0200),  /* COMPASS        Hands */
		ObjectInfoIndex(148, 67,  0, 0x0500),  /* COMPASS        Pouch/Chest */
		ObjectInfoIndex(149, 67,  0, 0x0500),  /* COMPASS        Pouch/Chest */
		ObjectInfoIndex(150, 67,  0, 0x0500),  /* TORCH          Pouch/Chest */
		ObjectInfoIndex(151, 67, 42, 0x0500),  /* TORCH          Pouch/Chest */
		ObjectInfoIndex(152, 67,  0, 0x0500),  /* TORCH          Pouch/Chest */
		ObjectInfoIndex(153, 67,  0, 0x0500),  /* TORCH          Pouch/Chest */
		ObjectInfoIndex(154,  2,  0, 0x0501),  /* WATERSKIN      Mouth/Pouch/Chest */
		ObjectInfoIndex(155,  2,  0, 0x0501),  /* WATER          Mouth/Pouch/Chest */
		ObjectInfoIndex(156,  2,  0, 0x0501),  /* JEWEL SYMAL    Mouth/Pouch/Chest */
		ObjectInfoIndex(157,  2,  0, 0x0501),  /* JEWEL SYMAL    Mouth/Pouch/Chest */
		ObjectInfoIndex(158,  2,  0, 0x0501),  /* ILLUMULET      Mouth/Pouch/Chest */
		ObjectInfoIndex(159,  2,  0, 0x0501),  /* ILLUMULET      Mouth/Pouch/Chest */
		ObjectInfoIndex(160,  2,  0, 0x0501),  /* FLAMITT        Mouth/Pouch/Chest */
		ObjectInfoIndex(161,  2,  0, 0x0501),  /* FLAMITT        Mouth/Pouch/Chest */
		ObjectInfoIndex(162,  2,  0, 0x0501),  /* EYE OF TIME    Mouth/Pouch/Chest */
		ObjectInfoIndex(163,  2,  0, 0x0501),  /* EYE OF TIME    Mouth/Pouch/Chest */
		ObjectInfoIndex(164, 68,  0, 0x0500),  /* STORMRING      Pouch/Chest */
		ObjectInfoIndex(165, 68,  0, 0x0500),  /* STORMRING      Pouch/Chest */
		ObjectInfoIndex(166, 68,  0, 0x0500),  /* STAFF OF CLAWS Pouch/Chest */
		ObjectInfoIndex(167, 68, 42, 0x0500),  /* STAFF OF CLAWS Pouch/Chest */
		ObjectInfoIndex(195, 80,  0, 0x0500),  /* STAFF OF CLAWS Pouch/Chest */
		ObjectInfoIndex(16, 38, 43, 0x0500),   /* BOLT BLADE     Pouch/Chest */
		ObjectInfoIndex(18, 38,  7, 0x0500),   /* BOLT BLADE     Pouch/Chest */
		ObjectInfoIndex(4, 35,  5, 0x0400),    /* FURY           Chest */
		ObjectInfoIndex(14, 37,  6, 0x0400),   /* FURY           Chest */
		ObjectInfoIndex(20, 11,  8, 0x0040),   /* THE FIRESTAFF  Quiver 1 */
		ObjectInfoIndex(23, 12,  9, 0x0040),   /* THE FIRESTAFF  Quiver 1 */
		ObjectInfoIndex(25, 12, 10, 0x0040),   /* THE FIRESTAFF  Quiver 1 */
		ObjectInfoIndex(27, 39, 11, 0x0040),   /* OPEN SCROLL    Quiver 1 */
		ObjectInfoIndex(32, 17, 12, 0x05C0),   /* SCROLL         Quiver 1/Quiver 2/Pouch/Chest */
		ObjectInfoIndex(33, 12, 13, 0x0040),   /* DAGGER         Quiver 1 */
		ObjectInfoIndex(34, 12, 13, 0x0040),   /* FALCHION       Quiver 1 */
		ObjectInfoIndex(35, 12, 14, 0x0040),   /* SWORD          Quiver 1 */
		ObjectInfoIndex(36, 12, 15, 0x0040),   /* RAPIER         Quiver 1 */
		ObjectInfoIndex(37, 12, 15, 0x0040),   /* SABRE          Quiver 1 */
		ObjectInfoIndex(38, 12, 16, 0x0040),   /* SAMURAI SWORD  Quiver 1 */
		ObjectInfoIndex(39, 12, 17, 0x0040),   /* DELTA          Quiver 1 */
		ObjectInfoIndex(40, 42, 18, 0x0040),   /* DIAMOND EDGE   Quiver 1 */
		ObjectInfoIndex(41, 12, 19, 0x0040),   /* VORPAL BLADE   Quiver 1 */
		ObjectInfoIndex(42, 13, 20, 0x0040),   /* THE INQUISITOR Quiver 1 */
		ObjectInfoIndex(43, 13, 21, 0x0040),   /* AXE            Quiver 1 */
		ObjectInfoIndex(44, 21, 22, 0x0040),   /* HARDCLEAVE     Quiver 1 */
		ObjectInfoIndex(45, 21, 22, 0x0040),   /* MACE           Quiver 1 */
		ObjectInfoIndex(46, 33, 23, 0x0440),   /* MACE OF ORDER  Quiver 1/Chest */
		ObjectInfoIndex(47, 43, 24, 0x0040),   /* MORNINGSTAR    Quiver 1 */
		ObjectInfoIndex(48, 44, 24, 0x0040),   /* CLUB           Quiver 1 */
		ObjectInfoIndex(49, 14, 27, 0x0040),   /* STONE CLUB     Quiver 1 */
		ObjectInfoIndex(50, 45, 27, 0x0040),   /* BOW            Quiver 1 */
		ObjectInfoIndex(51, 16, 26, 0x05C0),   /* CROSSBOW       Quiver 1/Quiver 2/Pouch/Chest */
		ObjectInfoIndex(52, 46, 26, 0x05C0),   /* ARROW          Quiver 1/Quiver 2/Pouch/Chest */
		ObjectInfoIndex(53, 11, 27, 0x0440),   /* SLAYER         Quiver 1/Chest */
		ObjectInfoIndex(54, 47, 42, 0x05C0),   /* SLING          Quiver 1/Quiver 2/Pouch/Chest */
		ObjectInfoIndex(55, 48, 40, 0x05C0),   /* ROCK           Quiver 1/Quiver 2/Pouch/Chest */
		ObjectInfoIndex(56, 49, 42, 0x05C0),   /* POISON DART    Quiver 1/Quiver 2/Pouch/Chest */
		ObjectInfoIndex(57, 50,  5, 0x0040),   /* THROWING STAR  Quiver 1 */
		ObjectInfoIndex(58, 11,  5, 0x0040),   /* STICK          Quiver 1 */
		ObjectInfoIndex(59, 31, 28, 0x0540),   /* STAFF          Quiver 1/Pouch/Chest */
		ObjectInfoIndex(60, 31, 29, 0x0540),   /* WAND           Quiver 1/Pouch/Chest */
		ObjectInfoIndex(61, 11, 30, 0x0040),   /* TEOWAND        Quiver 1 */
		ObjectInfoIndex(62, 11, 31, 0x0040),   /* YEW STAFF      Quiver 1 */
		ObjectInfoIndex(63, 11, 32, 0x0040),   /* STAFF OF MANAR Quiver 1 Atari ST Version 1.0 1987-12-08: ObjectAspectIndex = 35 */
		ObjectInfoIndex(64, 51, 33, 0x0040),   /* SNAKE STAFF    Quiver 1 */
		ObjectInfoIndex(65, 32,  5, 0x0440),   /* THE CONDUIT    Quiver 1/Chest */
		ObjectInfoIndex(66, 30, 35, 0x0040),   /* DRAGON SPIT    Quiver 1 */
		ObjectInfoIndex(135, 65, 36, 0x0440),  /* SCEPTRE OF LYF Quiver 1/Chest */
		ObjectInfoIndex(143, 45, 27, 0x0040),  /* ROBE           Quiver 1 */
		ObjectInfoIndex(28, 82,  1, 0x0040),   /* FINE ROBE      Quiver 1 */
		ObjectInfoIndex(80, 23,  0, 0x040C),   /* KIRTLE         Neck/Torso/Chest */
		ObjectInfoIndex(81, 23,  0, 0x040C),   /* SILK SHIRT     Neck/Torso/Chest */
		ObjectInfoIndex(82, 23,  0, 0x0410),   /* ELVEN DOUBLET  Legs/Chest */
		ObjectInfoIndex(112, 55,  0, 0x0420),  /* LEATHER JERKIN Feet/Chest */
		ObjectInfoIndex(114,  8,  0, 0x0420),  /* TUNIC          Feet/Chest */
		ObjectInfoIndex(67, 24,  0, 0x0408),   /* GHI            Torso/Chest */
		ObjectInfoIndex(83, 24,  0, 0x0410),   /* MAIL AKETON    Legs/Chest */
		ObjectInfoIndex(68, 24,  0, 0x0408),   /* MITHRAL AKETON Torso/Chest */
		ObjectInfoIndex(84, 24,  0, 0x0410),   /* TORSO PLATE    Legs/Chest */
		ObjectInfoIndex(69, 69,  0, 0x0408),   /* PLATE OF LYTE  Torso/Chest */
		ObjectInfoIndex(70, 24,  0, 0x0408),   /* PLATE OF DARC  Torso/Chest */
		ObjectInfoIndex(85, 24,  0, 0x0410),   /* CAPE           Legs/Chest */
		ObjectInfoIndex(86, 69,  0, 0x0410),   /* CLOAK OF NIGHT Legs/Chest */
		ObjectInfoIndex(71,  7,  0, 0x0408),   /* BARBARIAN HIDE Torso/Chest */
		ObjectInfoIndex(87,  7,  0, 0x0410),   /* ROBE           Legs/Chest */
		ObjectInfoIndex(119, 57,  0, 0x0420),  /* FINE ROBE      Feet/Chest */
		ObjectInfoIndex(72, 23,  0, 0x0408),   /* TABARD         Torso/Chest */
		ObjectInfoIndex(88, 23,  0, 0x0410),   /* GUNNA          Legs/Chest */
		ObjectInfoIndex(113, 29,  0, 0x0420),  /* ELVEN HUKE     Feet/Chest */
		ObjectInfoIndex(89, 69,  0, 0x0410),   /* LEATHER PANTS  Legs/Chest */
		ObjectInfoIndex(73, 69,  0, 0x0408),   /* BLUE PANTS     Torso/Chest */
		ObjectInfoIndex(74, 24,  0, 0x0408),   /* GHI TROUSERS   Torso/Chest */
		ObjectInfoIndex(90, 24,  0, 0x0410),   /* LEG MAIL       Legs/Chest */
		ObjectInfoIndex(103, 53,  0, 0x0402),  /* MITHRAL MAIL   Head/Chest */
		ObjectInfoIndex(104, 53,  0, 0x0402),  /* LEG PLATE      Head/Chest */
		ObjectInfoIndex(96,  9,  0, 0x0402),   /* POLEYN OF LYTE Head/Chest */
		ObjectInfoIndex(97,  9,  0, 0x0402),   /* POLEYN OF DARC Head/Chest */
		ObjectInfoIndex(98,  9,  0, 0x0402),   /* BEZERKER HELM  Head/Chest */
		ObjectInfoIndex(105, 54, 41, 0x0400),  /* HELMET         Chest */
		ObjectInfoIndex(106, 54, 41, 0x0200),  /* BASINET        Hands */
		ObjectInfoIndex(108, 10, 41, 0x0200),  /* CASQUE 'N COIF Hands */
		ObjectInfoIndex(107, 54, 41, 0x0200),  /* ARMET          Hands */
		ObjectInfoIndex(75, 19,  0, 0x0408),   /* HELM OF LYTE   Torso/Chest */
		ObjectInfoIndex(91, 19,  0, 0x0410),   /* HELM OF DARC   Legs/Chest */
		ObjectInfoIndex(76, 19,  0, 0x0408),   /* CALISTA        Torso/Chest */
		ObjectInfoIndex(92, 19,  0, 0x0410),   /* CROWN OF NERRA Legs/Chest */
		ObjectInfoIndex(99,  9,  0, 0x0402),   /* BUCKLER        Head/Chest */
		ObjectInfoIndex(115, 19,  0, 0x0420),  /* HIDE SHIELD    Feet/Chest */
		ObjectInfoIndex(100, 52,  0, 0x0402),  /* SMALL SHIELD   Head/Chest */
		ObjectInfoIndex(77, 20,  0, 0x0008),   /* WOODEN SHIELD  Torso */
		ObjectInfoIndex(93, 22,  0, 0x0010),   /* LARGE SHIELD   Legs */
		ObjectInfoIndex(116, 56,  0, 0x0420),  /* SHIELD OF LYTE Feet/Chest */
		ObjectInfoIndex(109, 10, 41, 0x0200),  /* SHIELD OF DARC Hands */
		ObjectInfoIndex(101, 52,  0, 0x0402),  /* SANDALS        Head/Chest */
		ObjectInfoIndex(78, 20,  0, 0x0008),   /* SUEDE BOOTS    Torso */
		ObjectInfoIndex(94, 22,  0, 0x0010),   /* LEATHER BOOTS  Legs */
		ObjectInfoIndex(117, 56,  0, 0x0420),  /* HOSEN          Feet/Chest */
		ObjectInfoIndex(110, 10, 41, 0x0200),  /* FOOT PLATE     Hands */
		ObjectInfoIndex(102, 52,  0, 0x0402),  /* GREAVE OF LYTE Head/Chest */
		ObjectInfoIndex(79, 20,  0, 0x0008),   /* GREAVE OF DARC Torso */
		ObjectInfoIndex(95, 22,  0, 0x0010),   /* ELVEN BOOTS    Legs */
		ObjectInfoIndex(118, 56,  0, 0x0420),  /* GEM OF AGES    Feet/Chest */
		ObjectInfoIndex(111, 10, 41, 0x0200),  /* EKKHARD CROSS  Hands */
		ObjectInfoIndex(140, 52,  0, 0x0402),  /* MOONSTONE      Head/Chest */
		ObjectInfoIndex(141, 19,  0, 0x0408),  /* THE HELLION    Torso/Chest */
		ObjectInfoIndex(142, 22,  0, 0x0010),  /* PENDANT FERAL  Legs */
		ObjectInfoIndex(194, 81,  0, 0x0420),  /* COPPER COIN    Feet/Chest */
		ObjectInfoIndex(196, 84,  0, 0x0408),  /* SILVER COIN    Torso/Chest */
		ObjectInfoIndex(0, 34,  0, 0x0500),    /* GOLD COIN      Pouch/Chest */
		ObjectInfoIndex(8,  6,  0, 0x0501),    /* BOULDER        Mouth/Pouch/Chest */
		ObjectInfoIndex(10, 15,  0, 0x0504),   /* BLUE GEM       Neck/Pouch/Chest */
		ObjectInfoIndex(12, 15,  0, 0x0504),   /* ORANGE GEM     Neck/Pouch/Chest */
		ObjectInfoIndex(146, 40,  0, 0x0500),  /* GREEN GEM      Pouch/Chest */
		ObjectInfoIndex(147, 41,  0, 0x0400),  /* MAGICAL BOX    Chest */
		ObjectInfoIndex(125,  4, 37, 0x0500),  /* MAGICAL BOX    Pouch/Chest */
		ObjectInfoIndex(126, 83, 37, 0x0500),  /* MIRROR OF DAWN Pouch/Chest */
		ObjectInfoIndex(127,  4, 37, 0x0500),  /* HORN OF FEAR   Pouch/Chest */
		ObjectInfoIndex(176, 18,  0, 0x0500),  /* ROPE           Pouch/Chest */
		ObjectInfoIndex(177, 18,  0, 0x0500),  /* RABBIT'S FOOT  Pouch/Chest */
		ObjectInfoIndex(178, 18,  0, 0x0500),  /* CORBAMITE      Pouch/Chest */
		ObjectInfoIndex(179, 18,  0, 0x0500),  /* CHOKER         Pouch/Chest */
		ObjectInfoIndex(180, 18,  0, 0x0500),  /* DEXHELM        Pouch/Chest */
		ObjectInfoIndex(181, 18,  0, 0x0500),  /* FLAMEBAIN      Pouch/Chest */
		ObjectInfoIndex(182, 18,  0, 0x0500),  /* POWERTOWERS    Pouch/Chest */
		ObjectInfoIndex(183, 18,  0, 0x0500),  /* SPEEDBOW       Pouch/Chest */
		ObjectInfoIndex(184, 62,  0, 0x0500),  /* CHEST          Pouch/Chest */
		ObjectInfoIndex(185, 62,  0, 0x0500),  /* OPEN CHEST     Pouch/Chest */
		ObjectInfoIndex(186, 62,  0, 0x0500),  /* ASHES          Pouch/Chest */
		ObjectInfoIndex(187, 62,  0, 0x0500),  /* BONES          Pouch/Chest */
		ObjectInfoIndex(188, 62,  0, 0x0500),  /* MON POTION     Pouch/Chest */
		ObjectInfoIndex(189, 62,  0, 0x0500),  /* UM POTION      Pouch/Chest */
		ObjectInfoIndex(190, 62,  0, 0x0500),  /* DES POTION     Pouch/Chest */
		ObjectInfoIndex(191, 62,  0, 0x0500),  /* VEN POTION     Pouch/Chest */
		ObjectInfoIndex(128, 76,  0, 0x0200),  /* SAR POTION     Hands */
		ObjectInfoIndex(129,  3,  0, 0x0500),  /* ZO POTION      Pouch/Chest */
		ObjectInfoIndex(130, 60,  0, 0x0500),  /* ROS POTION     Pouch/Chest */
		ObjectInfoIndex(131, 61,  0, 0x0500),  /* KU POTION      Pouch/Chest */
		ObjectInfoIndex(168, 27,  0, 0x0501),  /* DANE POTION    Mouth/Pouch/Chest */
		ObjectInfoIndex(169, 28,  0, 0x0501),  /* NETA POTION    Mouth/Pouch/Chest */
		ObjectInfoIndex(170, 25,  0, 0x0501),  /* BRO POTION     Mouth/Pouch/Chest */
		ObjectInfoIndex(171, 26,  0, 0x0501),  /* MA POTION      Mouth/Pouch/Chest */
		ObjectInfoIndex(172, 71,  0, 0x0401),  /* YA POTION      Mouth/Chest */
		ObjectInfoIndex(173, 70,  0, 0x0401),  /* EE POTION      Mouth/Chest */
		ObjectInfoIndex(174,  5,  0, 0x0501),  /* VI POTION      Mouth/Pouch/Chest */
		ObjectInfoIndex(175, 66,  0, 0x0501),  /* WATER FLASK    Mouth/Pouch/Chest */
		ObjectInfoIndex(120, 15,  0, 0x0504),  /* KATH BOMB      Neck/Pouch/Chest */
		ObjectInfoIndex(121, 15,  0, 0x0504),  /* PEW BOMB       Neck/Pouch/Chest */
		ObjectInfoIndex(122, 58,  0, 0x0504),  /* RA BOMB        Neck/Pouch/Chest */
		ObjectInfoIndex(123, 59,  0, 0x0504),  /* FUL BOMB       Neck/Pouch/Chest */
		ObjectInfoIndex(124, 59,  0, 0x0504),  /* APPLE          Neck/Pouch/Chest */
		ObjectInfoIndex(132, 79, 38, 0x0500),  /* CORN           Pouch/Chest */
		ObjectInfoIndex(133, 63, 38, 0x0500),  /* BREAD          Pouch/Chest */
		ObjectInfoIndex(134, 64,  0, 0x0500),  /* CHEESE         Pouch/Chest */
		ObjectInfoIndex(136, 72, 39, 0x0400),  /* SCREAMER SLICE Chest */
		ObjectInfoIndex(137, 73,  0, 0x0500),  /* WORM ROUND     Pouch/Chest */
		ObjectInfoIndex(138, 74,  0, 0x0500),  /* DRUMSTICK      Pouch/Chest */
		ObjectInfoIndex(139, 75,  0, 0x0504),  /* DRAGON STEAK   Neck/Pouch/Chest */
		ObjectInfoIndex(192, 77,  0, 0x0500),  /* IRON KEY       Pouch/Chest */
		ObjectInfoIndex(193, 78,  0, 0x0500),  /* KEY OF B       Pouch/Chest */
		ObjectInfoIndex(197, 74,  0, 0x0000),  /* SOLID KEY */
		ObjectInfoIndex(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;

	_dungeonFileHeader._ornamentRandomSeed = 0;
	_dungeonFileHeader._rawMapDataSize = 0;
	_dungeonFileHeader._mapCount = 0;
	_dungeonFileHeader._textDataWordCount = 0;
	_dungeonFileHeader._partyStartLocation = 0;
	_dungeonFileHeader._squareFirstThingCount = 0;

	for (uint16 i = 0; i < 5; ++i)
		_pileTopObject[i] = Thing(0);
	for (uint16 i = 0; i < 2; ++i)
		_currMapDoorInfo[i].resetToZero();
	for (uint16 i = 0; i < 16; i++)
		_dungeonFileHeader._thingCounts[i] = 0;

	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;
	if (_vm->isDemo())
		f.open("DemoDun.dat");
	else
		f.open("Dungeon.dat");

	if (!f.isOpen())
		error("Unable to open Dungeon.dat file");

	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();
}

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 */
	};

	Timeline &timeline = *_vm->_timeline;
	if (_vm->_gameMode != kDMModeLoadSavedGame)
		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->_gameMode != kDMModeLoadSavedGame) {
		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);

		//if (!file)
		//	delete dunDataStream;
	}

	// 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->_gameMode != kDMModeLoadSavedGame)
		_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->_gameMode != kDMModeLoadSavedGame) {
		for (uint16 i = 0; i < 300; ++i)
			_squareFirstThings[actualSquareFirstThingCount + i] = _vm->_thingNone;
	}

	// 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->_gameMode != kDMModeLoadSavedGame)
		timeline._eventMaxCount = 100;

	// load things
	for (uint16 thingType = kDMThingTypeDoor; thingType < kDMThingTypeTotal; ++thingType) {
		uint16 thingCount = _dungeonFileHeader._thingCounts[thingType];
		if (_vm->_gameMode != kDMModeLoadSavedGame)
			_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->_gameMode != kDMModeLoadSavedGame) {
			if ((thingType == kDMThingTypeGroup) || thingType >= kDMThingTypeProjectile)
				timeline._eventMaxCount += _dungeonFileHeader._thingCounts[thingType];

			for (uint16 i = 0; i < additionalThingCounts[thingType]; ++i)
				(_thingData[thingType] + (thingCount + i) * thingStoreWordCount)[0] = _vm->_thingNone.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 dunDataStream;
	} // 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) {
	DisplayMan &displMan = *_vm->_displayMan;

	setCurrentMap(_partyMapIndex = mapIndex);
	byte *metaMapData = _currMapData[_currMapWidth - 1] + _currMapHeight;

	displMan._currMapAllowedCreatureTypes = metaMapData;
	metaMapData += _currMap->_creatureTypeCount;

	memcpy(displMan._currMapWallOrnIndices, metaMapData, _currMap->_wallOrnCount);
	metaMapData += _currMap->_wallOrnCount;

	memcpy(displMan._currMapFloorOrnIndices, metaMapData, _currMap->_floorOrnCount);
	metaMapData += _currMap->_floorOrnCount;

	memcpy(displMan._currMapDoorOrnIndices, metaMapData, _currMap->_doorOrnCount);

	_currMapInscriptionWallOrnIndex = _currMap->_wallOrnCount;
	displMan._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) {
		ElementType squareType = Square(_currMapData[0][mapY]).getType();
		if (((mapX == -1) && (squareType == kDMElementTypeCorridor)) || (squareType == kDMElementTypePit))
			return Square(kDMElementTypeWall, kDMSquareMaskWallEastRandOrnament);

		squareType = Square(_currMapData[_currMapWidth - 1][mapY]).getType();
		if (((mapX == _currMapWidth) && (squareType == kDMElementTypeCorridor)) || (squareType == kDMElementTypePit))
			return Square(kDMElementTypeWall, kDMSquareMaskWallWestRandOrnament);
	} else if (isMapXInBounds) {
		ElementType squareType = Square(_currMapData[mapX][0]).getType();
		if (((mapY == -1) && (squareType == kDMElementTypeCorridor)) || (squareType == kDMElementTypePit))
			return Square(kDMElementTypeWall, kDMSquareMaslWallSouthRandOrnament);

		squareType = Square(_currMapData[mapX][_currMapHeight - 1]).getType();
		if (((mapY == _currMapHeight) && (squareType == kDMElementTypeCorridor)) || (squareType == kDMElementTypePit))
			return Square(kDMElementTypeWall, kDMSquareMaskWallNorthRandOrnament);
	}
	return Square(kDMElementTypeWall, 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], kDMSquareMaskThingListPresent))
		return -1;

	int16 curMapY = 0;
	uint16 thingIndex = _currMapColCumulativeSquareFirstThingCount[mapX];
	while (curMapY++ != mapY) {
		if (getFlag(*curSquare++, kDMSquareMaskThingListPresent))
			thingIndex++;
	}
	return thingIndex;
}

Thing DungeonMan::getSquareFirstThing(int16 mapX, int16 mapY) {
	int16 index = getSquareFirstThingIndex(mapX, mapY);
	if (index == -1)
		return _vm->_thingEndOfList;
	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

	DisplayMan &displMan = *_vm->_displayMan;
	ChampionMan &championMan = *_vm->_championMan;

	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[kDMSquareAspectElement] = Square(AL0307_uc_Square).getType();
	switch (aspectArray[kDMSquareAspectElement]) {
	case kDMElementTypeWall:
		switch (dir) {
		case kDMDirNorth:
			leftRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, kDMSquareMaskWallEastRandOrnament);
			frontRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, kDMSquareMaslWallSouthRandOrnament);
			rightRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, kDMSquareMaskWallWestRandOrnament);
			break;
		case kDMDirEast:
			leftRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, kDMSquareMaslWallSouthRandOrnament);
			frontRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, kDMSquareMaskWallWestRandOrnament);
			rightRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, kDMSquareMaskWallNorthRandOrnament);
			break;
		case kDMDirSouth:
			leftRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, kDMSquareMaskWallWestRandOrnament);
			frontRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, kDMSquareMaskWallNorthRandOrnament);
			rightRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, kDMSquareMaskWallEastRandOrnament);
			break;
		case kDMDirWest:
			leftRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, kDMSquareMaskWallNorthRandOrnament);
			frontRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, kDMSquareMaskWallEastRandOrnament);
			rightRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, kDMSquareMaslWallSouthRandOrnament);
			break;
		default:
			assert(false);
		}
		displMan._championPortraitOrdinal = 0;
		squareIsFakeWall = false;
T0172010_ClosedFakeWall:
		setSquareAspectOrnOrdinals(aspectArray, leftRandomWallOrnamentAllowed, frontRandomWallOrnamentAllowed, rightRandomWallOrnamentAllowed, dir, mapX, mapY, squareIsFakeWall);
		while ((curThing != _vm->_thingEndOfList) && (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;
						displMan._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() == kDMSensorWallChampionPortrait) {
						displMan._championPortraitOrdinal = _vm->indexToOrdinal(curSensor->getData());
					}
				}
			}
			curThing = getNextThing(curThing);
		}
		if (squareIsFakeWall && (_partyMapX != mapX) && (_partyMapY != mapY)) {
			aspectArray[kDMSquareAspectFirstGroupOrObject] = _vm->_thingEndOfList.toUint16();
			return;
		}
		break;
	case kDMElementTypeFakeWall:
		if (!getFlag(AL0307_uc_Square, kDMSquareMaskFakeWallOpen)) {
			aspectArray[kDMSquareAspectElement] = kDMElementTypeWall;
			leftRandomWallOrnamentAllowed = rightRandomWallOrnamentAllowed = frontRandomWallOrnamentAllowed = getFlag(AL0307_uc_Square, kDMSquareMaskFakeWallRandOrnamentOrFootprintsAllowed);
			squareIsFakeWall = true;
			goto T0172010_ClosedFakeWall;
		}
		aspectArray[kDMSquareAspectElement] = kDMElementTypeCorridor;
		AL0307_uc_FootprintsAllowed = getFlag(AL0307_uc_Square, kDMSquareMaskFakeWallRandOrnamentOrFootprintsAllowed) ? 8 : 0;
		// fall through
	case kDMElementTypeCorridor:
	case kDMElementTypePit:
	case kDMElementTypeTeleporter:
		if (aspectArray[kDMSquareAspectElement] == kDMElementTypeCorridor) {
			aspectArray[kDMSquareAspectFloorOrn] = getRandomOrnOrdinal(getFlag(AL0307_uc_Square, kDMSquareMaskCorridorRandOrnament), _currMap->_randFloorOrnCount, mapX, mapY, 30);
			AL0307_uc_FootprintsAllowed = true;
		} else if (aspectArray[kDMSquareAspectElement] == kDMElementTypePit) {
			if (getFlag(AL0307_uc_Square, kDMSquareMaskPitOpen)) {
				aspectArray[kDMSquareAspectPitInvisible] = getFlag(AL0307_uc_Square, kDMSquareMaskPitInvisible);
				AL0307_uc_FootprintsAllowed &= 0x0001;
			} else {
				aspectArray[kDMSquareAspectElement] = kDMElementTypeCorridor;
				AL0307_uc_FootprintsAllowed = true;
			}
		} else { // k5_ElementTypeTeleporter
			aspectArray[kDMSquareAspectTeleporterVisible] = getFlag(AL0307_uc_Square, kDMSquareMaskTeleporterOpen) && getFlag(AL0307_uc_Square, kDMSquareMaskTeleporterVisible);
			AL0307_uc_FootprintsAllowed = true;
		}

		while ((curThing != _vm->_thingEndOfList) && (curThing.getType() <= kDMThingTypeSensor)) {
			if (curThing.getType() == kDMThingTypeSensor) {
				Sensor *curSensor = (Sensor *)getThingData(curThing);
				aspectArray[kDMSquareAspectFloorOrn] = curSensor->getAttrOrnOrdinal();
			}
			curThing = getNextThing(curThing);
		}

		AL0307_uc_ScentOrdinal = championMan.getScentOrdinal(mapX, mapY);
		if (AL0307_uc_FootprintsAllowed && (AL0307_uc_ScentOrdinal) && (--AL0307_uc_ScentOrdinal >= championMan._party._firstScentIndex) && (AL0307_uc_ScentOrdinal < championMan._party._lastScentIndex))
			setFlag(aspectArray[kDMSquareAspectFloorOrn], kDMMaskFootprints);

		break;
	case kDMElementTypeStairs:
		aspectArray[kDMSquareAspectElement] = (bool((getFlag(AL0307_uc_Square, kDMSquareMaskStairsNorthSouth) >> 3)) == _vm->isOrientedWestEast(dir)) ? kDMElementTypeStairsSide : kDMElementTypeStairsFront;
		aspectArray[kDMSquareAspectStairsUp] = getFlag(AL0307_uc_Square, kDMSquareMaskStairsUp);
		AL0307_uc_FootprintsAllowed = false;
		while ((curThing != _vm->_thingEndOfList) && (curThing.getType() <= kDMThingTypeSensor))
			curThing = getNextThing(curThing);
		break;
	case kDMElementTypeDoor:
		if (bool((getFlag(AL0307_uc_Square, (byte) kDMSquareMaskDoorNorthSouth) >> 3)) == _vm->isOrientedWestEast(dir)) {
			aspectArray[kDMSquareAspectElement] = kDMElementTypeDoorSide;
		} else {
			aspectArray[kDMSquareAspectElement] = kDMElementTypeDoorFront;
			aspectArray[kDMSquareAspectDoorState] = Square(AL0307_uc_Square).getDoorState();
			aspectArray[kDMSquareAspectDoorThingIndex] = getSquareFirstThing(mapX, mapY).getIndex();
		}
		AL0307_uc_FootprintsAllowed = true;

		while ((curThing != _vm->_thingEndOfList) && (curThing.getType() <= kDMThingTypeSensor))
			curThing = getNextThing(curThing);

		AL0307_uc_ScentOrdinal = championMan.getScentOrdinal(mapX, mapY);
		if (AL0307_uc_FootprintsAllowed && (AL0307_uc_ScentOrdinal) && (--AL0307_uc_ScentOrdinal >= championMan._party._firstScentIndex) && (AL0307_uc_ScentOrdinal < championMan._party._lastScentIndex))
			setFlag(aspectArray[kDMSquareAspectFloorOrn], kDMMaskFootprints);
		break;
	}
	aspectArray[kDMSquareAspectFirstGroupOrObject] = 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[kDMSquareAspectRightWallOrnOrd] = getRandomOrnOrdinal(leftAllowed, randomWallOrnamentCount, mapX, ++mapY * (_vm->normalizeModulo4(++dir) + 1), 30);
	aspectArray[kDMSquareFrontWallOrnOrd] = getRandomOrnOrdinal(frontAllowed, randomWallOrnamentCount, mapX, mapY * (_vm->normalizeModulo4(++dir) + 1), 30);
	aspectArray[kDMSquareAspectLeftWallOrnOrd] = 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 = kDMSquareAspectRightWallOrnOrd; sideIndex <= kDMSquareAspectLeftWallOrnOrd; 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) {
		DisplayMan &displMan = *_vm->_displayMan;
		for (uint16 i = 0; i < k3_AlcoveOrnCount; ++i) {
			if (displMan._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 & kDMMaskDecodeEvenIfInvisible)) {
		type = (TextType)(type & ~kDMMaskDecodeEvenIfInvisible);
		char sepChar;
		if (type == kDMTextTypeMessage) {
			*destString++ = '\n';
			sepChar = ' ';
		} else if (type == kDMTextTypeInscription) {
			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 != kDMTextTypeInscription)
						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 != kDMTextTypeInscription) {
					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 == kDMTextTypeInscription) ? 0x81 : '\0');
}

Thing DungeonMan::getUnusedThing(uint16 thingType) {
	int16 thingCount = _dungeonFileHeader._thingCounts[getFlag(thingType, kDMMaskThingType)];
	if (thingType == (kDMMaskChampionBones | 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 == _vm->_thingNone) { /* 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 == _vm->_thingNone)
				return _vm->_thingNone;

			thingPtr = (Thing *)getThingData(curThing);
			break;
		}
	}
	memset(thingPtr, 0, thingDataByteCount * 2);

	*thingPtr = _vm->_thingEndOfList;
	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 == _vm->_thingNone)
		return 0;

	// Initialization is not present in original
	// Set to 0 by default as it's the default value used for _vm->_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() == kDMJunkTypeWaterskin)
			weight += junk->getChargeCount() << 1;

		break;
	case kDMThingTypeContainer:
		weight = 50;
		thing = ((Container *)junk)->getSlot();
		while (thing != _vm->_thingEndOfList) {
			weight += getObjectWeight(thing);
			thing = getNextThing(thing);
		}
		break;
	case kDMThingTypePotion:
		if (((Potion *)junk)->getType() == kDMPotionTypeEmptyFlask)
			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 kDMObjectInfoIndexFirstScroll;
	case kDMThingTypeContainer:
		return kDMObjectInfoIndexFirstContainer + Container(rawType).getType();
	case kDMThingTypeJunk:
		return kDMObjectInfoIndexFirstJunk + Junk(rawType).getType();
	case kDMThingTypeWeapon:
		return kDMObjectInfoIndexFirstWeapon + Weapon(rawType).getType();
	case kDMThingTypeArmour:
		return kDMObjectInfoIndexFirstArmour + Armour(rawType).getType();
	case kDMThingTypePotion:
		return kDMObjectInfoIndexFirstPotion + Potion(rawType).getType();
	default:
		return -1;
	}
}

void DungeonMan::linkThingToList(Thing thingToLink, Thing thingInList, int16 mapX, int16 mapY) {
	if (thingToLink == _vm->_thingEndOfList)
		return;

	Thing *thingPtr = (Thing *)getThingData(thingToLink);
	*thingPtr = _vm->_thingEndOfList;
	/* 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, kDMSquareMaskThingListPresent)) {
			thingInList = getSquareFirstThing(mapX, mapY);
		} else {
			setFlag(*currSquare, kDMSquareMaskThingListPresent);
			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++, kDMSquareMaskThingListPresent))
					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 != _vm->_thingEndOfList)
		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 == _vm->_thingExplFireBall)
			return -_vm->indexToOrdinal(k10_ProjectileAspectExplosionFireBall);
		if (thing == _vm->_thingExplSlime)
			return -_vm->indexToOrdinal(k12_ProjectileAspectExplosionSlime);
		if (thing == _vm->_thingExplLightningBolt)
			return -_vm->indexToOrdinal(k3_ProjectileAspectExplosionLightningBolt);
		if ((thing == _vm->_thingExplPoisonBolt) || (thing == _vm->_thingExplPoisonCloud))
			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 != _vm->_thingEndOfList) && (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, kDMArmourAttributeSharpDefense) + 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 _vm->_thingNone;

	GroupMan &groupMan = *_vm->_groupMan;
	ProjExpl &projExpl = *_vm->_projexpl;

	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++, kDMSquareMaskThingListPresent)) {
					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) {
									groupMan.dropGroupPossessions(currMapX, currMapY, squareThing, kDMSoundModeDoNotPlaySound);
									groupMan.groupDelete(currMapX, currMapY);
								} else {
									projExpl.projectileDeleteEvent(squareThing);
									unlinkThingFromList(squareThing, Thing(0), currMapX, currMapY);
									projExpl.projectileDelete(squareThing, 0, currMapX, currMapY);
								}
								break;
							case kDMThingTypeArmour:
								if (((Armour *)squareThingData)->getDoNotDiscard())
									continue;

								setCurrentMap(mapIndex);
								_vm->_moveSens->getMoveResult(squareThing, currMapX, currMapY, kDMMapXNotOnASquare, 0);
								break;
							case kDMThingTypeWeapon:
								if (((Weapon *)squareThingData)->getDoNotDiscard())
									continue;

								setCurrentMap(mapIndex);
								_vm->_moveSens->getMoveResult(squareThing, currMapX, currMapY, kDMMapXNotOnASquare, 0);
								break;
							case kDMThingTypeJunk:
								if (((Junk *)squareThingData)->getDoNotDiscard())
									continue;

								setCurrentMap(mapIndex);
								_vm->_moveSens->getMoveResult(squareThing, currMapX, currMapY, kDMMapXNotOnASquare, 0);
								break;
							case kDMThingTypePotion:
								if (((Potion *)squareThingData)->getDoNotDiscard())
									continue;

								setCurrentMap(mapIndex);
								_vm->_moveSens->getMoveResult(squareThing, currMapX, currMapY, kDMMapXNotOnASquare, 0);
								break;
							}
							setCurrentMap(currentMapIdx);
							lastDiscardedThingMapIndex[thingType] = mapIndex;
							return Thing(squareThing.getTypeAndIndex());
						}
					} while ((squareThing = getNextThing(squareThing)) != _vm->_thingEndOfList);
				}
			}
		}
		if ((mapIndex == _partyMapIndex) || (_dungeonFileHeader._mapCount <= 1)) {
			lastDiscardedThingMapIndex[thingType] = mapIndex;
			return _vm->_thingNone;
		}

		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) {
	CreatureType 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 == _vm->_thingEndOfList)
		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 == _vm->_thingEndOfList) && (((Thing *)currThing)->getTypeAndIndex() == thingToUnlink.toUint16())) { /* If the thing to unlink is the last thing on the square */
			clearFlag(_currMapData[mapX][mapY], kDMSquareMaskThingListPresent);
			uint16 squareFirstThingIdx = _dungeonFileHeader._squareFirstThingCount - 1;
			for (uint16 i = 0; i < squareFirstThingIdx - firstThingIndex; ++i)
				currThing[i] = currThing[i + 1];

			_squareFirstThings[squareFirstThingIdx] = _vm->_thingNone;
			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 = _vm->_thingEndOfList;
			return;
		}
		if (((Thing *)currThing)->getTypeAndIndex() == thingToUnlink.toUint16()) {
			*currThing = *thingPtr;
			*thingPtr = _vm->_thingEndOfList;
			return;
		}
		thingInList = *currThing;
	}

	Thing currThing = getNextThing(thingInList);
	while (currThing.getTypeAndIndex() != thingToUnlink.toUint16()) {
		if ((currThing == _vm->_thingEndOfList) || (currThing == _vm->_thingNone)) {
			if (thingPtr)
				*thingPtr = _vm->_thingEndOfList;
			return;
		}
		currThing = getNextThing(thingInList = currThing);
	}
	thingPtr = (Thing *)getThingData(thingInList);
	*thingPtr = getNextThing(currThing);
	thingPtr = (Thing *)getThingData(thingToUnlink);
	*thingPtr = _vm->_thingEndOfList;
}

int16 DungeonMan::getStairsExitDirection(int16 mapX, int16 mapY) {
	bool northSouthOrientedStairs = !getFlag(getSquare(mapX, mapY).toByte(), kDMSquareMaskStairsNorthSouth);

	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 == kDMElementTypeWall) || (squareType == kDMElementTypeStairs)) ? 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 = kDMWeaponRock;
		break;
	case kDMIconIndiceJunkBoulder:
		junkType = kDMJunkTypeBoulder;
		thingType = kDMThingTypeJunk;
		break;
	case kDMIconIndiceWeaponArrow:
		junkType = kDMWeaponArrow;
		break;
	case kDMIconIndiceWeaponSlayer:
		junkType = kDMWeaponSlayer;
		break;
	case kDMIconIndiceWeaponPoisonDart:
		junkType = kDMWeaponPoisonDart;
		break;
	case kDMIconIndiceWeaponThrowingStar:
		junkType = kDMWeaponThrowingStar;
		break;
	case kDMIconIndiceWeaponDagger:
		junkType = kDMWeaponDagger;
		break;
	case kDMIconIndiceWeaponTorchUnlit:
		junkType = kDMWeaponTorch;
		break;
	default:
		return _vm->_thingNone;
	}

	Thing unusedThing = getUnusedThing(thingType);
	if (unusedThing == _vm->_thingNone)
		return _vm->_thingNone;

	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 */
}

}
