#include "stdafx.h"
#include "DK2ScenarioReader.h"
//////////////////////////////////////////////////////////////////////////
#define CHECK_BIT_ON(flags, bitIndex) (((flags) & (1U 0)
//////////////////////////////////////////////////////////////////////////
#define DIVIDER_FLOAT 4096.0f
#define DIVIDER_DOUBLE 65536.0f
//////////////////////////////////////////////////////////////////////////
#define READ_FROM_FSTREAM(filestream, data) \
if (!filestream.read(reinterpret_cast(&data), sizeof(data))) \
{ \
return false; \
}
#define READ_FSTREAM_DATATYPE(filestream, destination, dataType) \
{ \
dataType _dataType; \
if (!filestream.read(reinterpret_cast(&_dataType), sizeof(dataType))) \
{ \
return false; \
} \
destination = static_cast(_dataType); \
}
#define SKIP_FSTREAM_BYTES(filestream, numBytes) \
{ \
if (!filestream.seekg(numBytes, std::ios::cur)) \
{ \
return false; \
} \
}
#define SKIP_FSTREAM_DATATYPE(filestream, dataType) SKIP_FSTREAM_BYTES(filestream, sizeof(dataType))
#define SKIP_FSTREAM_U8(filestream) SKIP_FSTREAM_DATATYPE(filestream, uint8_t)
#define SKIP_FSTREAM_U16(filestream) SKIP_FSTREAM_DATATYPE(filestream, uint16_t)
#define SKIP_FSTREAM_U32(filestream) SKIP_FSTREAM_DATATYPE(filestream, uint32_t)
#define READ_FSTREAM_U8(filestream, destination) READ_FSTREAM_DATATYPE(filestream, destination, uint8_t)
#define READ_FSTREAM_U16(filestream, destination) READ_FSTREAM_DATATYPE(filestream, destination, uint16_t)
#define READ_FSTREAM_U32(filestream, destination) READ_FSTREAM_DATATYPE(filestream, destination, uint32_t)
#define READ_FSTREAM_I8(filestream, destination) READ_FSTREAM_DATATYPE(filestream, destination, int8_t)
#define READ_FSTREAM_I16(filestream, destination) READ_FSTREAM_DATATYPE(filestream, destination, int16_t);
#define READ_FSTREAM_I32(filestream, destination) READ_FSTREAM_DATATYPE(filestream, destination, int32_t);
//////////////////////////////////////////////////////////////////////////
enum
{
DKLD_GLOBALS = 0,
DKLD_MAP = 100,
DKLD_TERRAIN = 110,
DKLD_ROOMS = 120,
DKLD_TRAPS = 130,
DKLD_DOORS = 140,
DKLD_KEEPER_SPELLS = 150,
DKLD_CREATURE_SPELLS = 160,
DKLD_CREATURES = 170,
DKLD_PLAYERS = 180,
DKLD_THINGS = 190,
DKLD_TRIGGERS = 210,
DKLD_LEVEL = 220,
DKLD_VARIABLES = 230,
DKLD_OBJECTS = 240,
DKLD_EFFECT_ELEMENTS = 250,
DKLD_SHOTS = 260,
DKLD_EFFECTS = 270
};
//////////////////////////////////////////////////////////////////////////
enum
{
DK_OBJECT_THING = 194,
DK_TRAP_THING = 195,
DK_DOOR_THING = 196,
DK_ACTIONPOINT_THING = 197,
DK_NEUTRAL_CREATURE_THING = 198,
DK_GOOD_CREATURE_THING = 199,
DK_CREATURE_THING = 200,
DK_HEROPARTY_THING = 201,
DK_DEAD_BODY_THING = 202,
DK_EFFECT_GENERATOR_THING = 203,
DK_ROOM_THING = 204,
DK_CAMERA_THING = 205,
};
//////////////////////////////////////////////////////////////////////////
// ArtResource flags
enum
{
ARTRESF_PLAYER_COLOURED = (0x000002UL),
ARTRESF_ANIMATING_TEXTURE = (0x000004UL),
ARTRESF_HAS_START_ANIMATION = (0x000008UL),
ARTRESF_HAS_END_ANIMATION = (0x000010UL),
ARTRESF_RANDOM_START_FRAME = (0x000020UL),
ARTRESF_ORIGIN_AT_BOTTOM = (0x000040UL),
ARTRESF_DOESNT_LOOP = (0x000080UL),
ARTRESF_FLAT = (0x000100UL),
ARTRESF_DOESNT_USE_PROGRESSIVE_MESH = (0x000200UL),
ARTRESF_USE_ANIMATING_TEXTURE_FOR_SELECTION = (0x010000UL),
ARTRESF_PRELOAD = (0x020000UL),
ARTRESF_BLOOD = (0x040000UL)
};
//////////////////////////////////////////////////////////////////////////
enum
{
TERRAINF_SOLID = (0x00000001UL),
TERRAINF_IMPENETRABLE = (0x00000002UL),
TERRAINF_OWNABLE = (0x00000004UL),
TERRAINF_TAGGABLE = (0x00000008UL),
TERRAINF_ATTACKABLE = (0x00000020UL),
TERRAINF_TORCH = (0x00000040UL), // has torch?
TERRAINF_WATER = (0x00000080UL),
TERRAINF_LAVA = (0x00000100UL),
TERRAINF_ALWAYS_EXPLORED = (0x00000200UL), // doesnt used
TERRAINF_PLAYER_COLOURED_PATH = (0x00000400UL),
TERRAINF_PLAYER_COLOURED_WALL = (0x00000800UL),
TERRAINF_CONSTRUCTION_TYPE_WATER = (0x00001000UL),
TERRAINF_CONSTRUCTION_TYPE_QUAD = (0x00002000UL),
TERRAINF_UNEXPLORE_IF_DUG_BY_ANOTHER_PLAYER = (0x00004000UL), // doesnt used
TERRAINF_FILL_INABLE = (0x00008000UL),
TERRAINF_ALLOW_ROOM_WALLS = (0x00010000UL),
TERRAINF_DECAY = (0x00020000UL),
TERRAINF_RANDOM_TEXTURE = (0x00040000UL),
TERRAINF_TERRAIN_COLOR_R = (0x00080000UL), // expands R channel of lightColor
TERRAINF_TERRAIN_COLOR_G = (0x00100000UL), // expands G channel of lightColor
TERRAINF_TERRAIN_COLOR_B = (0x00200000UL), // expands B channel of lightColor
TERRAINF_DWARF_CAN_DIG_THROUGH = (0x00800000UL),
TERRAINF_REVEAL_THROUGH_FOG_OF_WAR = (0x01000000UL),
TERRAINF_AMBIENT_COLOR_R = (0x02000000UL), // expands R channel of ambientLight
TERRAINF_AMBIENT_COLOR_G = (0x04000000UL), // expands G channel of ambientLight
TERRAINF_AMBIENT_COLOR_B = (0x08000000UL), // expands B channel of ambientLight
TERRAINF_LIGHT = (0x10000000UL),
TERRAINF_AMBIENT_LIGHT = (0x20000000UL),
};
//////////////////////////////////////////////////////////////////////////
enum
{
ROOMF_PLACEABLE_ON_WATER = (0x0001UL),
ROOMF_PLACEABLE_ON_LAVA = (0x0002UL),
ROOMF_PLACEABLE_ON_LAND = (0x0004UL),
ROOMF_HAS_WALLS = (0x0008UL),
ROOMF_CENTRE = (0x0010UL), // Placement
ROOMF_SPECIAL_TILES = (0x0020UL), // Placement
ROOMF_NORMAL_TILES = (0x0040UL), // Placement
ROOMF_BUILDABLE = (0x0080UL),
ROOMF_SPECIAL_WALLS = (0x0100UL), // Placement
ROOMF_ATTACKABLE = (0x0200UL),
ROOMF_UNK_0x0400 = (0x0400UL),
ROOMF_UNK_0x0800 = (0x0800UL),
ROOMF_HAS_FLAME = (0x1000UL),
ROOMF_IS_GOOD = (0x2000UL)
};
//////////////////////////////////////////////////////////////////////////
enum
{
OBJECTF_DIE_OVER_TIME = (0x0000001UL),
OBJECTF_DIE_OVER_TIME_IF_NOT_IN_ROOM = (0x0000002UL),
OBJECTF_TYPE_SPECIAL = (0x0000004UL),
OBJECTF_TYPE_SPELL_BOOK = (0x0000008UL),
OBJECTF_TYPE_CRATE = (0x0000010UL),
OBJECTF_TYPE_LAIR = (0x0000020UL),
OBJECTF_TYPE_GOLD = (0x0000040UL),
OBJECTF_TYPE_FOOD = (0x0000080UL),
OBJECTF_CAN_BE_PICKED_UP = (0x0000100UL),
OBJECTF_CAN_BE_SLAPPED = (0x0000200UL),
OBJECTF_DIE_WHEN_SLAPPED = (0x0000400UL),
OBJECTF_TYPE_LEVEL_GEM = (0x0001000UL),
OBJECTF_CAN_BE_DROPPED_ON_ANY_LAND = (0x0002000UL),
OBJECTF_OBSTACLE = (0x0004000UL),
OBJECTF_BOUNCE = (0x0008000UL),
OBJECTF_BOULDER_CAN_ROLL_THROUGH = (0x0010000UL),
OBJECTF_BOULDER_DESTROYS = (0x0020000UL),
OBJECTF_PILLAR = (0x0040000UL),
OBJECTF_DOOR_KEY = (0x0100000UL),
OBJECTF_DAMAGEABLE = (0x0200000UL),
OBJECTF_HIGHLIGHTABLE = (0x0400000UL),
OBJECTF_PLACEABLE = (0x0800000UL),
OBJECTF_FIRST_PERSON_OBSTACLE = (0x1000000UL),
OBJECTF_SOLID_OBSTACLE = (0x2000000UL),
OBJECTF_CAST_SHADOWS = (0x4000000UL)
};
//////////////////////////////////////////////////////////////////////////
using ScenarioVariableType = unsigned int;
// well known variable types
enum : ScenarioVariableType
{
ScenarioVariableType_Null = 0,
ScenarioVariableType_CreaturePool = 1,
ScenarioVariableType_Availability = 2,
ScenarioVariableType_EntranceGenerationSpeedSeconds = 3,
ScenarioVariableType_ClaimTileHealth = 4, // value=825
ScenarioVariableType_AttackTileHealth = 5,
ScenarioVariableType_RepairTileHealth = 6,
ScenarioVariableType_MineGoldHealth = 7,
ScenarioVariableType_DigRockHealth = 8,
ScenarioVariableType_DigOwnWallHealth = 9,
ScenarioVariableType_DigEnemyWallHealth = 10,
ScenarioVariableType_FillInHealth = 11,
ScenarioVariableType_ReinforceWallHealth = 12,
ScenarioVariableType_RepairWallHealth = 13,
ScenarioVariableType_ConvertRoomHealth = 14,
ScenarioVariableType_AttackRoomHealth = 15,
ScenarioVariableType_RepairRoomHealth = 16,
// 17
ScenarioVariableType_ChickenGenerationTimePerHatchery = 18,
// 19
ScenarioVariableType_GoldMinedFromGems = 20,
ScenarioVariableType_TimeBetweenReattemptingHungerSeconds = 21,
ScenarioVariableType_TimeBetweenReattemptingSleepSeconds = 22,
ScenarioVariableType_DeadBodyDiesAfterSeconds = 23,
ScenarioVariableType_MaxGoldPerTreasuryTile = 24,
ScenarioVariableType_DecomposeValueNeededForVampire = 25,
ScenarioVariableType_CreatureDyingStateDurationSeconds = 26,
ScenarioVariableType_SpecialIncreaseGoldAmount = 27,
ScenarioVariableType_SpecialIncreaseManaAmount = 28,
// 29
ScenarioVariableType_ImpIdleDelayBeforeReevaluationSeconds = 30,
ScenarioVariableType_DelayBeforePayReevaluationSeconds = 31,
ScenarioVariableType_MaxGoldPileOutsideTreasury = 32,
ScenarioVariableType_PayDayFrequencySeconds = 33,
ScenarioVariableType_MotionlessWhileResearchingSeconds = 34,
ScenarioVariableType_MotionlessWhileManufacturingSeconds = 35,
ScenarioVariableType_StateCounterDelayBeforeReattemptEating = 36,
ScenarioVariableType_StateCounterWhileGuarding = 37,
ScenarioVariableType_MotionlessWhilePrayingSeconds = 38,
ScenarioVariableType_MotionlessInPrisonSeconds = 39,
ScenarioVariableType_MotionlessWhileTrainingSeconds = 40,
ScenarioVariableType_MotionlessInCasinoSeconds = 41,
ScenarioVariableType_ExcessManaDecreaseRatePerSecond = 42,
ScenarioVariableType_SacrificesId = 43,
// 44
ScenarioVariableType_DefaultHorizontalTerrainWibble = 45,
ScenarioVariableType_DefaultVerticalTerrainWibble = 46,
ScenarioVariableType_RebelMaxFollowers = 47,
ScenarioVariableType_RebelLeavingPercentage = 48,
ScenarioVariableType_RebelBecomeGoodPercentage = 49,
ScenarioVariableType_RebelDefectPercentage = 50,
ScenarioVariableType_TimeBeforeFirstPayDaySeconds = 51,
ScenarioVariableType_DefaultTorchLightRed = 52,
ScenarioVariableType_DefaultTorchLightGreen = 53,
ScenarioVariableType_DefaultTorchLightBlue = 54,
ScenarioVariableType_PrisonModifyCreatureHealthPerSecond = 55,
// 56
// 57
// 58
// 59
// 60
// 61
// 62
// 63
// 64
ScenarioVariableType_CreatureStatsId = 65,
// 66
ScenarioVariableType_DefaultCellingHeightTiles = 67,
ScenarioVariableType_PayDayCutOffTimeSeconds = 68,
// 69
ScenarioVariableType_CasinoModifyCreatureGoldPerSecond = 70,
ScenarioVariableType_CasinoBigWinKeeperLossPercentage = 71,
// 72
ScenarioVariableType_CreaturesSupportedByFirstPortal = 73,
ScenarioVariableType_GameTicks = 74,
ScenarioVariableType_ModifyHealthOfCreatureInLairPerSecond = 75,
ScenarioVariableType_ModifyAngerOfCreatureInLairPerSecond = 76,
// 77
ScenarioVariableType_ModifyAngerInCompanyOfHatedCreaturesPerSecond = 78,
ScenarioVariableType_ForceAppliedToSlappedCreature = 79,
// 80
// 81
ScenarioVariableType_CannotSleepModifyCreatureHealthPerSecond = 82,
ScenarioVariableType_CannotLeaveModifyCreatureHealthPerSecond = 83,
ScenarioVariableType_PlayerRescanIntervalSeconds = 84,
ScenarioVariableType_PlayerRescanRange = 85,
ScenarioVariableType_MaxFreeRangeChickensPerPlayer = 86,
ScenarioVariableType_WoodBridgeLifeOnLavaSeconds = 87,
ScenarioVariableType_CreatureSleepsWhenBelowPercentHealth = 88,
// 89
// 90
// 91
// 92
ScenarioVariableType_DoorPickedTimeSeconds = 93,
ScenarioVariableType_MinimumImpThreshold = 94,
ScenarioVariableType_TimeBeforeFreeImpGeneratedSeconds = 95,
ScenarioVariableType_CreatureStunnedTimeSeconds = 96,
ScenarioVariableType_CreatureStunnedEffectDelaySeconds = 97,
ScenarioVariableType_FirstPersonStatIncreasePercentage = 98,
// 99
ScenarioVariableType_ManaCostInFirstPersonPerSecond = 100,
ScenarioVariableType_HealthDecreaseWhenOnFirePerSecond = 101,
ScenarioVariableType_ModifyCreatureAngerWhilePrayingPerSecond = 102,
// 103
ScenarioVariableType_RoomSellValuePercentageOfCost = 104,
ScenarioVariableType_DoorSellValuePercentageOfCost = 105,
ScenarioVariableType_TrapSellValuePercentageOfCost = 106,
// 107
// 108
// 109
ScenarioVariableType_InsufficientManaImpEvaluationPeriodSeconds = 110,
ScenarioVariableType_DeathWhenPossessingManaReduction = 111,
ScenarioVariableType_PercentageOfManaGainedBySacrificing = 112,
ScenarioVariableType_PossessionFriendlyStatIncreasePercentage = 113,
ScenarioVariableType_PossessionFriendlyHealthIncreasePercentage = 114,
ScenarioVariableType_ModifyCreatureHealthWhilePrayingPerSecond = 115,
ScenarioVariableType_HypnotismManaDrainPerSecond = 116,
// 117
// 118
// 119
// 120
// 121
// 122
// 123
// 124
ScenarioVariableType_LowManaWarningThreshold = 125,
ScenarioVariableType_TrainingRoomMaxExperienceLevel = 126,
ScenarioVariableType_StunnedDamageIncreasePercentage = 127,
ScenarioVariableType_MaxNumberOfThingsInHand = 128,
ScenarioVariableType_RubberBandAreaLimit = 129,
ScenarioVariableType_GravityConstant = 130,
ScenarioVariableType_DungeonHeartHealthRegenerationPerSecond = 131,
ScenarioVariableType_EPLossSecondWhileNotTraining = 132,
ScenarioVariableType_ModifyAngerWhileSolitaryInPitPerSecond = 133,
ScenarioVariableType_ModifyAngerOfCombatPitVictor = 134,
ScenarioVariableType_ModifyAngerOfPitSpectatorPerSecond = 135,
ScenarioVariableType_ConvertNeutralRoomHealth = 136,
// 137
ScenarioVariableType_ManufacturePointLossWhileNoWorkersPerSecond = 138,
ScenarioVariableType_CasinoBigWinnerIncreasedHappinessPersentage = 139,
ScenarioVariableType_DecreaseEntrancePersentageOfLastSackedCreatureType = 140,
ScenarioVariableType_IncreaseEntrancePersentageOfNewCreatureTypes = 141,
ScenarioVariableType_IncreasedWorkRatePersentageOfPlayersTorturedCreatureTypes = 142,
ScenarioVariableType_IncreasedWorkRatePersentageWhenCasinoBigWinOccurs = 143,
ScenarioVariableType_ImpPopCountdownTimeSeconds = 144,
ScenarioVariableType_NumberOfRandomWallDeteriorationsPerSecond = 145,
ScenarioVariableType_WallDeteriorationDamage = 146,
ScenarioVariableType_WallDamageFromShot = 147,
ScenarioVariableType_PercentageChanceOfKillingCreatureInPit = 148,
ScenarioVariableType_ModifyPlayerColdWhileTrainingPerSecond = 149,
ScenarioVariableType_SkeletonArmyDurationSeconds = 150,
ScenarioVariableType_OvercrowdingJailBreakPercentagePerExtraCreature = 151,
ScenarioVariableType_MaxTimeInGuardRoomBeforeWanderingToGuardPostSeconds = 152,
// 153
ScenarioVariableType_AmountOfGoldStolenInFirstPerson = 154,
ScenarioVariableType_BoulderDeteriorationDamagePercentagePerSecond = 155,
ScenarioVariableType_DrunkenDurationSeconds = 156,
ScenarioVariableType_BoulderInitialHealth = 157,
ScenarioVariableType_BoulderSpeedTilesPerSecond = 158,
ScenarioVariableType_BoulderToCreatureDamagePercentage = 159,
ScenarioVariableType_BoulderSpeedToHealthRatio = 160,
ScenarioVariableType_BoulderFearAmount = 161,
ScenarioVariableType_BoulderFearRange = 162,
ScenarioVariableType_DrunkChancePercentagePerDrink = 163,
ScenarioVariableType_IncreasedWorkRatePecentageFromSlapping = 164,
ScenarioVariableType_IncreasedWorkRateDurationFromSlappingSeconds = 165,
ScenarioVariableType_DungeonHeartMaxManaIncrease = 166,
ScenarioVariableType_DungeonHeartManaGenerationIncreasePerSecond = 167,
ScenarioVariableType_TremorDamagePerTerrainTilePerSecond = 168,
ScenarioVariableType_ExternalGuardingDurationSeconds = 169,
ScenarioVariableType_MinimumJobDurationSeconds = 170,
// 171
// 172
ScenarioVariableType_CreatureCriticalHealthPercentageOfMax = 173,
ScenarioVariableType_DungeonHeartObjectHealth = 174,
ScenarioVariableType_ClaimNeutralManaVaultHealth = 175,
ScenarioVariableType_ClaimEnemyManaVaultHealth = 176,
ScenarioVariableType_CreaturesSupportedPerAdditionalPortal = 177,
ScenarioVariableType_MaxGoldPerDungeonHeartTile = 178,
ScenarioVariableType_ImpExperienceGainPerSecond = 179,
ScenarioVariableType_DungeonHeartClaimScanRadiusTiles = 180,
ScenarioVariableType_StunTimePercentageIncreaseWhenHit = 181,
ScenarioVariableType_CallToArmsFightDistanceTiles = 182,
ScenarioVariableType_DungeonHeartReportingDistanceTiles = 183,
ScenarioVariableType_DwarfDiggingMultiplier = 184,
ScenarioVariableType_TriggerTrapTriggerSpeedTilesPerSecond = 185,
// 186
ScenarioVariableType_FirstPersonAccuracyAngleDegrees = 187,
ScenarioVariableType_SpecialReceiveImpsAmount = 188,
ScenarioVariableType_DungeonHeartCreatureGatheringDistanceTiles = 189,
ScenarioVariableType_GuardRoomReportingDistanceTiles = 190,
ScenarioVariableType_GuardPostReportingDistanceTiles = 191,
ScenarioVariableType_CombatPitMaxExperienceLevel = 192,
ScenarioVariableType_TimeInHandBeforeCreaturesBecomeAngrySeconds = 193,
ScenarioVariableType_SlapWorkFasterDurationSeconds = 194,
ScenarioVariableType_SlapSpeedUpDurationSeconds = 195,
ScenarioVariableType_TortureSpeedUpDurationSeconds = 196,
ScenarioVariableType_CasinoWinSpeedUpDurationSeconds = 197,
ScenarioVariableType_CriticalHealthPercentageForHeroLair = 198,
ScenarioVariableType_GamblingModifyCreatureMoneySmilesPerSecond = 199,
ScenarioVariableType_GamblingModifyCreatureMoneyMoneyPerSecond = 200,
ScenarioVariableType_GamblingModifyCreatureAngerSmilesPerSecond = 201,
ScenarioVariableType_GamblingModifyCreatureAngerMoneyPerSecond = 202,
ScenarioVariableType_GamblingDelayBeforeBecomingFearlessSeconds = 203,
ScenarioVariableType_GamblingFearlessDurationSeconds = 204,
ScenarioVariableType_GamblingCreatureJackpotChanceSmilesPerSecond = 205,
ScenarioVariableType_GamblingCreatureJackpotChanceMoneyPerSecond = 206,
ScenarioVariableType_GamblingJackpotPayTimeLimit = 207,
ScenarioVariableType_GamblingJackpotPayTimeLimitMissedAngerPercentageModifier = 208,
ScenarioVariableType_GamblingJackpotBasicAmount = 209,
ScenarioVariableType_FirstPersonRechargeModifyPercentage = 210,
ScenarioVariableType_BackOffMinimumDurationSeconds = 211,
ScenarioVariableType_BackOffMaximumDurationSeconds = 212,
ScenarioVariableType_GoodCreaturesChaseEnemyDurationSeconds = 213,
ScenarioVariableType_EvilCreaturesChaseEnemyDurationSeconds = 214,
ScenarioVariableType_MusicLevelTwoThreatThreshold = 215,
ScenarioVariableType_MusicLevelThreeThreatThreshold = 216,
ScenarioVariableType_MusicLevelFourThreatThreshold = 217,
ScenarioVariableType_TimeBeforeDungeonHeartConstructionBegins = 218,
ScenarioVariableType_DefaultTorchLightIntensity = 219,
ScenarioVariableType_DefaultTorchLightRadiusTiles = 220,
ScenarioVariableType_DefaultTorchLightHeightTiles = 221,
ScenarioVariableType_MaximumManaThreshold = 222,
ScenarioVariableType_MaximumGroupNumberOfFollowers = 223,
ScenarioVariableType_CreatureFirstPersonId = 224,
ScenarioVariableType_CombatPitMeleeDamageModifierPercentage = 225,
ScenarioVariableType_UpgradedPossessionCostPercentage = 226,
ScenarioVariableType_UpgradedCallToArmsCostPercentage = 227,
ScenarioVariableType_MPDScoreHeroKilled = 228,
ScenarioVariableType_MPDScoreLandOwned = 229,
ScenarioVariableType_MPDScoreGoldSlabsMined = 230,
ScenarioVariableType_MPDScoreItemManufactured = 231,
ScenarioVariableType_MPDScoreCreatureEntered = 232,
// 233
ScenarioVariableType_TortureChanceOfDyingWhenConverted = 234,
ScenarioVariableType_MaximumManaGainPerSecond = 235,
ScenarioVariableType_ClaimScanLightRed = 236,
ScenarioVariableType_ClaimScanLightGreen = 237,
ScenarioVariableType_ClaimScanLightBlue = 238,
ScenarioVariableType_PitPercentageDamageTakenOfNormalCombat = 239,
ScenarioVariableType_BoulderSlapDamage = 240,
ScenarioVariableType_LevelRating = 241,
ScenarioVariableType_AverageTime = 242,
};
//////////////////////////////////////////////////////////////////////////
inline bool KWDParseENUM(int inputInt, ePlayerID& outputID)
{
static const ePlayerID IDs[] =
{
ePlayerID_Null, // 0
ePlayerID_Good, // 1
ePlayerID_Neutral, // 2
ePlayerID_Keeper1, // 3
ePlayerID_Keeper2, // 4
ePlayerID_Keeper3, // 5
ePlayerID_Keeper4, // 6
};
if (inputInt < CountOf(IDs))
{
outputID = IDs[inputInt];
return true;
}
return false;
}
inline bool KWDParseENUM(int inputInt, eBridgeTerrain& outputID)
{
static const eBridgeTerrain IDs[] =
{
eBridgeTerrain_Null, // 0
eBridgeTerrain_Water, // 1
eBridgeTerrain_Lava, // 2
};
if (inputInt < CountOf(IDs))
{
outputID = IDs[inputInt];
return true;
}
return false;
}
inline bool KWDParseENUM(int inputInt, eThingMaterialType& outputID)
{
static const eThingMaterialType IDs[] =
{
eThingMaterialType_None, // 0
eThingMaterialType_Flesh, // 1
eThingMaterialType_Rock, // 2
eThingMaterialType_Wood, // 3
eThingMaterialType_Metal1, // 4
eThingMaterialType_Metal2, // 5
eThingMaterialType_Magic, // 6
eThingMaterialType_Glass, // 7
};
if (inputInt < CountOf(IDs))
{
outputID = IDs[inputInt];
return true;
}
return false;
}
inline bool KWDParseENUM(int inputInt, eArtResource& outputID)
{
static const eArtResource IDs[] =
{
eArtResource_Null, // 0
eArtResource_Sprite, // 1
eArtResource_Alpha, // 2
eArtResource_AdditiveAlpha, // 3
eArtResource_TerrainMesh, // 4
eArtResource_Mesh, // 5
eArtResource_AnimatingMesh, // 6
eArtResource_ProceduralMesh, // 7
eArtResource_MeshCollection, // 8
};
if (inputInt < CountOf(IDs))
{
outputID = IDs[inputInt];
return true;
}
return false;
}
inline bool KWDParseENUM(int inputInt, ePlayerType& outputID)
{
static const ePlayerType IDs[] =
{
ePlayerType_Human, // 0
ePlayerType_AI, // 1
};
if (inputInt < CountOf(IDs))
{
outputID = IDs[inputInt];
return true;
}
return false;
}
inline bool KWDParseENUM(int inputInt, eComputerAI& outputID)
{
static const eComputerAI IDs[] =
{
eComputerAI_MasterKeeper, // 0
eComputerAI_Conqueror, // 1
eComputerAI_Psychotic, // 2
eComputerAI_Stalwart, // 3
eComputerAI_Greyman, // 4
eComputerAI_Idiot, // 5
eComputerAI_Guardian, // 6
eComputerAI_ThickSkinned, // 7
eComputerAI_Paranoid, // 8
};
if (inputInt < CountOf(IDs))
{
outputID = IDs[inputInt];
return true;
}
return false;
}
inline bool KWDParseENUM(int inputInt, eRoomTileConstruction& outputID)
{
static const eRoomTileConstruction IDs[] =
{
eRoomTileConstruction_Complete,
eRoomTileConstruction_Quad,
eRoomTileConstruction_3_by_3,
eRoomTileConstruction_3_by_3_Rotated,
eRoomTileConstruction_Normal,
eRoomTileConstruction_CenterPool,
eRoomTileConstruction_DoubleQuad,
eRoomTileConstruction_5_by_5_Rotated,
eRoomTileConstruction_HeroGate,
eRoomTileConstruction_HeroGateTile,
eRoomTileConstruction_HeroGate_2_by_2,
eRoomTileConstruction_HeroGateFrontend,
eRoomTileConstruction_HeroGate_3_by_1,
};
if (inputInt == 0)
return false;
--inputInt; // shift down index
if (inputInt < eRoomTileConstruction_COUNT)
{
outputID = IDs[inputInt];
return true;
}
return false;
}
inline bool KWDParseENUM(int inputValue, eDirection& outputValue)
{
static const eDirection IDs[] =
{
eDirection_N,
eDirection_NE,
eDirection_E,
eDirection_SE,
eDirection_S,
eDirection_SW,
eDirection_W,
eDirection_NW
};
if (inputValue < CountOf(IDs))
{
outputValue = IDs[inputValue];
return true;
}
return false;
}
inline bool KWDParseENUM(int inputValue, eCreatureJobClass& outputValue)
{
static const eCreatureJobClass IDs[] =
{
eCreatureJobClass_Worker,
eCreatureJobClass_Thinker,
eCreatureJobClass_Fighter,
eCreatureJobClass_Scout,
};
if (inputValue < CountOf(IDs))
{
outputValue = IDs[inputValue];
return true;
}
return false;
}
inline bool KWDParseENUM(int inputValue, eCreatureJob& outputValue)
{
static const eCreatureJob IDs[] =
{
eCreatureJob_None,
eCreatureJob_Sleep,
eCreatureJob_Eat,
eCreatureJob_Research,
eCreatureJob_Train,
eCreatureJob_Manufacture,
eCreatureJob_Guard,
eCreatureJob_Torture,
eCreatureJob_Pray,
eCreatureJob_Drink,
eCreatureJob_Leave,
eCreatureJob_DestroyEnemyRooms,
eCreatureJob_DestroyWalls,
eCreatureJob_StealGold,
eCreatureJob_StealSpells,
eCreatureJob_Sulk,
eCreatureJob_Rebel,
eCreatureJob_StealCrates,
eCreatureJob_KillCreatures,
eCreatureJob_KillPlayer,
eCreatureJob_COUNT, // tunnelling 20
eCreatureJob_COUNT, // ? 21
eCreatureJob_Wait,
eCreatureJob_SendToActionPoint,
eCreatureJob_Explore,
eCreatureJob_StealEnemyGold,
eCreatureJob_CombatPitSpectate,
eCreatureJob_JailBreak,
eCreatureJob_COUNT, // tolling 28
};
outputValue = eCreatureJob_COUNT;
if (inputValue < CountOf(IDs))
{
outputValue = IDs[inputValue];
}
return outputValue < eCreatureJob_COUNT;
}
inline bool KWDRoomThingToRoomType(int inputValue, RoomTypeId& outputValue)
{
outputValue = RoomTypeId_Null;
switch (inputValue)
{
case 12: outputValue = RoomTypeId_Portal; break;
case 14: outputValue = RoomTypeId_DungeonHeart; break;
case 33: outputValue = RoomTypeId_HeroGate_2x2; break;
case 34: outputValue = RoomTypeId_HeroGate_Frontend; break;
case 37: outputValue = RoomTypeId_HeroGate_3x1; break;
case 40: outputValue = RoomTypeId_HeroPortal; break;
}
return outputValue != RoomTypeId_Null;
}
//////////////////////////////////////////////////////////////////////////
bool DK2ScenarioReader::ReadString(unsigned int stringLength, std::wstring& wideString)
{
wideString.resize(stringLength);
// read bytes
if (!cxx::read_elements(mFileStream, wideString.data(), stringLength))
return false;
// trim
wideString.erase(
std::find_if(wideString.begin(), wideString.end(), [](wchar_t c) -> bool {
return c == 0;
}),
wideString.end());
return true;
}
bool DK2ScenarioReader::ReadString8(unsigned int stringLength, std::string& ansiString)
{
ansiString.resize(stringLength);
// read bytes
if (!mFileStream.read(&ansiString[0], stringLength))
return false;
// trim
ansiString.erase(
std::find_if(ansiString.begin(), ansiString.end(), [](const char& c)->bool {
return c == 0;
}),
ansiString.end());
return true;
}
bool DK2ScenarioReader::ReadTimestamp()
{
unsigned short dkyear;
unsigned char dkday;
unsigned char dkmonth;
unsigned char dkhour;
unsigned char dkminute;
unsigned char dksecond;
unsigned char dkbyte;
unsigned short fillerBytes;
READ_FROM_FSTREAM(mFileStream, dkyear);
READ_FROM_FSTREAM(mFileStream, dkday);
READ_FROM_FSTREAM(mFileStream, dkmonth);
READ_FROM_FSTREAM(mFileStream, fillerBytes);
READ_FROM_FSTREAM(mFileStream, dkhour);
READ_FROM_FSTREAM(mFileStream, dkminute);
READ_FROM_FSTREAM(mFileStream, dksecond);
READ_FROM_FSTREAM(mFileStream, dkbyte);
return true;
}
bool DK2ScenarioReader::Read32bitsFloat(float& outputFloat)
{
unsigned int encoded_float;
READ_FSTREAM_U32(mFileStream, encoded_float);
// decode float
outputFloat = encoded_float / DIVIDER_FLOAT;
return true;
}
bool DK2ScenarioReader::ReadVector3f(glm::vec3& outputVector)
{
if (!Read32bitsFloat(outputVector.x) ||
!Read32bitsFloat(outputVector.y) ||
!Read32bitsFloat(outputVector.z))
{
return false;
}
return true;
}
bool DK2ScenarioReader::ReadLight()
{
unsigned int ikpos;
READ_FSTREAM_U32(mFileStream, ikpos); // x, / ConversionUtils.FLOAT
READ_FSTREAM_U32(mFileStream, ikpos); // y, / ConversionUtils.FLOAT
READ_FSTREAM_U32(mFileStream, ikpos); // z, / ConversionUtils.FLOAT
unsigned int iradius;
READ_FSTREAM_U32(mFileStream, iradius); // / ConversionUtils.FLOAT
unsigned int flags;
READ_FSTREAM_U32(mFileStream, flags);
unsigned char rgb;
READ_FSTREAM_U8(mFileStream, rgb); // r
READ_FSTREAM_U8(mFileStream, rgb); // g
READ_FSTREAM_U8(mFileStream, rgb); // b
READ_FSTREAM_U8(mFileStream, rgb); // a
return true;
}
bool DK2ScenarioReader::ReadArtResource(ArtResourceDefinition& artResource)
{
if (!ReadString8(64, artResource.mResourceName))
return false;
unsigned int resourceFlags = 0;
READ_FSTREAM_U32(mFileStream, resourceFlags);
// reading flags
artResource.mPlayerColoured = (resourceFlags & ARTRESF_PLAYER_COLOURED) > 0;
artResource.mAnimatingTexture = (resourceFlags & ARTRESF_ANIMATING_TEXTURE) > 0;
artResource.mHasStartAnimation = (resourceFlags & ARTRESF_HAS_START_ANIMATION) > 0;
artResource.mHasEndAnimation = (resourceFlags & ARTRESF_HAS_END_ANIMATION) > 0;
artResource.mRandomStartFrame = (resourceFlags & ARTRESF_RANDOM_START_FRAME) > 0;
artResource.mOriginAtBottom = (resourceFlags & ARTRESF_ORIGIN_AT_BOTTOM) > 0;
artResource.mDoesntLoop = (resourceFlags & ARTRESF_DOESNT_LOOP) > 0;
artResource.mFlat = (resourceFlags & ARTRESF_FLAT) > 0;
artResource.mDoesntUseProgressiveMesh = (resourceFlags & ARTRESF_DOESNT_USE_PROGRESSIVE_MESH) > 0;
artResource.mUseAnimatingTextureForSelection = (resourceFlags & ARTRESF_USE_ANIMATING_TEXTURE_FOR_SELECTION) > 0;
artResource.mPreload = (resourceFlags & ARTRESF_PRELOAD) > 0;
artResource.mBlood = (resourceFlags & ARTRESF_BLOOD) > 0;
union packed_data_t
{
struct
{
unsigned int mEncodedWidth;
unsigned int mEncodedHeight;
unsigned short mFrames;
unsigned short mNone_1;
} mImageType;
struct
{
unsigned int mEncodedScale;
unsigned short mFrames;
unsigned short mNone_1;
unsigned int mNone_2;
} mMeshType;
struct
{
unsigned int mFrames;
unsigned int mFps;
unsigned short mStartDist;
unsigned short mEndDist;
} mAnimType;
struct
{
unsigned int mId;
unsigned int mNone_1;
unsigned int mNone_2;
} mProcType;
struct
{
unsigned int mNone_1;
unsigned int mNone_2;
unsigned int mNone_3;
} mTerrainType;
unsigned char mBytes[12];
};
packed_data_t packed_data;
static_assert(sizeof(packed_data) == 12, "Wrong size");
if (!mFileStream.read(reinterpret_cast(&packed_data), sizeof(packed_data)))
return false;
// map resource type
unsigned char resourceType;
READ_FSTREAM_U8(mFileStream, resourceType);
if (!KWDParseENUM(resourceType, artResource.mResourceType))
return false;
if (artResource.mResourceType == eArtResource_AnimatingMesh)
{
READ_FSTREAM_U8(mFileStream, artResource.mStartAF);
READ_FSTREAM_U8(mFileStream, artResource.mEndAF);
}
else
{
unsigned short unknownWord;
READ_FSTREAM_U16(mFileStream, unknownWord);
}
unsigned char unknownByte;
READ_FROM_FSTREAM(mFileStream, unknownByte);
switch (artResource.mResourceType)
{
case eArtResource_Sprite:
case eArtResource_Alpha:
case eArtResource_AdditiveAlpha:
artResource.mImageDesc.mFrames = packed_data.mImageType.mFrames;
artResource.mImageDesc.mWidth = packed_data.mImageType.mEncodedWidth / DIVIDER_FLOAT;
artResource.mImageDesc.mHeight = packed_data.mImageType.mEncodedHeight / DIVIDER_FLOAT;
break;
case eArtResource_Mesh:
case eArtResource_MeshCollection:
artResource.mMeshDesc.mFrames = packed_data.mMeshType.mFrames;
artResource.mMeshDesc.mScale = packed_data.mMeshType.mEncodedScale / DIVIDER_FLOAT;
break;
case eArtResource_AnimatingMesh:
artResource.mAnimationDesc.mFps = packed_data.mAnimType.mFps;
artResource.mAnimationDesc.mFrames = packed_data.mAnimType.mFrames;
artResource.mAnimationDesc.mDistStart = packed_data.mAnimType.mStartDist;
artResource.mAnimationDesc.mDistEnd = packed_data.mAnimType.mEndDist;
break;
case eArtResource_ProceduralMesh:
artResource.mProcDesc.mId = packed_data.mProcType.mId;
break;
}
return true;
}
bool DK2ScenarioReader::ReadTerrainFlags(TerrainDefinition& terrainDef)
{
unsigned int terrainFlags = 0;
READ_FSTREAM_U32(mFileStream, terrainFlags);
terrainDef.mIsSolid = (terrainFlags & TERRAINF_SOLID) > 0;
terrainDef.mIsImpenetrable = (terrainFlags & TERRAINF_IMPENETRABLE) > 0;
terrainDef.mIsOwnable = (terrainFlags & TERRAINF_OWNABLE) > 0;
terrainDef.mIsTaggable = (terrainFlags & TERRAINF_TAGGABLE) > 0;
terrainDef.mIsAttackable = (terrainFlags & TERRAINF_ATTACKABLE) > 0;
terrainDef.mHasTorch = (terrainFlags & TERRAINF_TORCH) > 0;
terrainDef.mIsWater = (terrainFlags & TERRAINF_WATER) > 0;
terrainDef.mIsLava = (terrainFlags & TERRAINF_LAVA) > 0;
terrainDef.mAlwaysExplored = (terrainFlags & TERRAINF_ALWAYS_EXPLORED) > 0;
terrainDef.mPlayerColouredPath = (terrainFlags & TERRAINF_PLAYER_COLOURED_PATH) > 0;
terrainDef.mPlayerColouredWall = (terrainFlags & TERRAINF_PLAYER_COLOURED_WALL) > 0;
terrainDef.mConstructionTypeWater = (terrainFlags & TERRAINF_CONSTRUCTION_TYPE_WATER) > 0;
terrainDef.mConstructionTypeQuad = (terrainFlags & TERRAINF_CONSTRUCTION_TYPE_QUAD) > 0;
terrainDef.mUnexploreIfDugByAnotherPlayer = (terrainFlags & TERRAINF_UNEXPLORE_IF_DUG_BY_ANOTHER_PLAYER) > 0;
terrainDef.mFillInable = (terrainFlags & TERRAINF_FILL_INABLE) > 0;
terrainDef.mAllowRoomWalls = (terrainFlags & TERRAINF_ALLOW_ROOM_WALLS) > 0;
terrainDef.mIsDecay = (terrainFlags & TERRAINF_DECAY) > 0;
terrainDef.mHasRandomTexture = (terrainFlags & TERRAINF_RANDOM_TEXTURE) > 0;
terrainDef.mTerrainColorR = (terrainFlags & TERRAINF_TERRAIN_COLOR_R) > 0;
terrainDef.mTerrainColorG = (terrainFlags & TERRAINF_TERRAIN_COLOR_G) > 0;
terrainDef.mTerrainColorB = (terrainFlags & TERRAINF_TERRAIN_COLOR_B) > 0;
terrainDef.mDwarfCanDigThrough = (terrainFlags & TERRAINF_DWARF_CAN_DIG_THROUGH) > 0;
terrainDef.mRevealThroughFogOfWar = (terrainFlags & TERRAINF_REVEAL_THROUGH_FOG_OF_WAR) > 0;
terrainDef.mAmbientColorR = (terrainFlags & TERRAINF_AMBIENT_COLOR_R) > 0;
terrainDef.mAmbientColorG = (terrainFlags & TERRAINF_AMBIENT_COLOR_G) > 0;
terrainDef.mAmbientColorB = (terrainFlags & TERRAINF_AMBIENT_COLOR_B) > 0;
terrainDef.mHasLight = (terrainFlags & TERRAINF_LIGHT) > 0;
terrainDef.mHasAmbientLight = (terrainFlags & TERRAINF_AMBIENT_LIGHT) > 0;
return true;
}
bool DK2ScenarioReader::ReadRoomFlags(RoomDefinition& roomDef)
{
unsigned int roomFlags = 0;
READ_FSTREAM_U32(mFileStream, roomFlags);
roomDef.mPlaceableOnWater = (roomFlags & ROOMF_PLACEABLE_ON_WATER) > 0;
roomDef.mPlaceableOnLava = (roomFlags & ROOMF_PLACEABLE_ON_LAVA) > 0;
roomDef.mPlaceableOnLand = (roomFlags & ROOMF_PLACEABLE_ON_LAND) > 0;
roomDef.mHasWalls = (roomFlags & ROOMF_HAS_WALLS) > 0;
roomDef.mCentre = (roomFlags & ROOMF_CENTRE) > 0;
roomDef.mSpecialTiles = (roomFlags & ROOMF_SPECIAL_TILES) > 0;
roomDef.mNormalTiles = (roomFlags & ROOMF_NORMAL_TILES) > 0;
roomDef.mBuildable = (roomFlags & ROOMF_BUILDABLE) > 0;
roomDef.mSpecialWalls = (roomFlags & ROOMF_SPECIAL_WALLS) > 0;
roomDef.mIsAttackable = (roomFlags & ROOMF_ATTACKABLE) > 0;
roomDef.mHasFlame = (roomFlags & ROOMF_HAS_FLAME) > 0;
roomDef.mIsGood = (roomFlags & ROOMF_IS_GOOD) > 0;
return true;
}
bool DK2ScenarioReader::ReadObjectFlags(GameObjectDefinition& objectDef)
{
unsigned int objectFlags;
READ_FSTREAM_U32(mFileStream, objectFlags);
objectDef.mDieOverTime = (objectFlags & OBJECTF_DIE_OVER_TIME) > 0;
objectDef.mDieOverTimeIfNotInRoom = (objectFlags & OBJECTF_DIE_OVER_TIME_IF_NOT_IN_ROOM) > 0;
objectDef.mCanBePickedUp = (objectFlags & OBJECTF_CAN_BE_PICKED_UP) > 0;
objectDef.mCanBeSlapped = (objectFlags & OBJECTF_CAN_BE_SLAPPED) > 0;
objectDef.mDieWhenSlapped = (objectFlags & OBJECTF_DIE_WHEN_SLAPPED) > 0;
objectDef.mCanBeDroppedOnAnyLand = (objectFlags & OBJECTF_CAN_BE_DROPPED_ON_ANY_LAND) > 0;
objectDef.mObstacle = (objectFlags & OBJECTF_OBSTACLE) > 0;
objectDef.mBounce = (objectFlags & OBJECTF_BOUNCE) > 0;
objectDef.mBoulderCanRollThrough = (objectFlags & OBJECTF_BOULDER_CAN_ROLL_THROUGH) > 0;
objectDef.mBoulderDestroys = (objectFlags & OBJECTF_BOULDER_DESTROYS) > 0;
objectDef.mIsPillar = (objectFlags & OBJECTF_PILLAR) > 0;
objectDef.mDoorKey = (objectFlags & OBJECTF_DOOR_KEY) > 0;
objectDef.mIsDamageable = (objectFlags & OBJECTF_DAMAGEABLE) > 0;
objectDef.mHighlightable = (objectFlags & OBJECTF_HIGHLIGHTABLE) > 0;
objectDef.mPlaceable = (objectFlags & OBJECTF_PLACEABLE) > 0;
objectDef.mFirstPersonObstacle = (objectFlags & OBJECTF_FIRST_PERSON_OBSTACLE) > 0;
objectDef.mSolidObstacle = (objectFlags & OBJECTF_SOLID_OBSTACLE) > 0;
objectDef.mCastShadows = (objectFlags & OBJECTF_CAST_SHADOWS) > 0;
// category
objectDef.mObjectCategory = eGameObjectCategory_Normal;
{
const unsigned int categoryBitsMask = OBJECTF_TYPE_SPECIAL | OBJECTF_TYPE_SPELL_BOOK | OBJECTF_TYPE_CRATE |
OBJECTF_TYPE_LAIR | OBJECTF_TYPE_GOLD | OBJECTF_TYPE_FOOD | OBJECTF_TYPE_LEVEL_GEM;
switch (objectFlags & categoryBitsMask)
{
case OBJECTF_TYPE_SPECIAL : objectDef.mObjectCategory = eGameObjectCategory_Special; break;
case OBJECTF_TYPE_SPELL_BOOK: objectDef.mObjectCategory = eGameObjectCategory_SpellBook; break;
case OBJECTF_TYPE_CRATE : objectDef.mObjectCategory = eGameObjectCategory_Crate; break;
case OBJECTF_TYPE_LAIR : objectDef.mObjectCategory = eGameObjectCategory_Lair; break;
case OBJECTF_TYPE_GOLD : objectDef.mObjectCategory = eGameObjectCategory_Gold; break;
case OBJECTF_TYPE_FOOD : objectDef.mObjectCategory = eGameObjectCategory_Food; break;
case OBJECTF_TYPE_LEVEL_GEM : objectDef.mObjectCategory = eGameObjectCategory_LevelGem; break;
default:
cxx_assert((objectFlags & categoryBitsMask) == 0);
break;
}
}
return true;
}
bool DK2ScenarioReader::ReadStringId()
{
unsigned int ids[5];
for (unsigned int& idEntry : ids)
{
READ_FROM_FSTREAM(mFileStream, idEntry);
}
unsigned int unknownDword;
READ_FROM_FSTREAM(mFileStream, unknownDword);
return true;
}
bool DK2ScenarioReader::ReadMapData(ScenarioDefinition& scenarioData)
{
scenarioData.mMapTiles.resize(scenarioData.mLevelDimensionX * scenarioData.mLevelDimensionY);
// read tiles
for (int tiley = 0; tiley < scenarioData.mLevelDimensionY; ++tiley)
for (int tilex = 0; tilex < scenarioData.mLevelDimensionX; ++tilex)
{
const int tileIndex = (tiley * scenarioData.mLevelDimensionX) + tilex;
// terrain type is not mapped to internal id so it can be red as is
READ_FSTREAM_U8(mFileStream, scenarioData.mMapTiles[tileIndex].mTerrainType);
unsigned char playerID;
READ_FSTREAM_U8(mFileStream, playerID);
if (!KWDParseENUM(playerID, scenarioData.mMapTiles[tileIndex].mOwnerId))
return false;
unsigned char bridgeTerrain;
READ_FSTREAM_U8(mFileStream, bridgeTerrain);
if (!KWDParseENUM(bridgeTerrain, scenarioData.mMapTiles[tileIndex].mTerrainUnderTheBridge))
return false;
unsigned char filler;
READ_FROM_FSTREAM(mFileStream, filler);
}
return true;
}
bool DK2ScenarioReader::ReadScenarioVariables(int numElements, ScenarioDefinition& scenarioData)
{
for (int ielement = 0; ielement < numElements; ++ielement)
{
int variableType = 0;
READ_FROM_FSTREAM(mFileStream, variableType);
if (variableType == ScenarioVariableType_CreaturePool)
{
int dummyInt;
READ_FROM_FSTREAM(mFileStream, dummyInt); // creature id
READ_FROM_FSTREAM(mFileStream, dummyInt); // value
READ_FROM_FSTREAM(mFileStream, dummyInt); // player id
continue;
}
if (variableType == ScenarioVariableType_Availability)
{
unsigned short dummyWord;
READ_FROM_FSTREAM(mFileStream, dummyWord); // availability type
READ_FROM_FSTREAM(mFileStream, dummyWord); // player id
int dummyInt;
READ_FROM_FSTREAM(mFileStream, dummyInt); // type id
READ_FROM_FSTREAM(mFileStream, dummyInt); // availability value
continue;
}
if (variableType == ScenarioVariableType_SacrificesId)
{
unsigned char dummyByte;
READ_FROM_FSTREAM(mFileStream, dummyByte); // type 1
READ_FROM_FSTREAM(mFileStream, dummyByte); // id 1
READ_FROM_FSTREAM(mFileStream, dummyByte); // type 2
READ_FROM_FSTREAM(mFileStream, dummyByte); // id 2
READ_FROM_FSTREAM(mFileStream, dummyByte); // type 3
READ_FROM_FSTREAM(mFileStream, dummyByte); // id 3
READ_FROM_FSTREAM(mFileStream, dummyByte); // sacrifice reward type
READ_FROM_FSTREAM(mFileStream, dummyByte); // speech id
int dummyInt;
READ_FROM_FSTREAM(mFileStream, dummyInt); // reward value
continue;
}
if ((variableType == ScenarioVariableType_CreatureStatsId) ||
(variableType == ScenarioVariableType_CreatureFirstPersonId))
{
int dummyInt;
READ_FROM_FSTREAM(mFileStream, dummyInt); // stat type
READ_FROM_FSTREAM(mFileStream, dummyInt); // value
READ_FROM_FSTREAM(mFileStream, dummyInt); // level
continue;
}
// common
int intValue;
READ_FROM_FSTREAM(mFileStream, intValue); // value
int dummyInt;
READ_FROM_FSTREAM(mFileStream, dummyInt); // unknown 1
READ_FROM_FSTREAM(mFileStream, dummyInt); // unknown 1
ScenarioVariables& scenarioVars = scenarioData.mVariables;
switch (variableType)
{
case ScenarioVariableType_DigRockHealth:
scenarioVars.mDigRockHealth = intValue;
break;
case ScenarioVariableType_DigOwnWallHealth:
scenarioVars.mDigOwnWallHealth = intValue;
break;
case ScenarioVariableType_DigEnemyWallHealth:
scenarioVars.mDigEnemyWallHealth = intValue;
break;
case ScenarioVariableType_ClaimTileHealth:
scenarioVars.mClaimFloorHealth = intValue;
break;
case ScenarioVariableType_MineGoldHealth:
scenarioVars.mMineGoldHealth = intValue;
break;
case ScenarioVariableType_ReinforceWallHealth:
scenarioVars.mReinforceWallHealth = intValue;
break;
case ScenarioVariableType_GoldMinedFromGems:
scenarioVars.mGoldMinedFromGems = intValue;
break;
case ScenarioVariableType_MaxGoldPerTreasuryTile:
scenarioVars.mMaxGoldPerTreasuryTile = intValue;
break;
case ScenarioVariableType_SpecialIncreaseGoldAmount:
scenarioVars.mSpecialIncreaseGoldAmount = intValue;
break;
case ScenarioVariableType_MaxGoldPileOutsideTreasury:
scenarioVars.mMaxGoldPileOutsideTreasury = intValue;
break;
case ScenarioVariableType_MaxGoldPerDungeonHeartTile:
scenarioVars.mMaxGoldPerDungeonHeartTile = intValue;
break;
case ScenarioVariableType_MaximumManaThreshold:
scenarioVars.mMaximumManaThreshold = intValue;
break;
}
}
return true;
}
bool DK2ScenarioReader::ReadObjectDefinition(GameObjectDefinition& objectDef)
{
if (!ReadString8(32, objectDef.mObjectName))
return false;
// resources
if (!ReadArtResource(objectDef.mResourceMesh))
return false;
if (!ReadArtResource(objectDef.mResourceGuiIcon))
return false;
if (!ReadArtResource(objectDef.mResourceInHandIcon))
return false;
if (!ReadArtResource(objectDef.mResourceInHandMesh))
return false;
if (!ReadArtResource(objectDef.mResourceUnknown))
return false;
if (!ReadArtResource(objectDef.mResourceAdditional1))
return false;
if (!ReadArtResource(objectDef.mResourceAdditional2))
return false;
if (!ReadArtResource(objectDef.mResourceAdditional3))
return false;
if (!ReadArtResource(objectDef.mResourceAdditional4))
return false;
if (!ReadLight())
return false;
unsigned int width;
unsigned int height;
unsigned int mass;
unsigned int speed;
unsigned int airFriction;
READ_FROM_FSTREAM(mFileStream, width);
READ_FROM_FSTREAM(mFileStream, height);
READ_FROM_FSTREAM(mFileStream, mass);
READ_FROM_FSTREAM(mFileStream, speed);
READ_FROM_FSTREAM(mFileStream, airFriction);
// set params
objectDef.mWidth = (width * 1.0f) / DIVIDER_FLOAT;
objectDef.mHeight = (height * 1.0f) / DIVIDER_FLOAT;
objectDef.mMass = (mass * 1.0f) / DIVIDER_FLOAT;
objectDef.mSpeed = (speed * 1.0f) / DIVIDER_FLOAT;
objectDef.mAirFriction = (airFriction * 1.0f) / DIVIDER_DOUBLE;
unsigned char objMaterial;
READ_FSTREAM_U8(mFileStream, objMaterial);
if (!KWDParseENUM(objMaterial, objectDef.mObjectMaterial))
return false;
SKIP_FSTREAM_BYTES(mFileStream, 3);
if (!ReadObjectFlags(objectDef))
return false;
READ_FSTREAM_U16(mFileStream, objectDef.mHitpoints);
READ_FSTREAM_U16(mFileStream, objectDef.mMaxAngle);
SKIP_FSTREAM_BYTES(mFileStream, 2);
READ_FSTREAM_U16(mFileStream, objectDef.mManaValue);
READ_FSTREAM_U16(mFileStream, objectDef.mTooltipStringId);
READ_FSTREAM_U16(mFileStream, objectDef.mNameStringId);
READ_FSTREAM_U16(mFileStream, objectDef.mSlapEffectId);
READ_FSTREAM_U16(mFileStream, objectDef.mDeathEffectId);
READ_FSTREAM_U16(mFileStream, objectDef.mMiscEffectId);
// object type is not mapped to internal id so it can be red as is
READ_FSTREAM_U8(mFileStream, objectDef.mObjectClass);
unsigned char initialState;
READ_FROM_FSTREAM(mFileStream, initialState);
cxx_assert(initialState < eGameObjectState_MAX);
objectDef.mStartState = static_cast(initialState);
READ_FSTREAM_U8(mFileStream, objectDef.mRoomCapacity);
unsigned char pickupPriority;
READ_FROM_FSTREAM(mFileStream, pickupPriority);
// sound category
if (!ReadString8(32, objectDef.mSoundCategory))
return false;
return true;
}
bool DK2ScenarioReader::ReadObjectsData(int numElements, ScenarioDefinition& scenarioData)
{
scenarioData.mGameObjectDefs.resize(numElements + 1);
scenarioData.mGameObjectDefs[0] = {}; // dummy element
// read definitions
for (int iobject = 1; iobject < numElements + 1; ++iobject)
{
if (!ReadObjectDefinition(scenarioData.mGameObjectDefs[iobject]))
return false;
bool correctId = (scenarioData.mGameObjectDefs[iobject].mObjectClass == iobject);
cxx_assert(correctId);
}
return true;
}
bool DK2ScenarioReader::ReadPlayerDefinition(PlayerDefinition& playerDef)
{
READ_FSTREAM_U32(mFileStream, playerDef.mInitialGold);
unsigned int playerType;
READ_FSTREAM_U32(mFileStream, playerType);
if (!KWDParseENUM(playerType, playerDef.mPlayerType))
return false;
unsigned char aiType;
READ_FSTREAM_U8(mFileStream, aiType);
if (!KWDParseENUM(aiType, playerDef.mComputerAI))
return false;
unsigned char speed;
READ_FROM_FSTREAM(mFileStream, speed);
unsigned char openness;
READ_FROM_FSTREAM(mFileStream, openness);
unsigned char fillerByte;
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FROM_FSTREAM(mFileStream, fillerByte);
unsigned int fillerDword;
READ_FROM_FSTREAM(mFileStream, fillerDword);
READ_FROM_FSTREAM(mFileStream, fillerDword);
READ_FROM_FSTREAM(mFileStream, fillerDword);
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FROM_FSTREAM(mFileStream, fillerByte);
// build order
unsigned char buildOrderBytes[15];
if (!mFileStream.read(reinterpret_cast(buildOrderBytes), sizeof(buildOrderBytes)))
return false;
READ_FROM_FSTREAM(mFileStream, fillerByte); // flexibility
READ_FROM_FSTREAM(mFileStream, fillerByte); // digToNeutralRoomsWithinTilesOfClaimedArea
unsigned short fillerWord;
READ_FROM_FSTREAM(mFileStream, fillerWord); // removeCallToArmsAfterSeconds
READ_FROM_FSTREAM(mFileStream, fillerDword); // boulderTrapsOnLongCorridors
READ_FROM_FSTREAM(mFileStream, fillerDword); // boulderTrapsOnRouteToBreachPoints
READ_FROM_FSTREAM(mFileStream, fillerByte); // trapUseStyle
READ_FROM_FSTREAM(mFileStream, fillerByte); // doorTrapPreference
READ_FROM_FSTREAM(mFileStream, fillerByte); // doorUsage
READ_FROM_FSTREAM(mFileStream, fillerByte); // chanceOfLookingToUseTrapsAndDoors
READ_FROM_FSTREAM(mFileStream, fillerDword); // requireMinLevelForCreatures
READ_FROM_FSTREAM(mFileStream, fillerDword); // requireTotalThreatGreaterThanTheEnemy
READ_FROM_FSTREAM(mFileStream, fillerDword); // requireAllRoomTypesPlaced
READ_FROM_FSTREAM(mFileStream, fillerDword); // requireAllKeeperSpellsResearched
READ_FROM_FSTREAM(mFileStream, fillerDword); // onlyAttackAttackers
READ_FROM_FSTREAM(mFileStream, fillerDword); // neverAttack
READ_FROM_FSTREAM(mFileStream, fillerByte); // minLevelForCreatures
READ_FROM_FSTREAM(mFileStream, fillerByte); // totalThreatGreaterThanTheEnemy
READ_FROM_FSTREAM(mFileStream, fillerByte); // firstAttemptToBreachRoom
READ_FROM_FSTREAM(mFileStream, fillerByte); // firstDigToEnemyPoint
READ_FROM_FSTREAM(mFileStream, fillerByte); // breachAtPointsSimultaneously
READ_FROM_FSTREAM(mFileStream, fillerByte); // usePercentageOfTotalCreaturesInFirstFightAfterBreach
READ_FSTREAM_U16(mFileStream, playerDef.mManaValue);
READ_FROM_FSTREAM(mFileStream, fillerWord); // placeCallToArmsWhereThreatValueIsGreaterThan
READ_FROM_FSTREAM(mFileStream, fillerWord); // removeCallToArmsIfLessThanEnemyCreatures
READ_FROM_FSTREAM(mFileStream, fillerWord); // removeCallToArmsIfLessThanEnemyCreaturesWithinTiles
READ_FROM_FSTREAM(mFileStream, fillerWord); // pullCreaturesFromFightIfOutnumberedAndUnableToDropReinforcements
READ_FROM_FSTREAM(mFileStream, fillerByte); // threatValueOfDroppedCreaturesIsPercentageOfEnemyThreatValue
READ_FROM_FSTREAM(mFileStream, fillerByte); // spellStyle
READ_FROM_FSTREAM(mFileStream, fillerByte); // attemptToImprisonPercentageOfEnemyCreatures
READ_FROM_FSTREAM(mFileStream, fillerByte); // ifCreatureHealthIsPercentageAndNotInOwnRoomMoveToLairOrTemple
READ_FSTREAM_U16(mFileStream, playerDef.mGoldValue);
READ_FROM_FSTREAM(mFileStream, fillerDword); // tryToMakeUnhappyOnesHappy
READ_FROM_FSTREAM(mFileStream, fillerDword); // tryToMakeAngryOnesHappy
READ_FROM_FSTREAM(mFileStream, fillerDword); // disposeOfAngryCreatures
READ_FROM_FSTREAM(mFileStream, fillerDword); // disposeOfRubbishCreaturesIfBetterOnesComeAlong
READ_FROM_FSTREAM(mFileStream, fillerByte); // disposalMethod
READ_FROM_FSTREAM(mFileStream, fillerByte); // maximumNumberOfImps
READ_FROM_FSTREAM(mFileStream, fillerByte); // willNotSlapCreatures
READ_FROM_FSTREAM(mFileStream, fillerByte); // attackWhenNumberOfCreaturesIsAtLeast
READ_FROM_FSTREAM(mFileStream, fillerDword); // useLightningIfEnemyIsInWater
READ_FROM_FSTREAM(mFileStream, fillerByte); // useSightOfEvil
READ_FROM_FSTREAM(mFileStream, fillerByte); // useSpellsInBattle
READ_FROM_FSTREAM(mFileStream, fillerByte); // spellsPowerPreference
READ_FROM_FSTREAM(mFileStream, fillerByte); // useCallToArms
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FROM_FSTREAM(mFileStream, fillerWord); // mineGoldUntil
READ_FROM_FSTREAM(mFileStream, fillerWord); // waitSecondsAfterPreviousAttackBeforeAttackingAgain
READ_FSTREAM_U32(mFileStream, playerDef.mInitialMana);
READ_FROM_FSTREAM(mFileStream, fillerWord); // exploreUpToTilesToFindSpecials
READ_FROM_FSTREAM(mFileStream, fillerWord); // impTilesRatio
READ_FROM_FSTREAM(mFileStream, fillerWord); // buildStartX
READ_FROM_FSTREAM(mFileStream, fillerWord); // buildStartY
READ_FROM_FSTREAM(mFileStream, fillerWord); // buildEndX
READ_FROM_FSTREAM(mFileStream, fillerWord); // buildEndY
READ_FROM_FSTREAM(mFileStream, fillerByte); // likelyhoodToMovingCreaturesToLibraryForResearching
READ_FROM_FSTREAM(mFileStream, fillerByte); // chanceOfExploringToFindSpecials
READ_FROM_FSTREAM(mFileStream, fillerByte); // chanceOfFindingSpecialsWhenExploring
READ_FROM_FSTREAM(mFileStream, fillerByte); // fateOfImprisonedCreatures
READ_FROM_FSTREAM(mFileStream, fillerWord); // triggerId
unsigned char playerID;
READ_FSTREAM_U8(mFileStream, playerID);
if (!KWDParseENUM(playerID, playerDef.mPlayerId))
return false;
READ_FSTREAM_U16(mFileStream, playerDef.mStartCameraX); // cameraX
READ_FSTREAM_U16(mFileStream, playerDef.mStartCameraY); // cameraY
if (!ReadString8(32, playerDef.mPlayerName))
return false;
return true;
}
bool DK2ScenarioReader::ReadCreatureThingBehaviorFlags(ScenarioCreatureThing& creatureThingData)
{
unsigned char flags = 0;
READ_FSTREAM_U8(mFileStream, flags);
creatureThingData.mBehaviorFlags.mWillFight = CHECK_BIT_ON(flags, 0);
creatureThingData.mBehaviorFlags.mIsLeader = CHECK_BIT_ON(flags, 1);
creatureThingData.mBehaviorFlags.mIsFollower = CHECK_BIT_ON(flags, 2);
creatureThingData.mBehaviorFlags.mWillBeAttacked = CHECK_BIT_ON(flags, 3);
creatureThingData.mBehaviorFlags.mReturnToHeroLair = CHECK_BIT_ON(flags, 4);
creatureThingData.mBehaviorFlags.mFreeFriendsOnJailBreak = CHECK_BIT_ON(flags, 5);
creatureThingData.mBehaviorFlags.mActAsDropped = CHECK_BIT_ON(flags, 6);
creatureThingData.mBehaviorFlags.mStartAsDying = CHECK_BIT_ON(flags, 7);
return true;
}
bool DK2ScenarioReader::ReadCreatureThingData(ScenarioCreatureThing& creatureThingData)
{
READ_FSTREAM_I32(mFileStream, creatureThingData.mPositionX);
READ_FSTREAM_I32(mFileStream, creatureThingData.mPositionY);
READ_FSTREAM_I32(mFileStream, creatureThingData.mPositionZ);
READ_FSTREAM_U16(mFileStream, creatureThingData.mGoldHeld);
READ_FSTREAM_U8(mFileStream, creatureThingData.mLevel);
if (!ReadCreatureThingBehaviorFlags(creatureThingData))
return false;
READ_FSTREAM_I32(mFileStream, creatureThingData.mInitialHealth);
int objectiveTargetPlayerId;
READ_FSTREAM_I32(mFileStream, objectiveTargetPlayerId);
if (!KWDParseENUM(objectiveTargetPlayerId, creatureThingData.mObjectiveTargetPlayerId))
return false;
SKIP_FSTREAM_U16(mFileStream); // trigger id
READ_FSTREAM_U8(mFileStream, creatureThingData.mCreatureTypeId);
unsigned char playerId;
READ_FSTREAM_U8(mFileStream, playerId);
if (!KWDParseENUM(playerId, creatureThingData.mPlayerId))
return false;
return true;
}
bool DK2ScenarioReader::ReadObjectThingData(ScenarioObjectThing& objectThingData)
{
READ_FSTREAM_DATATYPE(mFileStream, objectThingData.mPositionX, int);
READ_FSTREAM_DATATYPE(mFileStream, objectThingData.mPositionY, int);
unsigned int unknownData;
READ_FROM_FSTREAM(mFileStream, unknownData);
int keeperSpellId = 0;
READ_FROM_FSTREAM(mFileStream, keeperSpellId);
objectThingData.mKeeperSpellId = keeperSpellId;
int moneyAmount = 0;
READ_FROM_FSTREAM(mFileStream, moneyAmount);
objectThingData.mMoneyAmount = moneyAmount;
unsigned short triggerId = 0;
READ_FROM_FSTREAM(mFileStream, triggerId);
objectThingData.mTriggerId = triggerId;
unsigned char objectId = 0;
READ_FROM_FSTREAM(mFileStream, objectId);
objectThingData.mObjectClassId = objectId;
unsigned char playerId = 0;
READ_FROM_FSTREAM(mFileStream, playerId);
if (!KWDParseENUM(playerId, objectThingData.mPlayerId))
return false;
return true;
}
bool DK2ScenarioReader::ReadRoomThingData(ScenarioRoomThing& roomThingData)
{
READ_FSTREAM_DATATYPE(mFileStream, roomThingData.mPositionX, int);
READ_FSTREAM_DATATYPE(mFileStream, roomThingData.mPositionY, int);
int unknownInt;
READ_FROM_FSTREAM(mFileStream, unknownInt);
short unknownShort;
READ_FROM_FSTREAM(mFileStream, unknownShort);
unsigned char direction;
READ_FROM_FSTREAM(mFileStream, direction);
if (!KWDParseENUM(direction, roomThingData.mDirection))
{
cxx_assert(false);
return false;
}
unsigned char unknownByte;
READ_FROM_FSTREAM(mFileStream, unknownByte);
READ_FSTREAM_DATATYPE(mFileStream, roomThingData.mInitialHealth, unsigned short);
unsigned char roomType = 0;
READ_FROM_FSTREAM(mFileStream, roomType);
if (!KWDRoomThingToRoomType(roomType, roomThingData.mRoomType))
{
cxx_assert(false);
return false;
}
unsigned char playerId = 0;
READ_FROM_FSTREAM(mFileStream, playerId);
if (!KWDParseENUM(playerId, roomThingData.mPlayerId))
{
cxx_assert(false);
return false;
}
return true;
}
bool DK2ScenarioReader::ReadThingsData(int numElements, ScenarioDefinition& scenarioData)
{
for (int ielement = 0; ielement < numElements; ++ielement)
{
unsigned int thingType = 0;
READ_FROM_FSTREAM(mFileStream, thingType);
unsigned int dataSize = 0;
READ_FROM_FSTREAM(mFileStream, dataSize);
bool wasSkipped = true;
switch (thingType)
{
case DK_OBJECT_THING:
{
wasSkipped = false;
if (!ReadObjectThingData(scenarioData.mObjectThings.emplace_back()))
{
scenarioData.mObjectThings.pop_back();
return false;
}
}
break;
case DK_TRAP_THING:
break;
case DK_DOOR_THING:
break;
case DK_ACTIONPOINT_THING:
break;
case DK_NEUTRAL_CREATURE_THING:
break;
case DK_GOOD_CREATURE_THING:
break;
case DK_CREATURE_THING:
{
wasSkipped = false;
if (!ReadCreatureThingData(scenarioData.mCreatureThings.emplace_back()))
{
scenarioData.mCreatureThings.pop_back();
return false;
}
}
break;
case DK_HEROPARTY_THING:
break;
case DK_DEAD_BODY_THING:
break;
case DK_EFFECT_GENERATOR_THING:
break;
case DK_ROOM_THING:
{
wasSkipped = false;
if (!ReadRoomThingData(scenarioData.mRoomThings.emplace_back()))
{
scenarioData.mRoomThings.pop_back();
return false;
}
}
break;
case DK_CAMERA_THING:
break;
default:
cxx_assert(false);
break;
}
if (wasSkipped)
{
SKIP_FSTREAM_BYTES(mFileStream, dataSize);
}
}
return true;
}
bool DK2ScenarioReader::ReadPlayersData(int numElements, ScenarioDefinition& scenarioData)
{
scenarioData.mPlayerDefs.resize(numElements + 1);
scenarioData.mPlayerDefs[0] = {}; // dummy element
// read definitions
for (int iplayer = 1; iplayer < numElements + 1; ++iplayer)
{
if (!ReadPlayerDefinition(scenarioData.mPlayerDefs[iplayer]))
return false;
bool correctId = (scenarioData.mPlayerDefs[iplayer].mPlayerId == iplayer);
cxx_assert(correctId);
}
return true;
}
bool DK2ScenarioReader::ReadRoomDefinition(RoomDefinition& roomDef)
{
if (!ReadString8(32, roomDef.mRoomName))
return false;
// resources
if (!ReadArtResource(roomDef.mGuiIcon))
return false;
if (!ReadArtResource(roomDef.mEditorIcon))
return false;
if (!ReadArtResource(roomDef.mCompleteResource))
return false;
if (!ReadArtResource(roomDef.mStraightResource))
return false;
if (!ReadArtResource(roomDef.mInsideCornerResource))
return false;
if (!ReadArtResource(roomDef.mUnknownResource))
return false;
if (!ReadArtResource(roomDef.mOutsideCornerResource))
return false;
if (!ReadArtResource(roomDef.mWallResource))
return false;
if (!ReadArtResource(roomDef.mCapResource))
return false;
if (!ReadArtResource(roomDef.mCeilingResource))
return false;
unsigned int ceilingHeight;
READ_FROM_FSTREAM(mFileStream, ceilingHeight);
unsigned short fillerWord;
READ_FROM_FSTREAM(mFileStream, fillerWord);
unsigned short torchIntensity;
READ_FROM_FSTREAM(mFileStream, torchIntensity);
if (!ReadRoomFlags(roomDef))
return false;
unsigned short tooltipStringId;
READ_FROM_FSTREAM(mFileStream, tooltipStringId);
unsigned short nameStringId;
READ_FROM_FSTREAM(mFileStream, nameStringId);
READ_FSTREAM_U16(mFileStream, roomDef.mCost);
unsigned short fightEffectId;
READ_FROM_FSTREAM(mFileStream, fightEffectId);
unsigned short generalDescriptionStringId;
READ_FROM_FSTREAM(mFileStream, generalDescriptionStringId);
unsigned short strenghtStringId;
READ_FROM_FSTREAM(mFileStream, strenghtStringId);
unsigned short torchRadius;
READ_FROM_FSTREAM(mFileStream, torchRadius);
unsigned short effects[8];
for (unsigned short& effectEntry : effects)
{
READ_FROM_FSTREAM(mFileStream, effectEntry);
}
READ_FSTREAM_U8(mFileStream, roomDef.mRoomType);
unsigned char fillerByte;
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FSTREAM_U8(mFileStream, roomDef.mTerrainType);
unsigned char tileConstruction;
READ_FSTREAM_U8(mFileStream, tileConstruction);
if (!KWDParseENUM(tileConstruction, roomDef.mTileConstruction))
return false;
unsigned char createdCreatureId;
READ_FROM_FSTREAM(mFileStream, createdCreatureId);
unsigned char torchColor[3];
for (unsigned char& colorComponent : torchColor)
{
READ_FROM_FSTREAM(mFileStream, colorComponent);
}
for (GameObjectClassId& objectid : roomDef.mObjectIds)
{
READ_FSTREAM_U8(mFileStream, objectid);
}
if (!ReadString8(32, roomDef.mSoundCategory))
return false;
READ_FSTREAM_U8(mFileStream, roomDef.mOrderInEditor);
READ_FROM_FSTREAM(mFileStream, fillerByte);
READ_FROM_FSTREAM(mFileStream, fillerWord);
READ_FROM_FSTREAM(mFileStream, fillerByte);
if (!ReadArtResource(roomDef.mTorchResource))
return false;
READ_FSTREAM_U8(mFileStream, roomDef.mRecommendedSizeX);
READ_FSTREAM_U8(mFileStream, roomDef.mRecommendedSizeY);
short healthGain;
READ_FROM_FSTREAM(mFileStream, healthGain);
return true;
}
bool DK2ScenarioReader::ReadRoomsData(int numElements, ScenarioDefinition& scenarioData)
{
scenarioData.mRoomDefs.resize(numElements + 1);
scenarioData.mRoomDefs[0] = {}; // dummy element
// read definitions
for (int iroom = 1; iroom < numElements + 1; ++iroom)
{
if (!ReadRoomDefinition(scenarioData.mRoomDefs[iroom]))
return false;
bool correctId = (scenarioData.mRoomDefs[iroom].mRoomType == iroom);
cxx_assert(correctId);
}
return true;
}
bool DK2ScenarioReader::ReadCreaturesDefinition(CreatureDefinition& creature)
{
if (!ReadString8(32, creature.mCreatureName))
return false;
SKIP_FSTREAM_BYTES(mFileStream, 84); // unknown data
// parse primary animations
for (int ianim = 0; ianim < 36; ++ianim)
{
if (!ReadArtResource(creature.mAnimationResources[ianim]))
return false;
}
if (!ReadArtResource(creature.mIcon1Resource))
return false;
if (!ReadArtResource(creature.mIcon2Resource))
return false;
SKIP_FSTREAM_U16(mFileStream);
SKIP_FSTREAM_U32(mFileStream);
SKIP_FSTREAM_U32(mFileStream);
READ_FSTREAM_U8(mFileStream, creature.mOrderInEditor);
READ_FSTREAM_U16(mFileStream, creature.mAngerStringIdGeneral);
if (!Read32bitsFloat(creature.mShotDelay))
return false;
READ_FSTREAM_U16(mFileStream, creature.mOlhiEffectId);
READ_FSTREAM_U16(mFileStream, creature.mIntroductionStringId);
if (!Read32bitsFloat(creature.mPerceptionRange))
return false;
READ_FSTREAM_U16(mFileStream, creature.mAngerStringIdLair);
READ_FSTREAM_U16(mFileStream, creature.mAngerStringIdFood);
READ_FSTREAM_U16(mFileStream, creature.mAngerStringIdPay);
READ_FSTREAM_U16(mFileStream, creature.mAngerStringIdWork);
READ_FSTREAM_U16(mFileStream, creature.mAngerStringIdSlap);
READ_FSTREAM_U16(mFileStream, creature.mAngerStringIdHeld);
READ_FSTREAM_U16(mFileStream, creature.mAngerStringIdLonely);
READ_FSTREAM_U16(mFileStream, creature.mAngerStringIdHatred);
READ_FSTREAM_U16(mFileStream, creature.mAngerStringIdTorture);
if (!ReadString8(32, creature.mTranslationSoundGategory))
return false;
if (!Read32bitsFloat(creature.mShuffleSpeed))
return false;
READ_FSTREAM_U8(mFileStream, creature.mCloneCreatureTypeId);
SKIP_FSTREAM_U8(mFileStream); // first person gamma effect
unsigned char fpWalkCycleScale;
READ_FSTREAM_U8(mFileStream, fpWalkCycleScale); // first person walk cycle scale, Movement
SKIP_FSTREAM_U8(mFileStream); // intro camera path index
SKIP_FSTREAM_U8(mFileStream);
if (!ReadArtResource(creature.mPortraitResource))
return false;
if (!ReadLight()) return false;
// max 2 attractions
if (!ReadCreatureAttraction()) return false;
if (!ReadCreatureAttraction()) return false;
float firstPersonWaddleScale; // movement
if (!Read32bitsFloat(firstPersonWaddleScale))
return false;
float firstPersonOscillateScale; // movement
if (!Read32bitsFloat(firstPersonOscillateScale))
return false;
// max 3 spells
if (!ReadCreatureSpell()) return false;
if (!ReadCreatureSpell()) return false;
if (!ReadCreatureSpell()) return false;
// max 4 resistances
if (!ReadCreatureResistance()) return false;
if (!ReadCreatureResistance()) return false;
if (!ReadCreatureResistance()) return false;
if (!ReadCreatureResistance()) return false;
// max 3 happy jobs
if (!ReadCreatureJobPreference()) return false;
if (!ReadCreatureJobPreference()) return false;
if (!ReadCreatureJobPreference()) return false;
// max 2 unhappy jobs
if (!ReadCreatureJobPreference()) return false;
if (!ReadCreatureJobPreference()) return false;
// max 3 angry jobs
if (!ReadCreatureJobPreference()) return false;
if (!ReadCreatureJobPreference()) return false;
if (!ReadCreatureJobPreference()) return false;
eCreatureJob hateJobs[2];
for (int i = 0; i < CountOf(hateJobs); ++i)
{
unsigned int jobType;
READ_FROM_FSTREAM(mFileStream, jobType);
if (!KWDParseENUM(jobType, hateJobs[i]))
{
cxx_assert(false);
}
}
// max 3 alternative jobs
if (!ReadCreatureJobAlternative()) return false;
if (!ReadCreatureJobAlternative()) return false;
if (!ReadCreatureJobAlternative()) return false;
if (!ReadVector3f(creature.mAnimationOffset))
return false;
SKIP_FSTREAM_U32(mFileStream);
if (!Read32bitsFloat(creature.mHeight))
return false;
SKIP_FSTREAM_U32(mFileStream); // float32 ?
SKIP_FSTREAM_U32(mFileStream);
if (!Read32bitsFloat(creature.mEyeHeight))
return false;
if (!Read32bitsFloat(creature.mSpeed))
return false;
if (!Read32bitsFloat(creature.mRunSpeed))
return false;
if (!Read32bitsFloat(creature.mHungerRate))
return false;
READ_FSTREAM_I32(mFileStream, creature.mTimeAwake);
READ_FSTREAM_I32(mFileStream, creature.mTimeSleep);
if (!Read32bitsFloat(creature.mDistanceCanSee))
return false;
if (!Read32bitsFloat(creature.mDistanceCanHear))
return false;
if (!Read32bitsFloat(creature.mStunDuration))
return false;
if (!Read32bitsFloat(creature.mGuardDuration))
return false;
if (!Read32bitsFloat(creature.mIdleDuration))
return false;
if (!Read32bitsFloat(creature.mSlapFearlessDuration))
return false;
SKIP_FSTREAM_U32(mFileStream);
SKIP_FSTREAM_U32(mFileStream);
SKIP_FSTREAM_U16(mFileStream); // possessionManaCost
SKIP_FSTREAM_U16(mFileStream); // ownLandHealthIncrease
if (!Read32bitsFloat(creature.mMeleeRange))
return false;
SKIP_FSTREAM_U32(mFileStream);
if (!Read32bitsFloat(creature.mTortureTimeToConvert))
return false;
if (!Read32bitsFloat(creature.mMeleeRecharge))
return false;
if (!ReadCreatureFlags(creature))
return false;
unsigned short expForNextLevel;
READ_FROM_FSTREAM(mFileStream, expForNextLevel);
unsigned char jobClass;
READ_FROM_FSTREAM(mFileStream, jobClass);
if (!KWDParseENUM(jobClass, creature.mJobClass))
return false;
unsigned char fightStyle;
READ_FROM_FSTREAM(mFileStream, fightStyle);
unsigned short expPerSecond;
READ_FROM_FSTREAM(mFileStream, expPerSecond);
unsigned short expPerSecondTraining;
READ_FROM_FSTREAM(mFileStream, expPerSecondTraining);
unsigned short researchPerSecond;
READ_FROM_FSTREAM(mFileStream, researchPerSecond);
unsigned short manufacturePerSecond;
READ_FROM_FSTREAM(mFileStream, manufacturePerSecond);
READ_FSTREAM_U16(mFileStream, creature.mHp);
READ_FSTREAM_U16(mFileStream, creature.mHpFromChicken);
READ_FSTREAM_U16(mFileStream, creature.mFear);
READ_FSTREAM_U16(mFileStream, creature.mThreat);
READ_FSTREAM_U16(mFileStream, creature.mMeleeDamage);
READ_FSTREAM_U16(mFileStream, creature.mSlapDamage);
READ_FSTREAM_U16(mFileStream, creature.mManaGenPrayer);
SKIP_FSTREAM_U16(mFileStream);
READ_FSTREAM_U16(mFileStream, creature.mPay);
READ_FSTREAM_U16(mFileStream, creature.mMaxGoldHeld);
SKIP_FSTREAM_U16(mFileStream); // float16?
READ_FSTREAM_U16(mFileStream, creature.mDecomposeValue);
READ_FSTREAM_U16(mFileStream, creature.mNameStringId);
READ_FSTREAM_U16(mFileStream, creature.mTooltipStringId);
READ_FSTREAM_U16(mFileStream, creature.mAngerNoLair);
READ_FSTREAM_U16(mFileStream, creature.mAngerNoFood);
READ_FSTREAM_U16(mFileStream, creature.mAngerNoPay);
READ_FSTREAM_U16(mFileStream, creature.mAngerNoWork);
READ_FSTREAM_U16(mFileStream, creature.mAngerSlap);
READ_FSTREAM_U16(mFileStream, creature.mAngerInHand);
READ_FSTREAM_U16(mFileStream, creature.mInitialGoldHeld);
SKIP_FSTREAM_U16(mFileStream); // entrance effect id
READ_FSTREAM_U16(mFileStream, creature.mGeneralDescriptionStringId);
READ_FSTREAM_U16(mFileStream, creature.mStrengthStringId);
READ_FSTREAM_U16(mFileStream, creature.mWeaknessStringId);
SKIP_FSTREAM_U16(mFileStream); // slap effect id
SKIP_FSTREAM_U16(mFileStream); // death effect id
SKIP_FSTREAM_U8(mFileStream); // melee1Swipe, Swipes, 1st person attacks
SKIP_FSTREAM_U8(mFileStream); // melee2Swipe
SKIP_FSTREAM_U8(mFileStream); // melee3Swipe
SKIP_FSTREAM_U8(mFileStream); // spellSwipe
SKIP_FSTREAM_U8(mFileStream); // firstPersonSpecialAbility1
SKIP_FSTREAM_U8(mFileStream); // firstPersonSpecialAbility2
SKIP_FSTREAM_BYTES(mFileStream, 3);
READ_FSTREAM_U8(mFileStream, creature.mCreatureTypeId);
SKIP_FSTREAM_U8(mFileStream);
SKIP_FSTREAM_U8(mFileStream);
SKIP_FSTREAM_U8(mFileStream); // hungerFill
SKIP_FSTREAM_U8(mFileStream); // unhappy threshold
SKIP_FSTREAM_U8(mFileStream); // meleeAttackType
SKIP_FSTREAM_U8(mFileStream);
READ_FSTREAM_U8(mFileStream, creature.mLairObjectId);
SKIP_FSTREAM_U8(mFileStream);
SKIP_FSTREAM_U8(mFileStream); // death fall direction
SKIP_FSTREAM_U8(mFileStream);
if (!ReadString8(32, creature.mSoundCategory))
return false;
unsigned char armorMaterial;
READ_FSTREAM_U8(mFileStream, armorMaterial);
if (!KWDParseENUM(armorMaterial, creature.mArmourType))
return false;
if (!ReadArtResource(creature.mFirstPersonFilterResource))
return false;
SKIP_FSTREAM_U16(mFileStream);
float unknownFloat;
if (!Read32bitsFloat(unknownFloat))
return false;
if (!ReadArtResource(creature.mAnimationResources[CreatureAnimation_DrunkIdle]))
return false;
SKIP_FSTREAM_U8(mFileStream); // special1swipe
SKIP_FSTREAM_U8(mFileStream); // special2swipe
if (!ReadArtResource(creature.mFirstPersonMeleeResource))
return false;
SKIP_FSTREAM_U32(mFileStream);
READ_FSTREAM_I16(mFileStream, creature.mTortureHpChange);
READ_FSTREAM_I16(mFileStream, creature.mTortureMoodChange);
if (!ReadArtResource(creature.mAnimationResources[CreatureAnimation_Melee2]))
return false;
if (!ReadArtResource(creature.mAnimationResources[CreatureAnimation_Special4]))
return false;
if (!ReadArtResource(creature.mAnimationResources[CreatureAnimation_Special5]))
return false;
if (!ReadArtResource(creature.mAnimationResources[CreatureAnimation_Special6]))
return false;
if (!ReadArtResource(creature.mAnimationResources[CreatureAnimation_Special7]))
return false;
if (!ReadArtResource(creature.mAnimationResources[CreatureAnimation_Special8]))
return false;
// 7 anim offsets
cxx_assert(CountOf(creature.mAnimationOffsets) == 7);
for (int i = 0; i < CountOf(creature.mAnimationOffsets); ++i)
{
if (!ReadVector3f(creature.mAnimationOffsets[i]))
return false;
}
if (!ReadArtResource(creature.mAnimationResources[CreatureAnimation_WalkBack]))
return false;
SKIP_FSTREAM_BYTES(mFileStream, 48 * 4);
if (!ReadArtResource(creature.mAnimationResources[CreatureAnimation_Pose_Frame]))
return false;
if (!ReadArtResource(creature.mAnimationResources[CreatureAnimation_Walk2]))
return false;
if (!ReadArtResource(creature.mAnimationResources[CreatureAnimation_Die_Pose]))
return false;
READ_FSTREAM_U16(mFileStream, creature.mUniqueNameTextId);
SKIP_FSTREAM_BYTES(mFileStream, 2 * 4);
SKIP_FSTREAM_U32(mFileStream); // firstPersonSpecialAbility1Count
SKIP_FSTREAM_U32(mFileStream); // firstPersonSpecialAbility2Count
if (!ReadArtResource(creature.mUniqueResource))
return false;
return true;
}
bool DK2ScenarioReader::ReadCreaturesData(const KWDFileHeader& header, ScenarioDefinition& scenarioData)
{
scenarioData.mCreatureDefs.resize(header.mItemsCount + 1);
scenarioData.mCreatureDefs[0] = {}; // dummy element
std::streamoff startoffset = mFileStream.tellg();
std::streamoff elementSize = header.mContentSize / header.mItemsCount;
// read definitions
for (int icreature = 1; icreature < header.mItemsCount + 1; ++icreature)
{
std::streamoff itemoffset = mFileStream.tellg();
if (!ReadCreaturesDefinition(scenarioData.mCreatureDefs[icreature]))
return false;
std::streamoff deltaoffset = mFileStream.tellg() - itemoffset;
cxx_assert(deltaoffset mPillarObjectId = GameObjectClassId_WorkshopPillar;
}
if (RoomDefinition* definition = scenarioData.GetRoomDefinition(RoomTypeId_Treasury))
{
definition->mPillarObjectId = GameObjectClassId_TreasuryPillar;
}
if (RoomDefinition* definition = scenarioData.GetRoomDefinition(RoomTypeId_Hatchery))
{
definition->mPillarObjectId = GameObjectClassId_HatcheryPillar;
}
if (RoomDefinition* definition = scenarioData.GetRoomDefinition(RoomTypeId_Casino))
{
definition->mPillarObjectId = GameObjectClassId_CasinoPillar;
}
if (RoomDefinition* definition = scenarioData.GetRoomDefinition(RoomTypeId_CombatPit))
{
definition->mPillarObjectId = GameObjectClassId_PitPillar;
}
if (RoomDefinition* definition = scenarioData.GetRoomDefinition(RoomTypeId_Graveyard))
{
definition->mPillarObjectId = GameObjectClassId_GraveyardPillar;
}
if (RoomDefinition* definition = scenarioData.GetRoomDefinition(RoomTypeId_TortureChamber))
{
definition->mPillarObjectId = GameObjectClassId_TorturePillar;
}
if (RoomDefinition* definition = scenarioData.GetRoomDefinition(RoomTypeId_Temple))
{
definition->mPillarObjectId = GameObjectClassId_TempleCandlestick;
}
// 3 enable walls for hero gate rooms
for (RoomDefinition& roller: scenarioData.mRoomDefs)
{
if (roller.mHasWalls) continue;
if ((roller.mTileConstruction == eRoomTileConstruction_HeroGateFrontend) ||
(roller.mTileConstruction == eRoomTileConstruction_HeroGate_3_by_1))
{
roller.mHasWalls = true;
}
}
// 3 creatures
if (JsonElement creaturesRoot = extensionsData.GetRootElement().FindElement("creatures"))
{
cxx_assert(creaturesRoot.IsObject());
for (CreatureDefinition& roller: scenarioData.mCreatureDefs)
{
if (roller.mCreatureName.empty()) continue;
if (JsonElement creatureExtData = creaturesRoot.FindElement(roller.mCreatureName))
{
// animations
float walkAnimScale = 1.0f;
if (JsonQuery(creatureExtData, "walk_anim_scale", walkAnimScale) && (walkAnimScale > 0.0f))
{
roller.mWalkAnimScale = walkAnimScale;
}
}
}
}
}
bool DK2ScenarioReader::ReadScenarioData(const std::string& filePath, ScenarioDefinition& scenarioData)
{
const std::string levelName = FSGetFileNameWithoutExtension(filePath);
// open file stream
mFileStream.open(filePath, std::ios::in | std::ios::binary);
if (!mFileStream.is_open())
return false;
std::vector paths;
if (!ReadMapInfo(scenarioData, paths))
{
gConsole.LogMessage(eLogLevel_Warning, "Error reading scenario data info from '%s'", levelName.c_str());
return false;
}
mFileStream.close();
// read data from data files
for (const LevelDataFilePath& pathEntry: paths)
{
std::string dataFilePath;
bool isPathLocated = gFiles.LocateMapData(pathEntry.mFilePath, dataFilePath);
if (isPathLocated && (pathEntry.nDataTypeId == DKLD_GLOBALS))
{
gConsole.LogMessage(eLogLevel_Info, "Scanario overrides globals");
}
if (!isPathLocated)
{
if (pathEntry.nDataTypeId != DKLD_GLOBALS)
{
gConsole.LogMessage(eLogLevel_Warning, "Cannot locate scenario data file '%s'", pathEntry.mFilePath.c_str());
}
continue;
}
mFileStream.open(dataFilePath, std::ios::in | std::ios::binary);
if (mFileStream.is_open())
{
if (!ReadDataFile(scenarioData))
{
gConsole.LogMessage(eLogLevel_Warning, "Error reading scenario data file '%s'", pathEntry.mFilePath.c_str());
return false;
}
mFileStream.close();
}
}
if (!ExploreTerrainTypes(scenarioData))
{
gConsole.LogMessage(eLogLevel_Warning, "Error exploring scenario terrain types");
return false;
}
std::string extensionsFilePath;
JsonDocument extensionsData;
if (!gFiles.PathToFile("gameplay/extensions.json", extensionsFilePath) || !FSLoadJSON(extensionsFilePath, extensionsData))
{
gConsole.LogMessage(eLogLevel_Warning, "Error loading gameplay extensions data");
}
ApplyExtensions(extensionsData, scenarioData);
return true;
}