diff --git a/Documentation/Changes.txt b/Documentation/Changes.txt index e307f123f..1d23df200 100644 --- a/Documentation/Changes.txt +++ b/Documentation/Changes.txt @@ -2,6 +2,7 @@ Version 1.0.6 ============= * Fix major pathfinding bug which could have caused lots of issues with enemy behaviour. +* Fix potential random crashes due to incorrect rendering behaviour. * Fix savegame crash for disabled enemies with partially set activation mask. * Fix certain enemies not damaging Lara if binoculars or lasersight mode is active. * Fix invisible Lara after starting a new game from title flyby with hidden Lara. @@ -10,13 +11,19 @@ Version 1.0.6 * Fix backholster weapons not updating their sound position together with player. * Fix black screen bug when there was an obstacle between the fixed camera and the target. * Fix underwater caustics not appearing without visiting options menu beforehand. -* Fix TR1 BIG_RAT which crashed the game when it was killed. -* Fix TR4 SKELETON spawn when used with OCB 3 -* Fix TR4 SAS teleporting over the blocks he walks by. * Fix enemy projectile effect colours. +* Fix TR1 rat which crashed the game when it was killed. +* Fix TR1 ape climbing. +* Fix TR2 small spider climbing and pathfinding. +* Fix TR4 skeleton spawn when used with OCB 3. +* Fix TR4 SAS teleporting over the blocks he walks by. +* Fix TR3 Shiva and TR4 baddy 2 not blocking bullets. * Restore original volumetric explosion effects. +* Add TR3 lizard and Puna. +* Add TR3 boss effects in ID_BOSS_SHIELD and ID_BOSS_SHOCKWAVE_EXPLOSION slots. * Add undead flag to TR4 sphinx to prevent it from becoming bugged after receiving a lot of damage. * Add an option to activate Lua or node events from legacy triggers. +* Add more warnings in logs to enemies which animation or required slot is missing. * Antitriggering an enemy will now cause it to vanish and pause. * Re-triggering an enemy will cause it to reappear and unpause. * Lua Moveable functions Enable and Disable now correctly trigger and antitrigger the moveable. diff --git a/TombEngine/Game/Lara/lara_fire.cpp b/TombEngine/Game/Lara/lara_fire.cpp index 2ed995109..80da21cb4 100644 --- a/TombEngine/Game/Lara/lara_fire.cpp +++ b/TombEngine/Game/Lara/lara_fire.cpp @@ -1049,29 +1049,31 @@ void LaraTargetInfo(ItemInfo* laraItem, const WeaponInfo& weaponInfo) void HitTarget(ItemInfo* laraItem, ItemInfo* targetEntity, GameVector* hitPos, int damage, bool isExplosive) { - const auto& lara = *GetLaraInfo(laraItem); const auto& object = Objects[targetEntity->ObjectNumber]; targetEntity->HitStatus = true; - if (targetEntity->IsCreature()) GetCreatureInfo(targetEntity)->HurtByLara = true; if (hitPos != nullptr) { - int foundJointID = -1; - for (int jointID = 0; jointID < object.nmeshes; jointID++) + hitPos->RoomNumber = targetEntity->RoomNumber; + + int foundJointIndex = -1; + for (int jointIndex = 0; jointIndex < object.nmeshes; jointIndex++) { - auto pos = GetJointPosition(targetEntity, jointID); - float distance = targetEntity->ItemFlags[7] != 0 ? (float)targetEntity->ItemFlags[7] : BLOCK(1 / 16.0f); - if (Vector3i::Distance(hitPos->ToVector3i(), pos) < distance) + const auto& mesh = g_Level.Meshes[object.meshIndex + jointIndex]; + auto jointPos = GetJointPosition(targetEntity, jointIndex); + + float distance = Vector3::Distance(hitPos->ToVector3(), jointPos.ToVector3()); + if (distance < mesh.sphere.Radius) { - foundJointID = jointID; + foundJointIndex = jointIndex; break; } } - object.HitRoutine(*targetEntity, *laraItem, *hitPos, damage, isExplosive, foundJointID); + object.HitRoutine(*targetEntity, *laraItem, *hitPos, damage, isExplosive, foundJointIndex); } else { diff --git a/TombEngine/Game/animation.cpp b/TombEngine/Game/animation.cpp index dca56470a..7365a00df 100644 --- a/TombEngine/Game/animation.cpp +++ b/TombEngine/Game/animation.cpp @@ -319,6 +319,11 @@ bool HasStateDispatch(ItemInfo* item, int targetState) return false; } +bool TestAnimNumber(const ItemInfo& item, int animNumber) +{ + return (item.Animation.AnimNumber == (Objects[item.ObjectNumber].animIndex + animNumber)); +} + bool TestLastFrame(ItemInfo* item, int animNumber) { if (animNumber == NO_ANIM) @@ -328,10 +333,22 @@ bool TestLastFrame(ItemInfo* item, int animNumber) return false; const auto& anim = g_Level.Anims[animNumber]; - return (item->Animation.FrameNumber >= anim.frameEnd); } +bool TestAnimFrame(const ItemInfo& item, int frameStart) +{ + const auto& anim = g_Level.Anims[item.Animation.AnimNumber]; + return (item.Animation.FrameNumber == (anim.frameBase + frameStart)); +} + +bool TestAnimFrameRange(const ItemInfo& item, int frameStart, int frameEnd) +{ + const auto& anim = g_Level.Anims[item.Animation.AnimNumber]; + return (item.Animation.FrameNumber >= (anim.frameBase + frameStart) && + item.Animation.FrameNumber <= (anim.frameBase + frameEnd)); +} + void TranslateItem(ItemInfo* item, short headingAngle, float forward, float down, float right) { item->Pose.Translate(headingAngle, forward, down, right); diff --git a/TombEngine/Game/animation.h b/TombEngine/Game/animation.h index 1505515d7..7463a52db 100644 --- a/TombEngine/Game/animation.h +++ b/TombEngine/Game/animation.h @@ -81,7 +81,10 @@ void AnimateLara(ItemInfo* item); void AnimateItem(ItemInfo* item); bool HasStateDispatch(ItemInfo* item, int targetState = NO_STATE); +bool TestAnimNumber(const ItemInfo& item, int animNumber); bool TestLastFrame(ItemInfo* item, int animNumber = NO_ANIM); +bool TestAnimFrame(const ItemInfo& item, int frameStart); +bool TestAnimFrameRange(const ItemInfo& item, int frameStart, int frameEnd); void TranslateItem(ItemInfo* item, short headingAngle, float forward, float down = 0.0f, float right = 0.0f); void TranslateItem(ItemInfo* item, const EulerAngles& orient, float distance); diff --git a/TombEngine/Game/control/box.cpp b/TombEngine/Game/control/box.cpp index ba2f538e0..1296ab744 100644 --- a/TombEngine/Game/control/box.cpp +++ b/TombEngine/Game/control/box.cpp @@ -26,7 +26,7 @@ constexpr auto REACHED_GOAL_RADIUS = 640; constexpr auto ATTACK_RANGE = SQUARE(SECTOR(3)); constexpr auto ESCAPE_CHANCE = 0x800; constexpr auto RECOVER_CHANCE = 0x100; -constexpr auto BIFF_AVOID_TURN = 1536; +constexpr auto BIFF_AVOID_TURN = ANGLE(11.25f); constexpr auto FEELER_DISTANCE = CLICK(2); constexpr auto FEELER_ANGLE = ANGLE(45.0f); constexpr auto CREATURE_AI_ROTATION_MAX = ANGLE(90.0f); @@ -41,34 +41,6 @@ constexpr auto FRAME_PRIO_BASE = 4; constexpr auto FRAME_PRIO_EXP = 1.5; #endif // CREATURE_AI_PRIORITY_OPTIMIZATION -// TODO: Do it via Lua instead. -- TokyoSU 22.12.21 -bool IsCreatureVaultAvailable(ItemInfo* item, int stepCount) -{ - switch (stepCount) - { - case -4: - return (item->ObjectNumber != ID_SMALL_SPIDER); - - case -3: - return (item->ObjectNumber != ID_CIVVY && - item->ObjectNumber != ID_MP_WITH_STICK && - item->ObjectNumber != ID_YETI && - item->ObjectNumber != ID_APE && - item->ObjectNumber != ID_SMALL_SPIDER); - - case -2: - return (item->ObjectNumber != ID_BADDY1 && - item->ObjectNumber != ID_BADDY2 && - item->ObjectNumber != ID_CIVVY && - item->ObjectNumber != ID_MP_WITH_STICK && - item->ObjectNumber != ID_YETI && - item->ObjectNumber != ID_APE && - item->ObjectNumber != ID_SMALL_SPIDER); - } - - return true; -} - void DrawBox(int boxIndex, Vector3 color) { if (boxIndex == NO_BOX) @@ -719,7 +691,7 @@ void CreatureFloat(short itemNumber) } } -void CreatureJoint(ItemInfo* item, short joint, short required) +void CreatureJoint(ItemInfo* item, short joint, short required, short maxAngle) { if (!item->IsCreature()) return; @@ -733,10 +705,10 @@ void CreatureJoint(ItemInfo* item, short joint, short required) change = ANGLE(-3.0f); creature->JointRotation[joint] += change; - if (creature->JointRotation[joint] > ANGLE(70.0f)) - creature->JointRotation[joint] = ANGLE(70.0f); - else if (creature->JointRotation[joint] < -ANGLE(70.0f)) - creature->JointRotation[joint] = -ANGLE(70.0f); + if (creature->JointRotation[joint] > maxAngle) + creature->JointRotation[joint] = maxAngle; + else if (creature->JointRotation[joint] < -maxAngle) + creature->JointRotation[joint] = -maxAngle; } void CreatureTilt(ItemInfo* item, short angle) @@ -925,21 +897,20 @@ bool ValidBox(ItemInfo* item, short zoneNumber, short boxNumber) if (boxNumber == NO_BOX) return false; - auto* object = &Objects[item->ObjectNumber]; - auto* creature = GetCreatureInfo(item); - auto* zone = g_Level.Zones[(int)creature->LOT.Zone][FlipStatus].data(); + const auto& creature = *GetCreatureInfo(item); + const auto& zone = g_Level.Zones[(int)creature.LOT.Zone][FlipStatus].data(); - if (creature->LOT.Fly == NO_FLYING && zone[boxNumber] != zoneNumber) + if (creature.LOT.Fly == NO_FLYING && zone[boxNumber] != zoneNumber) return false; - auto* box = &g_Level.Boxes[boxNumber]; - if (creature->LOT.BlockMask & box->flags) + const auto& box = g_Level.Boxes[boxNumber]; + if (creature.LOT.BlockMask & box.flags) return false; - if (item->Pose.Position.z > (box->left * SECTOR(1)) && - item->Pose.Position.z < (box->right * SECTOR(1)) && - item->Pose.Position.x > (box->top * SECTOR(1)) && - item->Pose.Position.x < (box->bottom * SECTOR(1))) + if (item->Pose.Position.z > (box.left * BLOCK(1)) && + item->Pose.Position.z < (box.right * BLOCK(1)) && + item->Pose.Position.x > (box.top * BLOCK(1)) && + item->Pose.Position.x < (box.bottom * BLOCK(1))) { return false; } @@ -951,9 +922,10 @@ bool EscapeBox(ItemInfo* item, ItemInfo* enemy, int boxNumber) { if (boxNumber == NO_BOX) return false; - auto* box = &g_Level.Boxes[boxNumber]; - int x = (box->top + box->bottom) * SECTOR(1) / 2 - enemy->Pose.Position.x; - int z = (box->left + box->right) * SECTOR(1) / 2 - enemy->Pose.Position.z; + + const auto& box = g_Level.Boxes[boxNumber]; + int x = ((box.top + box.bottom) * BLOCK(0.5f)) - enemy->Pose.Position.x; + int z = ((box.left + box.right) * BLOCK(0.5f)) - enemy->Pose.Position.z; if (x > -ESCAPE_DIST && x < ESCAPE_DIST && z > -ESCAPE_DIST && z < ESCAPE_DIST) @@ -1114,8 +1086,8 @@ bool CreatureActive(short itemNumber) if (!Objects[item->ObjectNumber].intelligent) return false; - // Object is already dead or body cleared. - if (item->Flags & IFLAG_KILLED || item->Flags & IFLAG_CLEAR_BODY) + // Object is already dead. + if (item->Flags & IFLAG_KILLED) return false; if (item->Status == ITEM_INVISIBLE || !item->IsCreature()) @@ -1180,6 +1152,36 @@ bool StalkBox(ItemInfo* item, ItemInfo* enemy, int boxNumber) return true; } +// TODO: Do it via Lua instead. -- TokyoSU 22.12.21 +bool IsCreatureVaultAvailable(ItemInfo* item, int stepCount) +{ + switch (stepCount) + { + case -4: + return (item->ObjectNumber != ID_SMALL_SPIDER); + + case -3: + return (item->ObjectNumber != ID_CIVVY && + item->ObjectNumber != ID_MP_WITH_STICK && + item->ObjectNumber != ID_YETI && + item->ObjectNumber != ID_LIZARD && + item->ObjectNumber != ID_APE && + item->ObjectNumber != ID_SMALL_SPIDER); + + case -2: + return (item->ObjectNumber != ID_BADDY1 && + item->ObjectNumber != ID_BADDY2 && + item->ObjectNumber != ID_CIVVY && + item->ObjectNumber != ID_MP_WITH_STICK && + item->ObjectNumber != ID_YETI && + item->ObjectNumber != ID_LIZARD && + item->ObjectNumber != ID_APE && + item->ObjectNumber != ID_SMALL_SPIDER); + } + + return true; +} + int CreatureVault(short itemNumber, short angle, int vault, int shift) { auto* item = &g_Level.Items[itemNumber]; @@ -1192,8 +1194,7 @@ int CreatureVault(short itemNumber, short angle, int vault, int shift) CreatureAnimation(itemNumber, angle, 0); - // FIXME: Add climb down animations for Von Croy and baddies? - if (item->Floor > y + CLICK(4.5f)) + if (item->Floor > (y + CLICK(4.5f))) { vault = 0; } @@ -1439,9 +1440,7 @@ void FindAITargetObject(CreatureInfo* creature, short objectNumber) aiItem->ObjectNumber = foundObject->objectNumber; aiItem->RoomNumber = foundObject->roomNumber; - aiItem->Pose.Position.x = foundObject->pos.Position.x; - aiItem->Pose.Position.y = foundObject->pos.Position.y; - aiItem->Pose.Position.z = foundObject->pos.Position.z; + aiItem->Pose.Position = foundObject->pos.Position; aiItem->Pose.Orientation.y = foundObject->pos.Orientation.y; aiItem->Flags = foundObject->flags; aiItem->TriggerFlags = foundObject->triggerFlags; @@ -1462,7 +1461,7 @@ int TargetReachable(ItemInfo* item, ItemInfo* enemy) auto& room = g_Level.Rooms[enemy->RoomNumber]; auto* floor = GetSector(&room, enemy->Pose.Position.x - room.x, enemy->Pose.Position.z - room.z); - // NEW: Only update enemy box number if it is actually reachable by enemy. + // NEW: Only update enemy box number if it is actually reachable by the enemy. // This prevents enemies from running to the player and attacking nothing when they are hanging or shimmying. -- Lwmte, 27.06.22 bool isReachable = false; @@ -1476,7 +1475,7 @@ int TargetReachable(ItemInfo* item, ItemInfo* enemy) { auto pointColl = GetCollision(floor, enemy->Pose.Position.x, enemy->Pose.Position.y, enemy->Pose.Position.z); auto bounds = GameBoundingBox(item); - isReachable = ((abs(enemy->Pose.Position.y - pointColl.Position.Floor)) < bounds.GetHeight()); + isReachable = abs(enemy->Pose.Position.y - pointColl.Position.Floor) < bounds.GetHeight(); } return (isReachable ? floor->Box : item->BoxNumber); diff --git a/TombEngine/Game/control/box.h b/TombEngine/Game/control/box.h index 3194d28be..ca6a24d9d 100644 --- a/TombEngine/Game/control/box.h +++ b/TombEngine/Game/control/box.h @@ -170,7 +170,7 @@ short CreatureEffect2(ItemInfo* item, BiteInfo bite, short velocity, short angle short CreatureEffect(ItemInfo* item, BiteInfo bite, std::function func); void CreatureUnderwater(ItemInfo* item, int depth); void CreatureFloat(short itemNumber); -void CreatureJoint(ItemInfo* item, short joint, short required); +void CreatureJoint(ItemInfo* item, short joint, short required, short maxAngle = ANGLE(70.0f)); void CreatureTilt(ItemInfo* item, short angle); short CreatureTurn(ItemInfo* item, short maxTurn); void CreatureDie(short itemNumber, bool explode); diff --git a/TombEngine/Game/effects/effects.cpp b/TombEngine/Game/effects/effects.cpp index 4de51079d..0b6b33d2d 100644 --- a/TombEngine/Game/effects/effects.cpp +++ b/TombEngine/Game/effects/effects.cpp @@ -244,18 +244,18 @@ void UpdateSparks() if (spark->sLife - spark->life == spark->extras >> 3 && spark->extras & 7) { - int unk; + int explosionType; if (spark->flags & SP_UNDERWEXP) { - unk = 1; + explosionType = 1; } else if (spark->flags & SP_PLASMAEXP) { - unk = 2; + explosionType = 2; } else { - unk = 0; + explosionType = 0; } for (int j = 0; j < (spark->extras & 7); j++) @@ -265,13 +265,13 @@ void UpdateSparks() spark->z, (spark->extras & 7) - 1, spark->dynamic, - unk, - (spark->extras & 7)); + explosionType, + spark->roomNumber); spark->dynamic = -1; } - if (unk == 1) + if (explosionType == 1) { TriggerExplosionBubble( spark->x, diff --git a/TombEngine/Game/effects/spark.cpp b/TombEngine/Game/effects/spark.cpp index 1f904334e..579bac973 100644 --- a/TombEngine/Game/effects/spark.cpp +++ b/TombEngine/Game/effects/spark.cpp @@ -14,6 +14,7 @@ using namespace TEN::Math::Random; namespace TEN::Effects::Spark { std::array SparkParticles; + void UpdateSparkParticles() { for (int i = 0; i < SparkParticles.size(); i++) @@ -68,7 +69,7 @@ namespace TEN::Effects::Spark s.active = true; } - void TriggerRicochetSpark(const GameVector& pos, short angle, int count) + void TriggerRicochetSpark(const GameVector& pos, short angle, int count, const Vector4& colorStart) { for (int i = 0; i < count; i++) { @@ -87,7 +88,7 @@ namespace TEN::Effects::Spark v += Vector3(GenerateFloat(-64, 64), GenerateFloat(-64, 64), GenerateFloat(-64, 64)); v.Normalize(v); s.velocity = v * GenerateFloat(17, 24); - s.sourceColor = Vector4(1, 0.8f, 0.2f, 1) * 3; + s.sourceColor = colorStart; s.destinationColor = Vector4(0, 0, 0, 0); s.active = true; } diff --git a/TombEngine/Game/effects/spark.h b/TombEngine/Game/effects/spark.h index 1a38a4b77..609d0e897 100644 --- a/TombEngine/Game/effects/spark.h +++ b/TombEngine/Game/effects/spark.h @@ -5,6 +5,8 @@ namespace TEN::Effects::Spark { + constexpr auto SPARK_RICOCHET_COLOR_DEFAULT = Vector4(1.0f, 0.8f, 0.2f, 1.0f); + struct SparkParticle { Vector3 pos; @@ -26,7 +28,7 @@ namespace TEN::Effects::Spark void UpdateSparkParticles(); SparkParticle& GetFreeSparkParticle(); void TriggerFlareSparkParticles(const Vector3i& pos, const Vector3i& vel, const ColorData& color, int roomNumber); - void TriggerRicochetSpark(const GameVector& pos, short angle, int num); + void TriggerRicochetSpark(const GameVector& pos, short angle, int num, const Vector4& colorStart = SPARK_RICOCHET_COLOR_DEFAULT); void TriggerFrictionSpark(const GameVector& pos, const EulerAngles& angle, float length, int count); void TriggerElectricSpark(const GameVector& pos, const EulerAngles& angle, int count); void TriggerAttackSpark(const Vector3& basePos, const Vector3& color); diff --git a/TombEngine/Game/items.cpp b/TombEngine/Game/items.cpp index c4b3cfc1e..0b8472b59 100644 --- a/TombEngine/Game/items.cpp +++ b/TombEngine/Game/items.cpp @@ -25,7 +25,7 @@ using namespace TEN::Control::Volumes; constexpr int ITEM_DEATH_TIMEOUT = 4 * FPS; -bool ItemInfo::TestOcb(short ocbFlags) +bool ItemInfo::TestOcb(short ocbFlags) const { return ((TriggerFlags & ocbFlags) == ocbFlags); } @@ -40,29 +40,53 @@ void ItemInfo::ClearAllOcb() TriggerFlags = 0; } -bool ItemInfo::TestFlags(short id, short value) +bool ItemInfo::TestFlags(int id, short flags) const { if (id < 0 || id > 7) return false; - return (ItemFlags[id] == value); + return (ItemFlags[id] & flags) != 0; } -void ItemInfo::SetFlags(short id, short value) +bool ItemInfo::TestFlagField(int id, short flags) const +{ + if (id < 0 || id > 7) + return false; + + return (ItemFlags[id] == flags); +} + +short ItemInfo::GetFlagField(int id) const +{ + if (id < 0 || id > 7) + return 0; + + return ItemFlags[id]; +} + +void ItemInfo::SetFlagField(int id, short flags) { if (id < 0 || id > 7) return; - ItemFlags[id] = value; + this->ItemFlags[id] = flags; +} + +void ItemInfo::ClearFlags(int id, short flags) +{ + if (id < 0 || id > 7) + return; + + this->ItemFlags[id] &= ~flags; } bool ItemInfo::TestMeshSwapFlags(unsigned int flags) { - for (size_t i = 0; i < Model.MeshIndex.size(); i++) + for (int i = 0; i < Model.MeshIndex.size(); i++) { if (flags & (1 << i)) { - if (Model.MeshIndex[i] == Model.BaseMesh + i) + if (Model.MeshIndex[i] == (Model.BaseMesh + i)) return false; } } @@ -242,8 +266,7 @@ void RemoveAllItemsInRoom(short roomNumber, short objectNumber) void AddActiveItem(short itemNumber) { auto* item = &g_Level.Items[itemNumber]; - - item->Flags |= 0x20; + item->Flags |= IFLAG_TRIGGERED; if (Objects[item->ObjectNumber].control == NULL) { @@ -470,10 +493,8 @@ void InitialiseItem(short itemNumber) item->Animation.Velocity.y = 0; item->Animation.Velocity.z = 0; - item->ItemFlags[3] = 0; - item->ItemFlags[2] = 0; - item->ItemFlags[1] = 0; - item->ItemFlags[0] = 0; + for (int i = 0; i < NUM_ITEM_FLAGS; i++) + item->ItemFlags[i] = 0; item->Active = false; item->Status = ITEM_NOT_ACTIVE; @@ -481,9 +502,7 @@ void InitialiseItem(short itemNumber) item->HitStatus = false; item->Collidable = true; item->LookedAt = false; - item->Timer = 0; - item->HitPoints = Objects[item->ObjectNumber].HitPoints; if (item->ObjectNumber == ID_HK_ITEM || @@ -516,7 +535,6 @@ void InitialiseItem(short itemNumber) } auto* room = &g_Level.Rooms[item->RoomNumber]; - item->NextItem = room->itemNumber; room->itemNumber = itemNumber; @@ -545,11 +563,10 @@ void InitialiseItem(short itemNumber) short CreateItem() { - short itemNumber = 0; + if (NextItemFree == NO_ITEM) + return NO_ITEM; - if (NextItemFree == -1) return NO_ITEM; - - itemNumber = NextItemFree; + short itemNumber = NextItemFree; g_Level.Items[NextItemFree].Flags = 0; NextItemFree = g_Level.Items[NextItemFree].NextItem; diff --git a/TombEngine/Game/items.h b/TombEngine/Game/items.h index 64bbdd3b2..f00c2f6ef 100644 --- a/TombEngine/Game/items.h +++ b/TombEngine/Game/items.h @@ -13,9 +13,11 @@ using namespace TEN::Utils; enum GAME_OBJECT_ID : short; -constexpr auto NO_ITEM = -1; +constexpr auto NO_ITEM = -1; constexpr auto NOT_TARGETABLE = -16384; -constexpr auto NUM_ITEMS = 1024; + +constexpr auto NUM_ITEMS = 1024; +constexpr auto NUM_ITEM_FLAGS = 8; constexpr unsigned int ALL_JOINT_BITS = UINT_MAX; constexpr unsigned int NO_JOINT_BITS = 0; @@ -43,11 +45,12 @@ enum ItemStatus enum ItemFlags { + IFLAG_TRIGGERED = (1 << 5), IFLAG_CLEAR_BODY = (1 << 7), IFLAG_INVISIBLE = (1 << 8), IFLAG_REVERSE = (1 << 14), IFLAG_KILLED = (1 << 15), - IFLAG_ACTIVATION_MASK = 0x3E00 // bits 9-13 + IFLAG_ACTIVATION_MASK = 0x3E00 // Bits 9-13 (IFLAG_CODEBITS) }; enum class EffectType @@ -100,7 +103,7 @@ struct EntityEffectData int Count = -1; }; -//todo we need to find good "default states" for a lot of these - squidshire 25/05/2022 +// TODO: We need to find good "default states" for a lot of these/ -- squidshire 25/05/2022 struct ItemInfo { GAME_OBJECT_ID ObjectNumber; @@ -138,7 +141,7 @@ struct ItemInfo BitField MeshBits = BitField(); unsigned short Flags; // ItemFlags enum - short ItemFlags[8]; + short ItemFlags[NUM_ITEM_FLAGS]; short TriggerFlags; // TODO: Move to CreatureInfo? @@ -146,12 +149,15 @@ struct ItemInfo short AfterDeath; short CarriedItem; - bool TestOcb(short ocbFlags); + bool TestOcb(short ocbFlags) const; void RemoveOcb(short ocbFlags); void ClearAllOcb(); - - bool TestFlags(short id, short value); - void SetFlags(short id, short value); + + bool TestFlags(int id, short flags) const; // ItemFlags[id] & flags + bool TestFlagField(int id, short flags) const; // ItemFlags[id] == flags + short GetFlagField(int id) const; // ItemFlags[id] + void SetFlagField(int id, short flags); // ItemFlags[id] = flags + void ClearFlags(int id, short flags); // ItemFlags[id] &= ~flags bool TestMeshSwapFlags(unsigned int flags); bool TestMeshSwapFlags(const std::vector& flags); diff --git a/TombEngine/Game/misc.cpp b/TombEngine/Game/misc.cpp index 49b4398b8..e7dcf9694 100644 --- a/TombEngine/Game/misc.cpp +++ b/TombEngine/Game/misc.cpp @@ -17,7 +17,8 @@ CreatureInfo* GetCreatureInfo(ItemInfo* item) void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature) { - float bestDistance = FLT_MAX; + float nearestDistance = INFINITY; + for (int i = 0; i < g_Level.NumItems; i++) { auto* targetEntity = &g_Level.Items[i]; @@ -30,10 +31,10 @@ void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature) targetEntity->Status != ITEM_INVISIBLE) { float distance = Vector3i::Distance(item->Pose.Position, targetEntity->Pose.Position); - if (distance < bestDistance) + if (distance < nearestDistance) { creature->Enemy = targetEntity; - bestDistance = distance; + nearestDistance = distance; } } } diff --git a/TombEngine/Game/room.cpp b/TombEngine/Game/room.cpp index a4f863d7f..31d93afb3 100644 --- a/TombEngine/Game/room.cpp +++ b/TombEngine/Game/room.cpp @@ -62,8 +62,8 @@ void DoFlipMap(short group) FlipStatus = FlipStats[group] = !FlipStats[group]; - for (int i = 0; i < ActiveCreatures.size(); i++) - ActiveCreatures[i]->LOT.TargetBox = NO_BOX; + for (auto& currentCreature : ActiveCreatures) + currentCreature->LOT.TargetBox = NO_BOX; } void AddRoomFlipItems(ROOM_INFO* room) diff --git a/TombEngine/Math/Geometry.cpp b/TombEngine/Math/Geometry.cpp index 257f67591..12e0ad778 100644 --- a/TombEngine/Math/Geometry.cpp +++ b/TombEngine/Math/Geometry.cpp @@ -14,6 +14,16 @@ namespace TEN::Math::Geometry return Vector3i(TranslatePoint(point.ToVector3(), headingAngle, forward, down, right)); } + Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, float distance) + { + return Vector3i(TranslatePoint(point.ToVector3(), orient, distance)); + } + + Vector3i TranslatePoint(const Vector3i& point, const Vector3& direction, float distance) + { + return Vector3i(TranslatePoint(point.ToVector3(), direction, distance)); + } + Vector3 TranslatePoint(const Vector3& point, short headingAngle, float forward, float down, float right) { if (forward == 0.0f && down == 0.0f && right == 0.0f) @@ -28,11 +38,6 @@ namespace TEN::Math::Geometry point.z + ((forward * cosHeading) - (right * sinHeading))); } - Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, float distance) - { - return Vector3i(TranslatePoint(point.ToVector3(), orient, distance)); - } - Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, float distance) { if (distance == 0.0f) @@ -49,11 +54,6 @@ namespace TEN::Math::Geometry point.z + (distance * (cosX * cosY))); } - Vector3i TranslatePoint(const Vector3i& point, const Vector3& direction, float distance) - { - return Vector3i(TranslatePoint(point.ToVector3(), direction, distance)); - } - Vector3 TranslatePoint(const Vector3& point, const Vector3& direction, float distance) { if (distance == 0.0f) @@ -102,11 +102,26 @@ namespace TEN::Math::Geometry float distanceAlpha = direction.Dot(origin - linePoint0) / direction.Dot(direction); if (distanceAlpha < 0.0f) + { return linePoint0; + } else if (distanceAlpha > 1.0f) + { + return linePoint1; + } + + return TranslatePoint(linePoint0, direction, distanceAlpha); + } + + Vector3 GetTruncatedLineEndpoint(const Vector3& linePoint0, const Vector3& linePoint1, float maxDistance) + { + auto distance = Vector3::Distance(linePoint0, linePoint1); + if (distance <= maxDistance) return linePoint1; - return (linePoint0 + (direction * distanceAlpha)); + auto direction = linePoint1 - linePoint0; + direction.Normalize(); + return TranslatePoint(linePoint0, direction, maxDistance); } EulerAngles GetOrientToPoint(const Vector3& origin, const Vector3& target) diff --git a/TombEngine/Math/Geometry.h b/TombEngine/Math/Geometry.h index f54fc517d..f661c4550 100644 --- a/TombEngine/Math/Geometry.h +++ b/TombEngine/Math/Geometry.h @@ -6,14 +6,14 @@ class Vector3i; namespace TEN::Math::Geometry { - // Since Y is assumed as the vertical axis, only the Y Euler component needs to be considered and - // 2D vector operations can be done in the XZ plane. Maybe revise geometry functions to each take an "up" vector argument someday. + // Since Y is assumed as the vertical axis, 2D operations are simply done in the XZ plane. + // Maybe revise geometry functions to each take a "force" vector argument someday. Vector3i TranslatePoint(const Vector3i& point, short headingAngle, float forward, float down = 0.0f, float right = 0.0f); - Vector3 TranslatePoint(const Vector3& point, short headingAngle, float forward, float down = 0.0f, float right = 0.0f); Vector3i TranslatePoint(const Vector3i& point, const EulerAngles& orient, float distance); - Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, float distance); Vector3i TranslatePoint(const Vector3i& point, const Vector3& direction, float distance); + Vector3 TranslatePoint(const Vector3& point, short headingAngle, float forward, float down = 0.0f, float right = 0.0f); + Vector3 TranslatePoint(const Vector3& point, const EulerAngles& orient, float distance); Vector3 TranslatePoint(const Vector3& point, const Vector3& direction, float distance); short GetShortestAngle(short fromAngle, short toAngle); @@ -22,6 +22,7 @@ namespace TEN::Math::Geometry float GetDistanceToLine(const Vector3& origin, const Vector3& linePoint0, const Vector3& linePoint1); Vector3 GetClosestPointOnLine(const Vector3& origin, const Vector3& linePoint0, const Vector3& linePoint1); + Vector3 GetTruncatedLineEndpoint(const Vector3& linePoint0, const Vector3& linePoint1, float maxDistance); EulerAngles GetOrientToPoint(const Vector3& origin, const Vector3& target); bool IsPointInFront(const Pose& pose, const Vector3& target); diff --git a/TombEngine/Objects/Effects/Boss.cpp b/TombEngine/Objects/Effects/Boss.cpp new file mode 100644 index 000000000..d0002f12a --- /dev/null +++ b/TombEngine/Objects/Effects/Boss.cpp @@ -0,0 +1,280 @@ +#include "framework.h" +#include "Objects/Effects/Boss.h" + +#include "Game/collision/collide_room.h" +#include "Game/effects/effects.h" +#include "Game/effects/spark.h" +#include "Game/effects/tomb4fx.h" +#include "Game/items.h" +#include "Game/misc.h" +#include "Objects/TR3/Entity/PunaBoss.h" +#include "Specific/setup.h" + +using namespace TEN::Effects::Spark; +using namespace TEN::Entities::Creatures::TR3; + +namespace TEN::Effects::Boss +{ + void SpawnShield(const ItemInfo& item, const Vector4& color) + { + if (!item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Shield)) + return; + + int itemNumber = CreateItem(); + if (itemNumber == NO_ITEM) + return; + + auto& shieldItem = g_Level.Items[itemNumber]; + + shieldItem.ObjectNumber = ID_BOSS_SHIELD; + shieldItem.RoomNumber = item.RoomNumber; + shieldItem.Pose.Position = item.Pose.Position; + shieldItem.Pose.Position.y -= CLICK(3); + shieldItem.Pose.Orientation = item.Pose.Orientation; + shieldItem.Flags |= IFLAG_ACTIVATION_MASK; + + InitialiseItem(itemNumber); + + shieldItem.Model.Color = color; + shieldItem.Collidable = true; + shieldItem.ItemFlags[0] = 2; + shieldItem.Model.Mutator[0].Scale = Vector3(2.0f); // Scale model by factor of 2. + shieldItem.Status = ITEM_ACTIVE; + + AddActiveItem(itemNumber); + } + + void SpawnShockwaveExplosion(const ItemInfo& item, const Vector4& color) + { + if (!item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::ShockwaveExplosion)) + return; + + int itemNumber = CreateItem(); + if (itemNumber == NO_ITEM) + return; + + auto& shockwaveItem = g_Level.Items[itemNumber]; + + shockwaveItem.Pose.Position = item.Pose.Position; + shockwaveItem.Pose.Position.y -= CLICK(2); + + shockwaveItem.Pose.Orientation = EulerAngles( + Random::GenerateAngle(ANGLE(-20.0f), ANGLE(20.0f)), + Random::GenerateAngle(ANGLE(-20.0f), ANGLE(20.0f)), + Random::GenerateAngle(ANGLE(-20.0f), ANGLE(20.0f))); + + shockwaveItem.ObjectNumber = ID_BOSS_EXPLOSION_SHOCKWAVE; + shockwaveItem.RoomNumber = item.RoomNumber; + shockwaveItem.Flags |= IFLAG_ACTIVATION_MASK; + + InitialiseItem(itemNumber); + + auto result = color; + result.w = 1.0f; + shockwaveItem.Model.Color = result; + shockwaveItem.Collidable = false; // No collision for this entity. + shockwaveItem.ItemFlags[0] = 70; // Timer before clearing; will fade out, then get destroyed. + shockwaveItem.Model.Mutator[0].Scale = Vector3::Zero; // Start without scale. + shockwaveItem.Status = ITEM_ACTIVE; + + AddActiveItem(itemNumber); + SoundEffect(SFX_TR3_BLAST_CIRCLE, &shockwaveItem.Pose); + } + + void ShieldControl(int itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + // Entity life. + if (item.ItemFlags[0] > 0) + item.ItemFlags[0]--; + + if (item.ItemFlags[0] <= 0) + { + RemoveActiveItem(itemNumber); + KillItem(itemNumber); + } + + auto colorEnd = Vector4(item.Model.Color.x - 0.1f, item.Model.Color.y - 0.1f, item.Model.Color.z - 0.1f, item.Model.Color.w); + item.Model.Color = Vector4::Lerp(item.Model.Color, colorEnd, 0.35f); + item.Pose.Orientation.y += Random::GenerateAngle(); + UpdateItemRoom(itemNumber); + } + + // NOTE: Ring and explosion wave rotate and scale by default. + void ShockwaveRingControl(int itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + // Entity life. + if (item.ItemFlags[0] > 0) + item.ItemFlags[0]--; + + if (item.ItemFlags[0] <= 0) + { + auto colorEnd = Vector4(item.Model.Color.x - 0.1f, item.Model.Color.y - 0.1f, item.Model.Color.z - 0.1f, item.Model.Color.w); + item.Model.Color = Vector4::Lerp(item.Model.Color, colorEnd, 0.35f); + + if (item.Model.Color.x <= 0.0f && + item.Model.Color.y <= 0.0f && + item.Model.Color.z <= 0.0f) + { + RemoveActiveItem(itemNumber); + KillItem(itemNumber); + } + } + + item.Model.Mutator[0].Scale += Vector3::One; + UpdateItemRoom(itemNumber); + } + + void ShockwaveExplosionControl(int itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + // Entity life. + if (item.ItemFlags[0] > 0) + item.ItemFlags[0]--; + + if (item.ItemFlags[0] <= 0) + { + auto colorEnd = Vector4(item.Model.Color.x - 0.1f, item.Model.Color.y - 0.1f, item.Model.Color.z - 0.1f, item.Model.Color.w); + item.Model.Color = Vector4::Lerp(item.Model.Color, colorEnd, 0.35f); + + if (item.Model.Color.x <= 0.0f && + item.Model.Color.y <= 0.0f && + item.Model.Color.z <= 0.0f) + { + RemoveActiveItem(itemNumber); + KillItem(itemNumber); + } + } + + item.Pose.Orientation.y += ANGLE(5.0f); + item.Model.Mutator[0].Scale += Vector3(0.5f); + UpdateItemRoom(itemNumber); + } + + void SpawnExplosionSmoke(const Vector3& pos) + { + auto& smoke = *GetFreeParticle(); + + auto sphere = BoundingSphere(pos, BLOCK(1 / 64.0f)); + auto effectPos = Random::GeneratePointInSphere(sphere); + + smoke.on = true; + smoke.blendMode = BLEND_MODES::BLENDMODE_ADDITIVE; + + smoke.x = effectPos.x; + smoke.y = effectPos.y; + smoke.z = effectPos.z; + smoke.xVel = Random::GenerateInt(BLOCK(0.5f), BLOCK(0.5f)); + smoke.yVel = GetRandomControl() - 128; + smoke.zVel = Random::GenerateInt(BLOCK(0.5f), BLOCK(0.5f)); + smoke.sR = 75; + smoke.sG = 125; + smoke.sB = 175; + smoke.dR = 25; + smoke.dG = 80; + smoke.dB = 100; + smoke.colFadeSpeed = 8; + smoke.fadeToBlack = 64; + smoke.life = + smoke.sLife = Random::GenerateInt(96, 128); + smoke.friction = 6; + smoke.rotAng = Random::GenerateAngle(); + smoke.rotAdd = Random::GenerateAngle(ANGLE(-0.2f), ANGLE(0.2f)); + + smoke.scalar = 3; + smoke.gravity = Random::GenerateInt(-8, -4); + smoke.maxYvel = Random::GenerateInt(-12, -8); + smoke.dSize = Random::GenerateInt(256, 288); + smoke.sSize = + smoke.size = smoke.dSize / 2; + smoke.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; + } + + // NOTE: Actual death occurs when countUntilDeath >= 60. + void ExplodeBoss(int itemNumber, ItemInfo& item, int countUntilDeath, const Vector4& color) + { + // Disable shield. + item.SetFlagField((int)BossItemFlags::ShieldIsEnabled, 0); + item.HitPoints = NOT_TARGETABLE; + + // Start explosion (entity will keep duration count). + int counter = item.ItemFlags[(int)BossItemFlags::ExplodeCount]; + if (counter == 1) + { + SpawnShockwaveExplosion(item, color); + + auto sphere = BoundingSphere(item.Pose.Position.ToVector3() + Vector3(0.0f, -CLICK(3), 0.0f), BLOCK(0.5f)); + for (int i = 0; i < 3; i++) + { + auto pos = Random::GeneratePointInSphere(sphere); + SpawnExplosionSmoke(pos); + } + } + + if (counter > 0 && !(counter % 10)) + { + auto sphere = BoundingSphere(item.Pose.Position.ToVector3() + Vector3(0.0f, -CLICK(3), 0.0f), BLOCK(0.5f)); + for (int i = 0; i < 3; i++) + { + auto pos = Random::GeneratePointInSphere(sphere); + SpawnExplosionSmoke(pos); + } + + sphere = BoundingSphere(item.Pose.Position.ToVector3() + Vector3(0.0f, -CLICK(2), 0.0f), BLOCK(1 / 16.0f)); + auto shockwavePos = Pose(Random::GeneratePointInSphere(sphere), item.Pose.Orientation); + + int speed = Random::GenerateInt(BLOCK(0.5f), BLOCK(1.6f)); + auto orient2D = Random::GenerateAngle(); + + TriggerShockwave( + &shockwavePos, 300, BLOCK(0.5f), speed, + color.x * UCHAR_MAX, color.y * UCHAR_MAX, color.z * UCHAR_MAX, + 36, orient2D, 0); + SoundEffect(SFX_TR3_BLAST_CIRCLE, &shockwavePos); + } + + TriggerDynamicLight( + item.Pose.Position.x, + item.Pose.Position.y - CLICK(2), + item.Pose.Position.z, + counter / 2, + color.x * UCHAR_MAX, color.y * UCHAR_MAX, color.z * UCHAR_MAX); + + if (counter >= countUntilDeath) + CreatureDie(itemNumber, true); + } + + void CheckForRequiredObjects(ItemInfo& item) + { + short flags = 0; + + if (item.ObjectNumber == ID_PUNA_BOSS && Objects[ID_LIZARD].loaded) + flags |= (short)BossFlagValue::Lizard; + + // The following are only for aesthetics. + + if (Objects[ID_BOSS_EXPLOSION_SHOCKWAVE].loaded) + flags |= (short)BossFlagValue::ShockwaveExplosion; + + if (Objects[ID_BOSS_SHIELD].loaded) + flags |= (short)BossFlagValue::Shield; + + item.SetFlagField((int)BossItemFlags::Object, flags); + } + + // NOTE: Used by player's FireWeapon() and only if ID_BOSS_SHIELD is loaded. + void SpawnShieldAndRichochetSparks(const ItemInfo& item, const Vector3& pos, const Vector4& color) + { + SpawnShield(item, color); + + auto target = GameVector(pos, item.RoomNumber); + auto yOrient = Geometry::GetOrientToPoint(item.Pose.Position.ToVector3(), target.ToVector3()).y; + auto sparkColor = color; + sparkColor.w = 1.0f; + TriggerRicochetSpark(target, yOrient, 13, sparkColor); + } +} diff --git a/TombEngine/Objects/Effects/Boss.h b/TombEngine/Objects/Effects/Boss.h new file mode 100644 index 000000000..2b6c6d0d8 --- /dev/null +++ b/TombEngine/Objects/Effects/Boss.h @@ -0,0 +1,36 @@ +#pragma once +#include "Game/items.h" + +namespace TEN::Effects::Boss +{ + enum class BossItemFlags + { + Object = 0, // BossFlagValue enum. + Rotation = 1, // Store rotation for use (e.g. Puna when summoning). + ShieldIsEnabled = 2, + AttackType = 3, + AttackCount = 4, // Change behaviour after some attack. + DeathCount = 5, + ItemNumber = 6, // Check if summon is dead. + ExplodeCount = 7 + }; + + enum class BossFlagValue + { + ShockwaveExplosion = (1 << 0), + Shield = (1 << 1), + Lizard = (1 << 2) + }; + + void ShieldControl(int itemNumber); + void ShockwaveRingControl(int itemNumber); + void ShockwaveExplosionControl(int itemNumber); + + void ExplodeBoss(int itemNumber, ItemInfo& item, int deathCountToDie, const Vector4& color); + void CheckForRequiredObjects(ItemInfo& item); + + void SpawnShield(const ItemInfo& item, const Vector4& color); + void SpawnShockwaveExplosion(const ItemInfo& item, const Vector4& color); + + void SpawnShieldAndRichochetSparks(const ItemInfo& item, const Vector3& pos, const Vector4& color); +} diff --git a/TombEngine/Objects/Effects/enemy_missile.cpp b/TombEngine/Objects/Effects/enemy_missile.cpp index 0ea8ff992..fd7723196 100644 --- a/TombEngine/Objects/Effects/enemy_missile.cpp +++ b/TombEngine/Objects/Effects/enemy_missile.cpp @@ -22,9 +22,20 @@ using namespace TEN::Math; namespace TEN::Entities::Effects { - void TriggerSethMissileFlame(short fxNum, short xVel, short yVel, short zVel) + enum class MissileType { - auto* fx = &EffectList[fxNum]; + SethNormal = 0, + SethLarge = 1, + Harpy = 2, + Demigod3Single = 3, + Demigod3Radial = 4, + Demigod2 = 5, + Mutant = 6 + }; + + void TriggerSethMissileFlame(short fxNumber, short xVel, short yVel, short zVel) + { + auto* fx = &EffectList[fxNumber]; int dx = LaraItem->Pose.Position.x - fx->pos.Position.x; int dz = LaraItem->Pose.Position.z - fx->pos.Position.z; @@ -62,7 +73,7 @@ namespace TEN::Entities::Effects spark->gravity = 0; spark->maxYvel = 0; - spark->fxObj = fxNum; + spark->fxObj = fxNumber; if (fx->flag1 == 1) spark->scalar = 3; @@ -144,14 +155,14 @@ namespace TEN::Entities::Effects int maxRotation = 0; int maxVelocity = 0; - if (fx->flag1 == 1) + if (fx->flag1 == (int)MissileType::SethLarge) { maxRotation = ANGLE(2.8f); maxVelocity = CLICK(1); } else { - if (fx->flag1 == 6) + if (fx->flag1 == (int)MissileType::Mutant) { if (fx->counter) fx->counter--; @@ -168,7 +179,7 @@ namespace TEN::Entities::Effects if (fx->speed < maxVelocity) { - if (fx->flag1 == 6) + if (fx->flag1 == (int)MissileType::Mutant) fx->speed++; else fx->speed += 3; @@ -204,12 +215,12 @@ namespace TEN::Entities::Effects fx->pos.Orientation.x += dx; - if (fx->flag1 != 4 && (fx->flag1 != 6 || !fx->counter)) + if (fx->flag1 != (int)MissileType::Demigod3Radial && (fx->flag1 != (int)MissileType::Mutant || !fx->counter)) fx->pos.Orientation.y += dy; } fx->pos.Orientation.z += 16 * fx->speed; - if (fx->flag1 == 6) + if (fx->flag1 == (int)MissileType::Mutant) fx->pos.Orientation.z += 16 * fx->speed; int oldX = fx->pos.Position.x; @@ -229,10 +240,10 @@ namespace TEN::Entities::Effects fx->pos.Position.y = oldY; fx->pos.Position.z = oldZ; - if (fx->flag1 != 6) + if (fx->flag1 != (int)MissileType::Mutant) BubblesShatterFunction(fx, 0, -32); - if (fx->flag1 == 1) + if (fx->flag1 == (int)MissileType::SethLarge) { TriggerShockwave(&fx->pos, 32, 160, 64, 64, 128, 00, 24, (((~g_Level.Rooms[fx->roomNumber].flags) / 16) & 2) * 65536, 0); TriggerExplosionSparks(oldX, oldY, oldZ, 3, -2, 2, fx->roomNumber); @@ -241,26 +252,26 @@ namespace TEN::Entities::Effects { if (fx->flag1) { - if (fx->flag1 == 3 || fx->flag1 == 4) + if (fx->flag1 == (int)MissileType::Demigod3Single || fx->flag1 == (int)MissileType::Demigod3Radial) { TriggerShockwave(&fx->pos, 32, 160, 64, 0, 96, 128, 16, 0, 0); } - else if (fx->flag1 == 5) + else if (fx->flag1 == (int)MissileType::Demigod2) { TriggerShockwave(&fx->pos, 32, 160, 64, 128, 64, 0, 16, 0, 0); } else { - if (fx->flag1 != 2) + if (fx->flag1 != (int)MissileType::Harpy) { - if (fx->flag1 == 6) + if (fx->flag1 == (int)MissileType::Mutant) { TriggerExplosionSparks(oldX, oldY, oldZ, 3, -2, 0, fx->roomNumber); - TriggerShockwave(&fx->pos, 48, 240, 64, 0, 96, 128, 24, 0, 15); + TriggerShockwave(&fx->pos, 48, 240, 64, 128, 96, 0, 24, 0, 15); fx->pos.Position.y -= 128; - TriggerShockwave(&fx->pos, 48, 240, 48, 0, 112, 128, 16, 0, 15); + TriggerShockwave(&fx->pos, 48, 240, 48, 128, 112, 0, 16, 0, 15); fx->pos.Position.y += 256; - TriggerShockwave(&fx->pos, 48, 240, 48, 0, 112, 128, 16, 0, 15); + TriggerShockwave(&fx->pos, 48, 240, 48, 128, 112, 0, 16, 0, 15); } } @@ -283,12 +294,12 @@ namespace TEN::Entities::Effects if (ItemNearLara(fx->pos.Position, 200)) { LaraItem->HitStatus = true; - if (fx->flag1 != 6) + if (fx->flag1 != (int)MissileType::Mutant) BubblesShatterFunction(fx, 0, -32); KillEffect(fxNum); - if (fx->flag1 == 1) + if (fx->flag1 == (int)MissileType::SethLarge) { TriggerShockwave(&fx->pos, 48, 240, 64, 0, 128, 64, 24, 0, 0); TriggerExplosionSparks(oldX, oldY, oldZ, 3, -2, 2, fx->roomNumber); @@ -298,26 +309,26 @@ namespace TEN::Entities::Effects { switch (fx->flag1) { - case 3: - case 4: + case (int)MissileType::Demigod3Single: + case (int)MissileType::Demigod3Radial: TriggerShockwave(&fx->pos, 32, 160, 64, 0, 96, 128, 16, 0, 10); break; - case 5: + case (int)MissileType::Demigod2: TriggerShockwave(&fx->pos, 32, 160, 64, 128, 64, 0, 16, 0, 5); break; - case 2: - TriggerShockwave(&fx->pos, 32, 160, 64, 128, 128, 0, 16, 0, 3);//H + case (int)MissileType::Harpy: + TriggerShockwave(&fx->pos, 32, 160, 64, 128, 128, 0, 16, 0, 3); break; - case 6: + case (int)MissileType::Mutant: TriggerExplosionSparks(oldX, oldY, oldZ, 3, -2, 0, fx->roomNumber); - TriggerShockwave(&fx->pos, 48, 240, 64, 0, 96, 128, 24, 0, 0); + TriggerShockwave(&fx->pos, 48, 240, 64, 128, 96, 0, 24, 0, 0); fx->pos.Position.y -= 128; - TriggerShockwave(&fx->pos, 48, 240, 48, 0, 112, 128, 16, 0, 0); + TriggerShockwave(&fx->pos, 48, 240, 48, 128, 112, 0, 16, 0, 0); fx->pos.Position.y += 256; - TriggerShockwave(&fx->pos, 48, 240, 48, 0, 112, 128, 16, 0, 0); + TriggerShockwave(&fx->pos, 48, 240, 48, 128, 112, 0, 16, 0, 0); ItemBurn(LaraItem); break; } @@ -341,21 +352,21 @@ namespace TEN::Entities::Effects switch (fx->flag1) { default: - case 1: + case (int)MissileType::SethLarge: TriggerSethMissileFlame(fxNum, 32 * dx, 32 * dy, 32 * dz); break; - case 2: + case (int)MissileType::Harpy: TriggerHarpyFlameFlame(fxNum, 16 * dx, 16 * dy, 16 * dz); break; - case 3: - case 4: - case 5: + case (int)MissileType::Demigod3Single: + case (int)MissileType::Demigod3Radial: + case (int)MissileType::Demigod2: TriggerDemigodMissileFlame(fxNum, 16 * dx, 16 * dy, 16 * dz); break; - case 6: + case (int)MissileType::Mutant: TriggerCrocgodMissileFlame(fxNum, 16 * dx, 16 * dy, 16 * dz); break; } diff --git a/TombEngine/Objects/Generic/Doors/generic_doors.cpp b/TombEngine/Objects/Generic/Doors/generic_doors.cpp index 72c866035..5af833186 100644 --- a/TombEngine/Objects/Generic/Doors/generic_doors.cpp +++ b/TombEngine/Objects/Generic/Doors/generic_doors.cpp @@ -401,11 +401,8 @@ namespace TEN::Entities::Doors if (boxIndex != NO_BOX) { g_Level.Boxes[boxIndex].flags &= ~BLOCKED; - - for (int i = 0; i < ActiveCreatures.size(); i++) - { - ActiveCreatures[i]->LOT.TargetBox = NO_BOX; - } + for (auto& currentCreature : ActiveCreatures) + currentCreature->LOT.TargetBox = NO_BOX; } } } @@ -437,8 +434,8 @@ namespace TEN::Entities::Doors { g_Level.Boxes[boxIndex].flags |= BLOCKED; - for (int i = 0; i < ActiveCreatures.size(); i++) - ActiveCreatures[i]->LOT.TargetBox = NO_BOX; + for (auto& currentCreature : ActiveCreatures) + currentCreature->LOT.TargetBox = NO_BOX; } } } diff --git a/TombEngine/Objects/TR1/Entity/tr1_winged_mutant.cpp b/TombEngine/Objects/TR1/Entity/tr1_winged_mutant.cpp index ace23e813..d5900368c 100644 --- a/TombEngine/Objects/TR1/Entity/tr1_winged_mutant.cpp +++ b/TombEngine/Objects/TR1/Entity/tr1_winged_mutant.cpp @@ -150,12 +150,12 @@ namespace TEN::Entities::Creatures::TR1 if (Targetable(item, AI) && (AI->zoneNumber != AI->enemyZone || AI->distance > WINGED_MUTANT_ATTACK_RANGE)) { if ((AI->angle > 0 && AI->angle < ANGLE(45.0f)) && - item->TestFlags(WMUTANT_OCB_DISABLE_DART_WEAPON, false)) + item->TestFlagField(WMUTANT_OCB_DISABLE_DART_WEAPON, false)) { return WMUTANT_PROJ_DART; } else if ((AI->angle < 0 && AI->angle > -ANGLE(45.0f)) && - item->TestFlags(WMUTANT_OCB_DISABLE_BOMB_WEAPON, false)) + item->TestFlagField(WMUTANT_OCB_DISABLE_BOMB_WEAPON, false)) { return WMUTANT_PROJ_BOMB; } @@ -171,19 +171,19 @@ namespace TEN::Entities::Creatures::TR1 { SwitchPathfinding(creature, WMUTANT_PATH_AERIAL); SetAnimation(item, WMUTANT_ANIM_FLY); - item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL); + item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL); } else if (item->TestOcb(WMUTANT_OCB_START_INACTIVE)) { SwitchPathfinding(creature, WMUTANT_PATH_GROUND); SetAnimation(item, WMUTANT_ANIM_INACTIVE); - item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); + item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); } else if (item->TestOcb(WMUTANT_OCB_START_POSE)) { SwitchPathfinding(creature, WMUTANT_PATH_GROUND); SetAnimation(item, WMUTANT_ANIM_INACTIVE); - item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); + item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); } // Remove OCBs since we don't need them anymore. @@ -201,26 +201,28 @@ namespace TEN::Entities::Creatures::TR1 auto* item = &g_Level.Items[itemNumber]; InitialiseCreature(itemNumber); - item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); - item->SetFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE); + item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); + item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE); if (item->TestOcb(WMUTANT_OCB_NO_WINGS)) { - item->SetFlags(WMUTANT_CONF_CAN_FLY, false); + item->SetFlagField(WMUTANT_CONF_CAN_FLY, false); item->MeshBits = 0xFFE07FFF; } else - item->SetFlags(WMUTANT_CONF_CAN_FLY, true); + item->SetFlagField(WMUTANT_CONF_CAN_FLY, true); if (item->TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON)) - item->SetFlags(WMUTANT_CONF_DISABLE_BOMB_WEAPON, true); + item->SetFlagField(WMUTANT_CONF_DISABLE_BOMB_WEAPON, true); if (item->TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON)) - item->SetFlags(WMUTANT_CONF_DISABLE_DART_WEAPON, true); + item->SetFlagField(WMUTANT_CONF_DISABLE_DART_WEAPON, true); if (item->TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON)) item->RemoveOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON); + if (item->TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON)) item->RemoveOcb(WMUTANT_OCB_DISABLE_DART_WEAPON); + if (item->TestOcb(WMUTANT_OCB_NO_WINGS)) item->RemoveOcb(WMUTANT_OCB_NO_WINGS); } @@ -237,8 +239,8 @@ namespace TEN::Entities::Creatures::TR1 short head = 0; short torso = 0; // Only when shooting. - bool flyEnabled = item->TestFlags(WMUTANT_CONF_CAN_FLY, true); - bool flyStatus = item->TestFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL); + bool flyEnabled = item->TestFlagField(WMUTANT_CONF_CAN_FLY, true); + bool flyStatus = item->TestFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL); WingedInitOCB(item, creature); @@ -274,7 +276,7 @@ namespace TEN::Entities::Creatures::TR1 if (flyStatus && creature->Mood != MoodType::Escape && AI.zoneNumber == AI.enemyZone) { - item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); + item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND); } SwitchPathfinding(creature, WMUTANT_PATH_AERIAL); @@ -285,7 +287,7 @@ namespace TEN::Entities::Creatures::TR1 (!AI.ahead || creature->Mood == MoodType::Bored)) || creature->Mood == MoodType::Escape) { - item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL); + item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL); } } @@ -302,7 +304,7 @@ namespace TEN::Entities::Creatures::TR1 case WMUTANT_STATE_IDLE: torso = 0; creature->MaxTurn = 0; - item->SetFlags(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PROJ_NONE); + item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PROJ_NONE); if (flyStatus && flyEnabled) item->Animation.TargetState = WMUTANT_STATE_FLY; @@ -433,7 +435,7 @@ namespace TEN::Entities::Creatures::TR1 case WMUTANT_STATE_AIM_DART: torso = AI.angle / 2; creature->MaxTurn = 0; - item->SetFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART); + item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART); if (shootType == WMUTANT_PROJ_DART) item->Animation.TargetState = WMUTANT_STATE_SHOOT; @@ -445,7 +447,7 @@ namespace TEN::Entities::Creatures::TR1 case WMUTANT_STATE_AIM_BOMB: torso = AI.angle / 2; creature->MaxTurn = 0; - item->SetFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB); + item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB); if (shootType == WMUTANT_PROJ_BOMB) item->Animation.TargetState = WMUTANT_STATE_SHOOT; @@ -459,15 +461,15 @@ namespace TEN::Entities::Creatures::TR1 torso = AI.angle / 2; creature->MaxTurn = 0; - bool isDart = item->TestFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART); - bool isBomb = item->TestFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB); + bool isDart = item->TestFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART); + bool isBomb = item->TestFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB); if (isDart) CreatureEffect2(item, WingedMutantShardBite, WINGED_MUTANT_SHARD_VELOCITY, torso, ShardGun); else if (isBomb) CreatureEffect2(item, WingedMutantRocketBite, WINGED_MUTANT_BOMB_VELOCITY, torso, BombGun); - item->SetFlags(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE); + item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE); break; } diff --git a/TombEngine/Objects/TR2/Entity/tr2_eagle_or_crow.cpp b/TombEngine/Objects/TR2/Entity/tr2_eagle_or_crow.cpp index 54ce4f993..3cc32a034 100644 --- a/TombEngine/Objects/TR2/Entity/tr2_eagle_or_crow.cpp +++ b/TombEngine/Objects/TR2/Entity/tr2_eagle_or_crow.cpp @@ -16,14 +16,18 @@ namespace TEN::Entities::Creatures::TR2 const auto EagleBite = BiteInfo(Vector3(15.0f, 46.0f, 21.0f), 6); const auto CrowBite = BiteInfo(Vector3(2.0f, 10.0f, 60.0f), 14); - // TODO - enum EagleState + enum EagleOrCrowState { - + // No state 0. + EAGLE_CROW_STATE_FLY = 1, + EAGLE_CROW_STATE_IDLE = 2, + EAGLE_CROW_STATE_PLANE = 3, + EAGLE_CROW_STATE_DEATH_START = 4, + EAGLE_CROW_STATE_DEATH_END = 5, + EAGLE_CROW_STATE_ATTACK = 6 }; - // TODO - enum EagleAnim + enum EagleOrCrowAnim { }; diff --git a/TombEngine/Objects/TR2/tr2_objects.cpp b/TombEngine/Objects/TR2/tr2_objects.cpp index cbad5ebc1..d51e290fc 100644 --- a/TombEngine/Objects/TR2/tr2_objects.cpp +++ b/TombEngine/Objects/TR2/tr2_objects.cpp @@ -4,6 +4,7 @@ #include "Game/collision/collide_item.h" #include "Game/control/box.h" #include "Game/itemdata/creature_info.h" +#include "Objects/Utils/object_helper.h" #include "Specific/level.h" #include "Specific/setup.h" @@ -156,16 +157,7 @@ static void StartEntity(ObjectInfo* obj) obj = &Objects[ID_GOON_SILENCER2]; if (obj->loaded) { - if (Objects[ID_GOON_SILENCER1].loaded) - { - obj->animIndex = Objects[ID_GOON_SILENCER1].animIndex; - obj->frameBase = Objects[ID_GOON_SILENCER1].frameBase; - } - else - { - TENLog("ID_GOON_SILENCER1 not found.", LogLevel::Warning); - } - + AssignObjectAnimations(*obj, ID_GOON_SILENCER1, "ID_GOON_SILENCER2", "ID_GOON_SILENCER1"); obj->initialise = InitialiseCreature; obj->collision = CreatureCollision; obj->control = SilencerControl; @@ -183,16 +175,7 @@ static void StartEntity(ObjectInfo* obj) obj = &Objects[ID_GOON_SILENCER3]; if (obj->loaded) { - if (Objects[ID_GOON_SILENCER1].loaded) - { - obj->animIndex = Objects[ID_GOON_SILENCER1].animIndex; - obj->frameBase = Objects[ID_GOON_SILENCER1].frameBase; - } - else - { - TENLog("ID_GOON_SILENCER1 not found.", LogLevel::Warning); - } - + AssignObjectAnimations(*obj, ID_GOON_SILENCER1, "ID_GOON_SILENCER3", "ID_GOON_SILENCER1"); obj->initialise = InitialiseCreature; obj->collision = CreatureCollision; obj->control = SilencerControl; @@ -371,16 +354,7 @@ static void StartEntity(ObjectInfo* obj) obj = &Objects[ID_MERCENARY_AUTOPISTOLS2]; if (obj->loaded) { - if (Objects[ID_MERCENARY_AUTOPISTOLS1].loaded) - { - obj->animIndex = Objects[ID_MERCENARY_AUTOPISTOLS1].animIndex; - obj->frameBase = Objects[ID_MERCENARY_AUTOPISTOLS1].frameBase; - } - else - { - TENLog("ID_MERCENARY_AUTOPISTOLS1 not found.", LogLevel::Warning); - } - + AssignObjectAnimations(*obj, ID_MERCENARY_AUTOPISTOLS1, "ID_MERCENARY_AUTOPISTOLS2", "ID_MERCENARY_AUTOPISTOLS1"); obj->initialise = InitialiseCreature; obj->collision = CreatureCollision; obj->control = MercenaryAutoPistolControl; @@ -427,6 +401,7 @@ static void StartEntity(ObjectInfo* obj) obj = &Objects[ID_SWORD_GUARDIAN]; if (obj->loaded) { + CheckIfSlotExists(ID_SWORD_GUARDIAN_STATUE, "ID_SWORD_GUARDIAN", "ID_SWORD_GUARDIAN_STATUE"); obj->initialise = InitialiseCreature; obj->collision = CreatureCollision; obj->control = SwordGuardianControl; @@ -443,6 +418,7 @@ static void StartEntity(ObjectInfo* obj) obj = &Objects[ID_SPEAR_GUARDIAN]; if (obj->loaded) { + CheckIfSlotExists(ID_SPEAR_GUARDIAN_STATUE, "ID_SPEAR_GUARDIAN", "ID_SPEAR_GUARDIAN_STATUE"); obj->initialise = InitialiseSpearGuardian; obj->collision = CreatureCollision; obj->control = SpearGuardianControl; @@ -459,9 +435,7 @@ static void StartEntity(ObjectInfo* obj) obj = &Objects[ID_DRAGON_FRONT]; if (obj->loaded) { - if (!Objects[ID_DRAGON_BACK].loaded) - TENLog("ID_DRAGON_FRONT needs ID_DRAGON_BACK.", LogLevel::Warning); - + CheckIfSlotExists(ID_DRAGON_BACK, "ID_DRAGON_FRONT", "ID_DRAGON_BACK"); obj->initialise = InitialiseCreature; obj->collision = DragonCollision; obj->control = DragonControl; @@ -476,19 +450,18 @@ static void StartEntity(ObjectInfo* obj) obj = &Objects[ID_DRAGON_BACK]; if (obj->loaded) { - if (!Objects[ID_MARCO_BARTOLI].loaded) - TENLog("ID_DRAGON_BACK needs ID_MARCO_BARTOLI.", LogLevel::Warning); - + CheckIfSlotExists(ID_MARCO_BARTOLI, "ID_DRAGON_BACK", "ID_MARCO_BARTOLI"); obj->initialise = InitialiseCreature; obj->collision = DragonCollision; obj->control = DragonControl; obj->radius = 256; - obj->SetupHitEffect(true); + obj->SetupHitEffect(); } obj = &Objects[ID_MARCO_BARTOLI]; if (obj->loaded) { + CheckIfSlotExists(ID_DRAGON_BACK, "ID_MARCO_BARTOLI", "ID_DRAGON_BACK"); obj->initialise = InitialiseBartoli; obj->control = BartoliControl; } diff --git a/TombEngine/Objects/TR3/Entity/Lizard.cpp b/TombEngine/Objects/TR3/Entity/Lizard.cpp new file mode 100644 index 000000000..536038640 --- /dev/null +++ b/TombEngine/Objects/TR3/Entity/Lizard.cpp @@ -0,0 +1,397 @@ +#include "framework.h" +#include "Objects/TR3/Entity/Lizard.h" + +#include "Game/control/box.h" +#include "Game/effects/tomb4fx.h" +#include "Game/Lara/lara_helpers.h" +#include "Game/misc.h" +#include "Game/people.h" +#include "Specific/level.h" + +namespace TEN::Entities::Creatures::TR3 +{ + constexpr auto LIZARD_ATTACK_1_DAMAGE = 120; + constexpr auto LIZARD_ATTACK_2_DAMAGE = 100; + + constexpr auto LIZARD_ATTACK_0_RANGE = SQUARE(BLOCK(2.5f)); + constexpr auto LIZARD_ATTACK_1_RANGE = SQUARE(BLOCK(0.75f)); + constexpr auto LIZARD_ATTACK_2_RANGE = SQUARE(BLOCK(1.5f)); + constexpr auto LIZARD_WALK_RANGE = SQUARE(BLOCK(2)); + + constexpr auto LIZARD_WALK_CHANCE = 1 / 256.0f; + constexpr auto LIZARD_BORED_WALK_CHANCE = 1 / 512.0f; + constexpr auto LIZARD_WAIT_CHANCE = 1 / 256.0f; + + constexpr auto LIZARD_WALK_TURN_RATE_MAX = ANGLE(10.0f); + constexpr auto LIZARD_RUN_TURN_RATE_MAX = ANGLE(4.0f); + + constexpr auto LIZARD_VAULT_SHIFT = 260; + + const auto LizardBiteAttackBite = BiteInfo(Vector3(0.0f, -120.0f, 120.0f), 10); + const auto LizardSwipeAttackBite = BiteInfo(Vector3::Zero, 5); + const auto LizardGasBite = BiteInfo(Vector3(0.0f, -64.0f, 56.0f), 9); + const auto LizardSwipeAttackJoints = std::vector{ 5 }; + const auto LizardBiteAttackJoints = std::vector{ 10 }; + + enum LizardState + { + // No state 0. + LIZARD_STATE_IDLE = 1, + LIZARD_STATE_WALK_FORWARD = 2, + LIZARD_STATE_PUNCH_2 = 3, + LIZARD_STATE_AIM_2 = 4, + LIZARD_STATE_WAIT = 5, + LIZARD_STATE_AIM_1 = 6, + LIZARD_STATE_AIM_0 = 7, + LIZARD_STATE_PUNCH_1 = 8, + LIZARD_STATE_PUNCH_0 = 9, + LIZARD_STATE_RUN_FORWARD = 10, + LIZARD_STATE_DEATH = 11, + LIZARD_STATE_VAULT_3_STEPS_UP = 12, + LIZARD_STATE_VAULT_1_STEP_UP = 13, + LIZARD_STATE_VAULT_2_STEPS_UP = 14, + LIZARD_STATE_VAULT_4_STEPS_DOWN = 15 + }; + + enum LizardAnim + { + LIZARD_ANIM_SLIDE_1 = 23, + LIZARD_ANIM_DEATH = 26, + LIZARD_ANIM_VAULT_4_STEPS_UP = 27, + LIZARD_ANIM_VAULT_2_STEPS_UP = 28, + LIZARD_ANIM_VAULT_3_STEPS_UP = 29, + LIZARD_ANIM_VAULT_4_STEPS_DOWN = 30, + LIZARD_ANIM_SLIDE_2 = 31 + }; + + static bool IsLizardTargetBlocked(ItemInfo& item) + { + auto& creature = *GetCreatureInfo(&item); + + return (creature.Enemy && creature.Enemy->BoxNumber != NO_BOX && + (g_Level.Boxes[creature.Enemy->BoxNumber].flags & BLOCKABLE)); + } + + static void SpawnLizardGas(int itemNumber, const BiteInfo& bite, int speed) + { + static constexpr auto numPoisonThrows = 2; + + for (int i = 0; i < numPoisonThrows; i++) + ThrowPoison(itemNumber, bite.meshNum, Vector3i(bite.Position), Vector3i(0.0f, -100.0f, speed << 2), Vector3(0.0f, 1.0f, 0.0f)); + + ThrowPoison(itemNumber, bite.meshNum, Vector3i(bite.Position), Vector3i(0.0f, -100.0f, speed << 1), Vector3(0.0f, 1.0f, 0.0f)); + } + + void LizardControl(short itemNumber) + { + if (!CreatureActive(itemNumber)) + return; + + auto& item = g_Level.Items[itemNumber]; + auto& creature = *GetCreatureInfo(&item); + + short headingAngle = 0; + short tiltAngle = 0; + auto headOrient = EulerAngles::Zero; + + if (item.HitPoints <= 0) + { + // Avoid doing the death animation if summoned. + if (item.Animation.ActiveState != LIZARD_STATE_DEATH) + SetAnimation(&item, LIZARD_ANIM_DEATH); + + // Explode if summoned. + if (item.TestFlagField(0, 1) && item.Animation.FrameNumber == GetFrameNumber(&item, 50)) + CreatureDie(itemNumber, true); + } + else + { + AI_INFO ai; + CreatureAIInfo(&item, &ai); + GetCreatureMood(&item, &ai, true); + CreatureMood(&item, &ai, true); + + if (IsLizardTargetBlocked(item)) + creature.Mood = MoodType::Attack; + + headingAngle = CreatureTurn(&item, creature.MaxTurn); + + // Turn head. Avoid while climbing or falling. + if (ai.ahead && + item.Animation.ActiveState < LIZARD_STATE_DEATH) + { + headOrient.x = ai.xAngle; + headOrient.y = ai.angle; + } + + bool isPlayerPoisonedOrTargetBlocked = + (creature.Enemy != nullptr && GetLaraInfo(creature.Enemy)->PoisonPotency < 256) || + IsLizardTargetBlocked(item); + + switch (item.Animation.ActiveState) + { + case LIZARD_STATE_IDLE: + creature.MaxTurn = 0; + creature.Flags = 0; + + if (creature.Mood == MoodType::Escape) + { + item.Animation.TargetState = LIZARD_STATE_RUN_FORWARD; + } + else if (creature.Mood == MoodType::Bored) + { + if (item.Animation.RequiredState) + { + item.Animation.TargetState = item.Animation.RequiredState; + } + else if (Random::TestProbability(LIZARD_BORED_WALK_CHANCE)) + { + item.Animation.TargetState = LIZARD_STATE_WALK_FORWARD; + } + else + { + item.Animation.TargetState = LIZARD_STATE_WAIT; + } + } + else if (ai.bite && ai.distance < LIZARD_ATTACK_1_RANGE) + { + item.Animation.TargetState = LIZARD_STATE_AIM_1; + } + else if (Targetable(&item, &ai) && ai.bite && + ai.distance < LIZARD_ATTACK_0_RANGE && isPlayerPoisonedOrTargetBlocked) + { + item.Animation.TargetState = LIZARD_STATE_AIM_0; + } + else + { + item.Animation.TargetState = LIZARD_STATE_RUN_FORWARD; + } + + break; + + case LIZARD_STATE_WAIT: + creature.MaxTurn = 0; + + if (creature.Mood != MoodType::Bored) + { + item.Animation.TargetState = LIZARD_STATE_IDLE; + } + else if (Random::TestProbability(LIZARD_WALK_CHANCE)) + { + item.Animation.RequiredState = LIZARD_STATE_WALK_FORWARD; + item.Animation.TargetState = LIZARD_STATE_IDLE; + } + + break; + + case LIZARD_STATE_WALK_FORWARD: + if (TestAnimNumber(item, LIZARD_ANIM_SLIDE_1) || TestAnimNumber(item, LIZARD_ANIM_SLIDE_2)) + creature.MaxTurn = 0; + else + creature.MaxTurn = LIZARD_WALK_TURN_RATE_MAX; + + if (creature.Mood == MoodType::Escape) + { + item.Animation.TargetState = LIZARD_STATE_RUN_FORWARD; + } + else if (creature.Mood == MoodType::Bored) + { + if (Random::TestProbability(LIZARD_WAIT_CHANCE)) + { + item.Animation.RequiredState = LIZARD_STATE_WAIT; + item.Animation.TargetState = LIZARD_STATE_IDLE; + } + } + else if (ai.bite && ai.distance < LIZARD_ATTACK_1_RANGE) + { + item.Animation.TargetState = LIZARD_STATE_IDLE; + } + else if (ai.bite && ai.distance < LIZARD_ATTACK_2_RANGE) + { + item.Animation.TargetState = LIZARD_STATE_AIM_2; + } + else if (Targetable(&item, &ai) && ai.distance < LIZARD_ATTACK_0_RANGE && + isPlayerPoisonedOrTargetBlocked) + { + item.Animation.TargetState = LIZARD_STATE_IDLE; + } + else if (ai.distance > LIZARD_WALK_RANGE) + { + item.Animation.TargetState = LIZARD_STATE_RUN_FORWARD; + } + + break; + + case LIZARD_STATE_RUN_FORWARD: + creature.MaxTurn = LIZARD_RUN_TURN_RATE_MAX; + tiltAngle = headingAngle / 2; + + if (creature.Mood == MoodType::Escape) + { + break; + } + else if (creature.Mood == MoodType::Bored) + { + item.Animation.TargetState = LIZARD_STATE_WALK_FORWARD; + } + else if (ai.bite && ai.distance < LIZARD_ATTACK_1_RANGE) + { + item.Animation.TargetState = LIZARD_STATE_IDLE; + } + else if (Targetable(&item, &ai) && ai.distance < LIZARD_ATTACK_0_RANGE && + isPlayerPoisonedOrTargetBlocked) + { + item.Animation.TargetState = LIZARD_STATE_IDLE; + } + else if (ai.ahead && ai.distance < LIZARD_WALK_RANGE) + { + item.Animation.TargetState = LIZARD_STATE_WALK_FORWARD; + } + + break; + + case LIZARD_STATE_AIM_0: + creature.MaxTurn = 0; + creature.Flags = 0; + + if (abs(ai.angle) < LIZARD_RUN_TURN_RATE_MAX) + { + item.Pose.Orientation.y += ai.angle; + } + else if (ai.angle < 0) + { + item.Pose.Orientation.y -= LIZARD_RUN_TURN_RATE_MAX; + } + else + { + item.Pose.Orientation.y += LIZARD_RUN_TURN_RATE_MAX; + } + + // Maybe we should add targetable as well? -- TokyoSU + if (ai.bite && ai.distance < LIZARD_ATTACK_0_RANGE && isPlayerPoisonedOrTargetBlocked) + item.Animation.TargetState = LIZARD_STATE_PUNCH_0; + else + item.Animation.TargetState = LIZARD_STATE_IDLE; + + break; + + case LIZARD_STATE_AIM_1: + creature.MaxTurn = LIZARD_WALK_TURN_RATE_MAX; + creature.Flags = 0; + + if (ai.ahead && ai.distance < LIZARD_ATTACK_1_RANGE) + item.Animation.TargetState = LIZARD_STATE_PUNCH_1; + else + item.Animation.TargetState = LIZARD_STATE_IDLE; + + break; + + case LIZARD_STATE_AIM_2: + creature.MaxTurn = LIZARD_WALK_TURN_RATE_MAX; + creature.Flags = 0; + + if (ai.ahead && ai.distance < LIZARD_ATTACK_2_RANGE) + item.Animation.TargetState = LIZARD_STATE_PUNCH_2; + else + item.Animation.TargetState = LIZARD_STATE_IDLE; + + break; + + case LIZARD_STATE_PUNCH_0: + creature.MaxTurn = 0; + + if (abs(ai.angle) < LIZARD_RUN_TURN_RATE_MAX) + { + item.Pose.Orientation.y += ai.angle; + } + else if (ai.angle < 0) + { + item.Pose.Orientation.y -= LIZARD_RUN_TURN_RATE_MAX; + } + else + { + item.Pose.Orientation.y += LIZARD_RUN_TURN_RATE_MAX; + } + + if (TestAnimFrameRange(item, 7, 28)) + { + if (creature.Flags < 24) + creature.Flags += 2; + + if (creature.Flags < 24) + SpawnLizardGas(itemNumber, LizardGasBite, creature.Flags); + else + SpawnLizardGas(itemNumber, LizardGasBite, (GetRandomControl() & 15) + 8); + } + + if (TestAnimFrame(item, 28)) + creature.Flags = 0; + + break; + + case LIZARD_STATE_PUNCH_1: + creature.MaxTurn = 0; + + if (!creature.Flags && item.TouchBits.Test(LizardSwipeAttackJoints)) + { + DoDamage(creature.Enemy, LIZARD_ATTACK_1_DAMAGE); + CreatureEffect(&item, LizardSwipeAttackBite, DoBloodSplat); + SoundEffect(SFX_TR4_LARA_THUD, &item.Pose); + creature.Flags = 1; + } + + if (ai.distance < LIZARD_ATTACK_2_RANGE) + item.Animation.TargetState = LIZARD_STATE_PUNCH_2; + + break; + + case LIZARD_STATE_PUNCH_2: + creature.MaxTurn = 0; + + if (creature.Flags != 2 && item.TouchBits.Test(LizardBiteAttackJoints)) + { + DoDamage(creature.Enemy, LIZARD_ATTACK_2_DAMAGE); + CreatureEffect(&item, LizardSwipeAttackBite, DoBloodSplat); + SoundEffect(SFX_TR4_LARA_THUD, &item.Pose); + creature.Flags = 2; + } + + break; + } + } + + CreatureTilt(&item, tiltAngle); + CreatureJoint(&item, 0, headOrient.x); + CreatureJoint(&item, 1, headOrient.y); + + if (item.Animation.ActiveState < LIZARD_STATE_DEATH) + { + switch (CreatureVault(itemNumber, headingAngle, 2, LIZARD_VAULT_SHIFT)) + { + case 2: + SetAnimation(&item, LIZARD_ANIM_VAULT_2_STEPS_UP); + creature.MaxTurn = 0; + break; + + case 3: + SetAnimation(&item, LIZARD_ANIM_VAULT_3_STEPS_UP); + creature.MaxTurn = 0; + break; + + case 4: + SetAnimation(&item, LIZARD_ANIM_VAULT_4_STEPS_UP); + creature.MaxTurn = 0; + break; + + case -4: + SetAnimation(&item, LIZARD_ANIM_VAULT_4_STEPS_DOWN); + creature.MaxTurn = 0; + break; + } + } + else + { + CreatureAnimation(itemNumber, headingAngle, 0); + } + } +} diff --git a/TombEngine/Objects/TR3/Entity/Lizard.h b/TombEngine/Objects/TR3/Entity/Lizard.h new file mode 100644 index 000000000..fce7c177c --- /dev/null +++ b/TombEngine/Objects/TR3/Entity/Lizard.h @@ -0,0 +1,9 @@ +#pragma once + +struct BiteInfo; +struct ItemInfo; + +namespace TEN::Entities::Creatures::TR3 +{ + void LizardControl(short itemNumber); +} diff --git a/TombEngine/Objects/TR3/Entity/PunaBoss.cpp b/TombEngine/Objects/TR3/Entity/PunaBoss.cpp new file mode 100644 index 000000000..166b2eb9d --- /dev/null +++ b/TombEngine/Objects/TR3/Entity/PunaBoss.cpp @@ -0,0 +1,529 @@ +#include "framework.h" +#include "Objects/TR3/Entity/PunaBoss.h" + +#include "Game/control/box.h" +#include "Game/control/los.h" +#include "Game/effects/effects.h" +#include "Game/effects/item_fx.h" +#include "Game/effects/lightning.h" +#include "Game/Lara/lara_helpers.h" +#include "Game/misc.h" +#include "Math/Math.h" +#include "Objects/Effects/Boss.h" +#include "Specific/level.h" +#include "Specific/setup.h" + +using namespace TEN::Effects::Boss; +using namespace TEN::Effects::Items; +using namespace TEN::Effects::Lightning; + +namespace TEN::Entities::Creatures::TR3 +{ + constexpr auto PUNA_LIGHTNING_DAMAGE = 350; + + constexpr auto PUNA_ATTACK_RANGE = BLOCK(20); + constexpr auto PUNA_ALERT_RANGE = BLOCK(2.5f); + + constexpr auto PUNA_TURN_RATE_MAX = ANGLE(3.0f); + constexpr auto PUNA_CHAIR_TURN_RATE_MAX = ANGLE(7.0f); + constexpr auto PUNA_HEAD_X_ANGLE_MAX = ANGLE(20.0f); + constexpr auto PUNA_ADJUST_LIGHTNING_X_ANGLE = ANGLE(3.0f); + + constexpr auto PUNA_EXPLOSION_NUM_MAX = 60; + constexpr auto PUNA_HEAD_ATTACK_NUM_MAX = 4; + constexpr auto PUNA_EFFECT_COLOR = Vector4(0.0f, 0.4f, 0.5f, 0.5f); + + const auto PunaBossHeadBite = BiteInfo(Vector3::Zero, 8); + const auto PunaBossHandBite = BiteInfo(Vector3::Zero, 14); + + enum PunaState + { + PUNA_STATE_IDLE = 0, + PUNA_STATE_HEAD_ATTACK = 1, + PUNA_STATE_HAND_ATTACK = 2, + PUNA_STATE_DEATH = 3 + }; + + enum PunaAnim + { + PUNA_ANIM_IDLE = 0, + PUNA_ANIM_HEAD_ATTACK = 1, + PUNA_ANIM_HAND_ATTACK = 2, + PUNA_ANIM_DEATH = 3 + }; + + enum class PunaAttackType + { + AwaitPlayer, + DeathLightning, + SummonLightning, + Wait // Used while an active lizard is nearby. + }; + + static short GetPunaHeadOrientToTarget(ItemInfo& item, const Vector3& target) + { + if (!item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard)) + return NO_ITEM; + + auto pos = GetJointPosition(&item, PunaBossHeadBite.meshNum).ToVector3(); + auto orient = Geometry::GetOrientToPoint(pos, target); + return (orient.y - item.Pose.Orientation.y); + } + + static std::vector GetLizardEntityList(const ItemInfo& item) + { + auto entityList = std::vector{}; + + for (auto& currentEntity : g_Level.Items) + { + if (currentEntity.ObjectNumber == ID_LIZARD && + currentEntity.RoomNumber == item.RoomNumber && + currentEntity.HitPoints > 0 && + currentEntity.Status == ITEM_INVISIBLE && + !(currentEntity.Flags & IFLAG_KILLED)) + { + entityList.push_back(currentEntity.Index); + } + } + + return entityList; + } + + static Vector3 GetLizardTargetPosition(ItemInfo& item) + { + if (!item.TestFlagField((int)BossItemFlags::ItemNumber, NO_ITEM)) + { + const auto& targetEntity = g_Level.Items[item.GetFlagField((int)BossItemFlags::ItemNumber)]; + return targetEntity.Pose.Position.ToVector3(); + } + + // Failsafe. + const auto& creature = *GetCreatureInfo(&item); + return creature.Target.ToVector3(); + } + + static int GetLizardItemNumber(const ItemInfo& item) + { + if (!item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard)) + return NO_ITEM; + + auto lizardList = GetLizardEntityList(item); + if (lizardList.empty()) + return NO_ITEM; + + if (lizardList.size() == 1) + return lizardList[0]; + else + return lizardList[Random::GenerateInt(0, lizardList.size() - 1)]; + } + + static bool IsLizardActiveNearby(const ItemInfo& item, bool isInitializing = false) + { + for (auto& currentEntity : g_Level.Items) + { + // Check if the entity is a lizard. + if (currentEntity.ObjectNumber != ID_LIZARD) + continue; + + // Check if the entity is in the same room as Puna. + if (currentEntity.RoomNumber != item.RoomNumber) + continue; + + // If the entity is currently initializing, return early. + if (isInitializing) + return true; + + // Check status of entity. + if (currentEntity.HitPoints > 0 && + currentEntity.Status == ITEM_INVISIBLE && + !(currentEntity.Flags & IFLAG_KILLED)) + { + return true; + } + } + + return false; + } + + static void SpawnSummonSmoke(const Vector3& pos) + { + auto& smoke = *GetFreeParticle(); + + int scale = Random::GenerateInt(256, 384); + + smoke.on = true; + smoke.spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex; + smoke.blendMode = BLEND_MODES::BLENDMODE_ADDITIVE; + smoke.x = pos.x + Random::GenerateInt(-64, 64); + smoke.y = pos.y - Random::GenerateInt(0, 32); + smoke.z = pos.z + Random::GenerateInt(-64, 64); + smoke.xVel = Random::GenerateInt(-128, 128); + smoke.yVel = Random::GenerateInt(-32, -16); + smoke.zVel = Random::GenerateInt(-128, 128); + smoke.sR = 16; + smoke.sG = 64; + smoke.sB = 0; + smoke.dR = 8; + smoke.dG = 32; + smoke.dB = 0; + smoke.colFadeSpeed = Random::GenerateInt(16, 24); + smoke.fadeToBlack = 64; + smoke.sLife = + smoke.life = Random::GenerateInt(96, 112); + + smoke.extras = 0; + smoke.dynamic = -1; + smoke.friction = 0; + + if (Random::TestProbability(1 / 2.0f)) + { + smoke.rotAng = Random::GenerateAngle(); + smoke.rotAdd = Random::GenerateAngle(ANGLE(-0.06f), ANGLE(0.06f)); + smoke.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF | SP_WIND; + } + else + { + smoke.flags = SP_SCALE | SP_DEF | SP_EXPDEF | SP_WIND; + } + + smoke.scalar = 3; + smoke.gravity = Random::GenerateInt(-16, -8); + smoke.maxYvel = Random::GenerateInt(-12, -8); + smoke.size = + smoke.sSize = scale / 2; + smoke.dSize = scale; + } + + static void SpawnLizard(ItemInfo& item) + { + if (!item.TestFlagField((int)BossItemFlags::ItemNumber, NO_ITEM)) + { + auto itemNumber = item.GetFlagField((int)BossItemFlags::ItemNumber); + auto& currentItem = g_Level.Items[itemNumber]; + + for (int i = 0; i < 20; i++) + SpawnSummonSmoke(currentItem.Pose.Position.ToVector3()); + + AddActiveItem(itemNumber); + currentItem.ItemFlags[0] = 1; // Flag 1 = spawned lizard. + item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::Wait); + } + } + + static void SpawnPunaLightning(ItemInfo& item, const Vector3& pos, const BiteInfo& bite, bool isSummon) + { + const auto& creature = *GetCreatureInfo(&item); + + auto origin = GameVector(GetJointPosition(&item, bite.meshNum, bite.Position), item.RoomNumber); + + if (isSummon) + { + auto target = GameVector(pos, item.RoomNumber); + + TriggerLightning(&origin.ToVector3i(), &target.ToVector3i(), 1, 0, 255, 180, 30, LI_THININ | LI_SPLINE | LI_MOVEEND, 8, 12); + TriggerLightning(&origin.ToVector3i(), &target.ToVector3i(), 1, 180, 255, 0, 30, LI_THININ | LI_SPLINE | LI_MOVEEND, 3, 12); + TriggerLightning(&origin.ToVector3i(), &target.ToVector3i(), Random::GenerateInt(25, 50), 100, 200, 200, 30, LI_THININ | LI_THINOUT, 4, 12); + TriggerLightning(&origin.ToVector3i(), &target.ToVector3i(), Random::GenerateInt(25, 50), 100, 250, 255, 30, LI_THININ | LI_THINOUT, 2, 12); + + TriggerDynamicLight(origin.x, origin.y, origin.z, 20, 0, 255, 0); + SpawnLizard(item); + } + else + { + auto target = GameVector(Geometry::TranslatePoint(origin.ToVector3(), pos - origin.ToVector3(), PUNA_ATTACK_RANGE), creature.Enemy->RoomNumber); + + auto origin1 = GameVector(Geometry::TranslatePoint(origin.ToVector3(), pos - origin.ToVector3(), PUNA_ATTACK_RANGE / 4), creature.Enemy->RoomNumber); + auto origin2 = GameVector(Geometry::TranslatePoint(origin1.ToVector3(), pos - origin1.ToVector3(), PUNA_ATTACK_RANGE / 4), creature.Enemy->RoomNumber); + + auto target2 = GameVector(Geometry::TranslatePoint(origin.ToVector3(), pos - origin.ToVector3(), PUNA_ATTACK_RANGE / 6), creature.Enemy->RoomNumber); + auto target3 = GameVector(Geometry::TranslatePoint(origin1.ToVector3(), pos - origin1.ToVector3(), PUNA_ATTACK_RANGE / 10), creature.Enemy->RoomNumber); + + TriggerLightning(&origin.ToVector3i(), &target2.ToVector3i(), Random::GenerateInt(15, 40), 20, 160, 160, 20, LI_THINOUT | LI_THININ, 4, 6); + TriggerLightning(&origin.ToVector3i(), &target2.ToVector3i(), Random::GenerateInt(25, 35), 20, 160, 160, 20, LI_THINOUT | LI_THININ, 2, 7); + + TriggerLightning(&target2.ToVector3i(), &origin1.ToVector3i(), Random::GenerateInt(15, 40), 20, 160, 160, 20, LI_THINOUT | LI_THININ, 4, 6); + TriggerLightning(&target2.ToVector3i(), &origin1.ToVector3i(), Random::GenerateInt(25, 35), 20, 160, 160, 20, LI_THINOUT | LI_THININ, 2, 7); + + TriggerLightning(&origin1.ToVector3i(), &target3.ToVector3i(), Random::GenerateInt(15, 40), 20, 160, 160, 20, LI_THINOUT | LI_THININ, 4, 9); + TriggerLightning(&origin1.ToVector3i(), &target3.ToVector3i(), Random::GenerateInt(25, 35), 20, 160, 160, 20, LI_THINOUT | LI_THININ, 2, 10); + + TriggerLightning(&origin2.ToVector3i(), &target3.ToVector3i(), Random::GenerateInt(15, 40), 20, 160, 160, 16, LI_THINOUT | LI_THININ, 4, 7); + TriggerLightning(&origin2.ToVector3i(), &target3.ToVector3i(), Random::GenerateInt(25, 35), 20, 160, 160, 16, LI_THINOUT | LI_THININ, 2, 8); + + TriggerLightning(&origin.ToVector3i(), &target.ToVector3i(), 1, 20, 160, 160, 30, LI_THININ | LI_SPLINE | LI_MOVEEND, 12, 12); + TriggerLightning(&origin.ToVector3i(), &target.ToVector3i(), 1, 80, 160, 160, 30, LI_THININ | LI_SPLINE | LI_MOVEEND, 5, 12); + + TriggerDynamicLight(origin.x, origin.y, origin.z, 20, 0, 255, 255); + + auto hitPos = Vector3i::Zero; + MESH_INFO* mesh = nullptr; + if (ObjectOnLOS2(&origin, &target, &hitPos, &mesh, ID_LARA) == GetLaraInfo(creature.Enemy)->ItemNumber) + { + if (creature.Enemy->HitPoints <= PUNA_LIGHTNING_DAMAGE) + { + ItemElectricBurn(creature.Enemy); + DoDamage(creature.Enemy, PUNA_LIGHTNING_DAMAGE); + } + else + { + DoDamage(creature.Enemy, PUNA_LIGHTNING_DAMAGE); + } + } + } + } + + void InitialisePuna(short itemNumber) + { + auto& item = g_Level.Items[itemNumber]; + + InitialiseCreature(itemNumber); + SetAnimation(&item, PUNA_ANIM_IDLE); + CheckForRequiredObjects(item); + + // Save Puna's default angle. It will be used while waiting (i.e. an active lizard is nearby). + // NOTE: Since Puna is oriented to face away from the player, add 180 degrees. + item.SetFlagField((int)BossItemFlags::Rotation, item.Pose.Orientation.y + ANGLE(180.0f)); + item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::AwaitPlayer); // Normal behaviour at start. + item.SetFlagField((int)BossItemFlags::ShieldIsEnabled, 1); // Activated at start. + item.SetFlagField((int)BossItemFlags::AttackCount, 0); + item.SetFlagField((int)BossItemFlags::DeathCount, 0); + item.SetFlagField((int)BossItemFlags::ItemNumber, NO_ITEM); + item.SetFlagField((int)BossItemFlags::ExplodeCount, 0); + + // If there is no lizard nearby, remove the lizard flag. + if (!IsLizardActiveNearby(item, true)) + item.ClearFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard); + } + + void PunaControl(short itemNumber) + { + if (!CreatureActive(itemNumber)) + return; + + auto& item = g_Level.Items[itemNumber]; + auto& object = Objects[item.ObjectNumber]; + auto& creature = *GetCreatureInfo(&item); + + static auto targetPos = Vector3i::Zero; + + auto headOrient = EulerAngles::Zero; + short headingAngle = 0; + short prevYOrient = 0; + + bool hasTurned = false; + bool isLizardActiveNearby = IsLizardActiveNearby(item); + + if (item.HitPoints <= 0) + { + if (item.Animation.ActiveState != PUNA_STATE_DEATH) + { + SetAnimation(&item, PUNA_ANIM_DEATH); + SoundEffect(SFX_TR3_PUNA_BOSS_DEATH, &item.Pose); + item.ItemFlags[(int)BossItemFlags::DeathCount] = 1; + creature.MaxTurn = 0; + } + + int frameEnd = g_Level.Anims[object.animIndex + PUNA_ANIM_DEATH].frameEnd; + if (item.Animation.FrameNumber >= frameEnd) + { + // Avoid having the object stop working. + item.Animation.FrameNumber = frameEnd; + item.MeshBits.ClearAll(); + + if (item.GetFlagField((int)BossItemFlags::ExplodeCount) < PUNA_EXPLOSION_NUM_MAX) + item.ItemFlags[(int)BossItemFlags::ExplodeCount]++; + + // Do explosion effect. + ExplodeBoss(itemNumber, item, PUNA_EXPLOSION_NUM_MAX, PUNA_EFFECT_COLOR); + return; + } + else + { + auto deathCount = item.GetFlagField((int)BossItemFlags::DeathCount); + item.Pose.Orientation.z = (Random::GenerateInt() % deathCount) - (item.ItemFlags[(int)BossItemFlags::DeathCount] >> 1); + + if (deathCount < 2048) + item.ItemFlags[(int)BossItemFlags::DeathCount] += 32; + } + } + else + { + prevYOrient = item.Pose.Orientation.y; + + AI_INFO ai; + CreatureAIInfo(&item, &ai); + if (ai.ahead) + { + headOrient.x = ai.xAngle; + headOrient.y = ai.angle; + } + + if (item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::AwaitPlayer) && creature.Enemy != nullptr) + { + float distance = Vector3i::Distance(creature.Enemy->Pose.Position, item.Pose.Position); + + if (distance <= BLOCK(2.5f)) + item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::DeathLightning); + + // Rotate the object on puna boss chair. + creature.JointRotation[0] += PUNA_CHAIR_TURN_RATE_MAX; + return; + } + + // Get target. + if (item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::DeathLightning)) + { + creature.Target = creature.Enemy->Pose.Position; + } + else if (item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard) && + item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::SummonLightning) && + item.TestFlagField((int)BossItemFlags::ItemNumber, NO_ITEM) && + !item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::Wait) && isLizardActiveNearby) + { + // Get random lizard item number. + item.SetFlagField((int)BossItemFlags::ItemNumber, (short)GetLizardItemNumber(item)); + creature.Target = GetLizardTargetPosition(item); + } + else if (item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard) && + item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::Wait) && + !item.TestFlagField((int)BossItemFlags::ItemNumber, NO_ITEM)) + { + // Rotate to idle position while player fights lizard. + auto targetOrient = EulerAngles(item.Pose.Orientation.x, item.GetFlagField((int)BossItemFlags::Rotation), item.Pose.Orientation.z); + item.Pose.Orientation.InterpolateConstant(targetOrient, ANGLE(3.0f)); + + // Check if target is dead. + auto& summonItem = g_Level.Items[item.GetFlagField((int)BossItemFlags::ItemNumber)]; + + if (summonItem.HitPoints <= 0) + { + // Reset the attack type, attack count, itemNumber, and restart the sequence. + item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::DeathLightning); + item.SetFlagField((int)BossItemFlags::AttackCount, 0); + item.SetFlagField((int)BossItemFlags::ItemNumber, NO_ITEM); + } + } + + short headYOrient = GetPunaHeadOrientToTarget(item, creature.Target.ToVector3()); + headingAngle = CreatureTurn(&item, creature.MaxTurn); + + switch (item.Animation.ActiveState) + { + case PUNA_STATE_IDLE: + creature.MaxTurn = PUNA_TURN_RATE_MAX; + item.SetFlagField((int)BossItemFlags::ShieldIsEnabled, 1); + + if ((item.Animation.TargetState != PUNA_STATE_HAND_ATTACK && item.Animation.TargetState != PUNA_STATE_HEAD_ATTACK) && + ai.angle > ANGLE(-1.0f) && ai.angle < ANGLE(1.0f) && + creature.Enemy->HitPoints > 0 && + item.GetFlagField((int)BossItemFlags::AttackCount) < PUNA_HEAD_ATTACK_NUM_MAX && + !item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::SummonLightning) && !item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::Wait)) + { + creature.MaxTurn = 0; + targetPos = creature.Target; + targetPos.y -= CLICK(2); + item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::DeathLightning); + + if (Random::TestProbability(1 / 3.0f)) + item.Animation.TargetState = PUNA_STATE_HEAD_ATTACK; + else + item.Animation.TargetState = PUNA_STATE_HAND_ATTACK; + + if (item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard) && isLizardActiveNearby) + item.ItemFlags[(int)BossItemFlags::AttackCount]++; + } + else if (item.ItemFlags[(int)BossItemFlags::AttackCount] >= PUNA_HEAD_ATTACK_NUM_MAX && + creature.Enemy->HitPoints > 0 && + item.ItemFlags[(int)BossItemFlags::AttackType] != (int)PunaAttackType::Wait) + { + item.SetFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::SummonLightning); + + if (!item.TestFlagField((int)BossItemFlags::ItemNumber, NO_ITEM)) + { + if (headYOrient > ANGLE(-1.0f) && headYOrient < ANGLE(1.0f)) + { + targetPos = creature.Target; + targetPos.y -= CLICK(2); + item.Animation.TargetState = PUNA_STATE_HAND_ATTACK; + } + } + } + + break; + + case PUNA_STATE_HEAD_ATTACK: + item.SetFlagField((int)BossItemFlags::ShieldIsEnabled, 0); + creature.MaxTurn = 0; + + if (item.Animation.FrameNumber == GetFrameNumber(&item, 14)) + SpawnPunaLightning(item, targetPos.ToVector3(), PunaBossHeadBite, false); + + break; + + case PUNA_STATE_HAND_ATTACK: + item.SetFlagField((int)BossItemFlags::ShieldIsEnabled, 0); + creature.MaxTurn = 0; + + if (item.Animation.FrameNumber == GetFrameNumber(&item, 30)) + { + if (item.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Lizard) && + item.TestFlagField((int)BossItemFlags::AttackType, (int)PunaAttackType::SummonLightning) && + !item.TestFlagField((int)BossItemFlags::ItemNumber, NO_ITEM) && isLizardActiveNearby) + { + SpawnPunaLightning(item, targetPos.ToVector3(), PunaBossHandBite, true); + } + else + { + SpawnPunaLightning(item, targetPos.ToVector3(), PunaBossHandBite, false); + } + } + + break; + } + } + + // Rotate chair. + creature.JointRotation[0] += PUNA_CHAIR_TURN_RATE_MAX; + + CreatureJoint(&item, 1, headOrient.y); + CreatureJoint(&item, 2, headOrient.x, PUNA_HEAD_X_ANGLE_MAX); + CreatureAnimation(itemNumber, headingAngle, 0); + + // Emit sound while chair is rotating. + if (prevYOrient != item.Pose.Orientation.y && !hasTurned) + { + hasTurned = true; + SoundEffect(SFX_TR3_PUNA_BOSS_TURN_CHAIR, &item.Pose); + } + else if (prevYOrient == item.Pose.Orientation.y) + { + hasTurned = false; + StopSoundEffect(SFX_TR3_PUNA_BOSS_CHAIR_2); + StopSoundEffect(SFX_TR3_PUNA_BOSS_TURN_CHAIR); + } + } + + void PunaHit(ItemInfo& target, ItemInfo& source, std::optional pos, int damage, bool isExplosive, int jointIndex) + { + if (pos.has_value()) + { + if (target.TestFlags((int)BossItemFlags::Object, (short)BossFlagValue::Shield) && + target.TestFlagField((int)BossItemFlags::ShieldIsEnabled, 1)) + { + auto color = Vector4( + 0.0f, Random::GenerateFloat(0.0f, 0.5f), Random::GenerateFloat(0.0f, 0.5f), + Random::GenerateFloat(0.5f, 0.8f)); + + SpawnShieldAndRichochetSparks(target, pos->ToVector3(), color); + } + else + { + if (target.HitStatus) + SoundEffect(SFX_TR3_PUNA_BOSS_TAKE_HIT, &target.Pose); + + DoBloodSplat(pos->x, pos->y, pos->z, 5, source.Pose.Orientation.y, pos->RoomNumber); + DoItemHit(&target, damage, isExplosive); + } + } + } +} diff --git a/TombEngine/Objects/TR3/Entity/PunaBoss.h b/TombEngine/Objects/TR3/Entity/PunaBoss.h new file mode 100644 index 000000000..b19778381 --- /dev/null +++ b/TombEngine/Objects/TR3/Entity/PunaBoss.h @@ -0,0 +1,11 @@ +#pragma once + +class GameVector; +struct ItemInfo; + +namespace TEN::Entities::Creatures::TR3 +{ + void InitialisePuna(short itemNumber); + void PunaControl(short itemNumber); + void PunaHit(ItemInfo& target, ItemInfo& source, std::optional pos, int damage, bool isExplosive, int jointIndex); +} diff --git a/TombEngine/Objects/TR3/Entity/tr3_mp_gun.cpp b/TombEngine/Objects/TR3/Entity/tr3_mp_gun.cpp index 76a38c19a..70e2190af 100644 --- a/TombEngine/Objects/TR3/Entity/tr3_mp_gun.cpp +++ b/TombEngine/Objects/TR3/Entity/tr3_mp_gun.cpp @@ -122,9 +122,8 @@ namespace TEN::Entities::Creatures::TR3 laraAI.distance = pow(dx, 2) + pow(dx, 2); - for (int slot = 0; slot < ActiveCreatures.size(); slot++) + for (auto& currentCreature : ActiveCreatures) { - auto* currentCreature = ActiveCreatures[slot]; if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber) continue; diff --git a/TombEngine/Objects/TR3/Entity/tr3_mp_stick.cpp b/TombEngine/Objects/TR3/Entity/tr3_mp_stick.cpp index 87390bb58..a03a232cd 100644 --- a/TombEngine/Objects/TR3/Entity/tr3_mp_stick.cpp +++ b/TombEngine/Objects/TR3/Entity/tr3_mp_stick.cpp @@ -105,9 +105,8 @@ namespace TEN::Entities::Creatures::TR3 laraAI.distance = pow(dx, 2) + pow(dx, 2); int bestDistance = INT_MAX; - for (int slot = 0; slot < ActiveCreatures.size(); slot++) + for (auto& currentCreature : ActiveCreatures) { - auto* currentCreature = ActiveCreatures[slot]; if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber) continue; diff --git a/TombEngine/Objects/TR3/Entity/tr3_raptor.cpp b/TombEngine/Objects/TR3/Entity/tr3_raptor.cpp index 1125655a9..d5983b544 100644 --- a/TombEngine/Objects/TR3/Entity/tr3_raptor.cpp +++ b/TombEngine/Objects/TR3/Entity/tr3_raptor.cpp @@ -98,15 +98,12 @@ namespace TEN::Entities::Creatures::TR3 ItemInfo* nearestItem = nullptr; float minDistance = FLT_MAX; - for (auto* activeCreature : ActiveCreatures) + for (auto& currentCreature : ActiveCreatures) { - if (activeCreature->ItemNumber == NO_ITEM || activeCreature->ItemNumber == itemNumber) - { - activeCreature++; + if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber) continue; - } - auto* targetItem = &g_Level.Items[activeCreature->ItemNumber]; + auto* targetItem = &g_Level.Items[currentCreature->ItemNumber]; int distance = Vector3i::Distance(item->Pose.Position, targetItem->Pose.Position); if (distance < minDistance && item->HitPoints > 0) @@ -114,8 +111,6 @@ namespace TEN::Entities::Creatures::TR3 nearestItem = targetItem; minDistance = distance; } - - activeCreature++; } if (nearestItem != nullptr && diff --git a/TombEngine/Objects/TR3/Entity/tr3_shiva.cpp b/TombEngine/Objects/TR3/Entity/tr3_shiva.cpp index 226037759..71f259daf 100644 --- a/TombEngine/Objects/TR3/Entity/tr3_shiva.cpp +++ b/TombEngine/Objects/TR3/Entity/tr3_shiva.cpp @@ -82,17 +82,7 @@ namespace TEN::Entities::Creatures::TR3 }; - void SwapShivaMeshToStone(ItemInfo& item, int jointIndex) - { - item.Model.MeshIndex[jointIndex] = Objects[ID_SHIVA_STATUE].meshIndex + jointIndex; - } - - void SwapShivaMeshToNormal(ItemInfo& item, int jointIndex) - { - item.Model.MeshIndex[jointIndex] = Objects[ID_SHIVA].meshIndex + jointIndex; - } - - void ShivaDamage(ItemInfo* item, CreatureInfo* creature, int damage) + static void ShivaDamage(ItemInfo* item, CreatureInfo* creature, int damage) { if (!creature->Flags && item->TouchBits.Test(ShivaAttackRightJoints)) { @@ -111,7 +101,17 @@ namespace TEN::Entities::Creatures::TR3 } } - bool DoShivaSwapMesh(ItemInfo& item, bool isDead) + static void SwapShivaMeshToStone(ItemInfo& item, int jointIndex) + { + item.Model.MeshIndex[jointIndex] = Objects[ID_SHIVA_STATUE].meshIndex + jointIndex; + } + + static void SwapShivaMeshToNormal(ItemInfo& item, int jointIndex) + { + item.Model.MeshIndex[jointIndex] = Objects[ID_SHIVA].meshIndex + jointIndex; + } + + static bool DoShivaSwapMesh(ItemInfo& item, bool isDead) { const auto& object = Objects[item.ObjectNumber]; auto& creature = *GetCreatureInfo(&item); @@ -122,11 +122,11 @@ namespace TEN::Entities::Creatures::TR3 if (isDead && item.ItemFlags[0] < 0) { - item.SetFlags(2, 0); + item.SetFlagField(2, 0); } else if (!isDead && item.ItemFlags[0] >= object.nmeshes) { - item.SetFlags(2, 0); + item.SetFlagField(2, 0); } else { @@ -154,19 +154,102 @@ namespace TEN::Entities::Creatures::TR3 { item.Animation.TargetState = SHIVA_STATE_IDLE; creature.Flags = -45; - item.SetFlags(1, 0); - item.SetFlags(1, 1); // Is alive (for savegame). + item.SetFlagField(1, 0); + item.SetFlagField(1, 1); // Is alive (for savegame). } else if (item.TestFlags(2, 0) && isDead) { - item.SetFlags(1, 0); - item.SetFlags(1, 2); // Is dead. + item.SetFlagField(1, 0); + item.SetFlagField(1, 2); // Is dead. return true; } return false; } + static void SpawnShivaSmoke(const Vector3& pos, int roomNumber) + { + auto& smoke = *GetFreeParticle(); + + bool isUnderwater = TestEnvironment(ENV_FLAG_WATER, Vector3i(pos), roomNumber); + auto sphere = BoundingSphere(pos, 16); + auto effectPos = Random::GeneratePointInSphere(sphere); + + smoke.on = true; + smoke.blendMode = BLEND_MODES::BLENDMODE_ADDITIVE; + + smoke.x = effectPos.x; + smoke.y = effectPos.y; + smoke.z = effectPos.z; + smoke.xVel = Random::GenerateInt(-BLOCK(0.5f), BLOCK(0.5f)); + smoke.yVel = Random::GenerateInt(-BLOCK(1 / 8.0f), BLOCK(1 / 8.0f)); + smoke.zVel = Random::GenerateInt(-BLOCK(0.5f), BLOCK(0.5f)); + + if (isUnderwater) + { + smoke.sR = 144; + smoke.sG = 144; + smoke.sB = 144; + smoke.dR = 64; + smoke.dG = 64; + smoke.dB = 64; + } + else + { + smoke.sR = 0; + smoke.sG = 0; + smoke.sB = 0; + smoke.dR = 192; + smoke.dG = 192; + smoke.dB = 208; + } + + smoke.colFadeSpeed = 8; + smoke.fadeToBlack = 64; + smoke.sLife = + smoke.life = Random::GenerateInt(96, 128); + smoke.extras = 0; + smoke.dynamic = -1; + + if (isUnderwater) + { + smoke.yVel /= 16; + smoke.y += 32; + smoke.friction = 4 | 16; + } + else + { + smoke.friction = 6; + } + + smoke.rotAng = Random::GenerateAngle(); + smoke.rotAdd = Random::GenerateAngle(ANGLE(-0.2f), ANGLE(0.2f)); + + smoke.scalar = 3; + + if (isUnderwater) + { + smoke.gravity = 0; + smoke.maxYvel = 0; + } + else + { + smoke.gravity = Random::GenerateInt(-8, -4); + smoke.maxYvel = Random::GenerateInt(-8, -4); + } + + int scale = Random::GenerateInt(128, 160); + smoke.size = + smoke.sSize = scale / 4; + smoke.dSize = scale; + + scale += Random::GenerateInt(32, 64); + smoke.size = + smoke.sSize = scale / 8; + smoke.dSize = scale; + smoke.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; + } + void InitialiseShiva(short itemNumber) { auto& item = g_Level.Items[itemNumber]; @@ -177,7 +260,7 @@ namespace TEN::Entities::Creatures::TR3 item.Status &= ~ITEM_INVISIBLE; // Joint index used for swapping mesh. - item.SetFlags(0, 0); + item.SetFlagField(0, 0); if (item.TestFlags(1, 0)) { @@ -185,7 +268,7 @@ namespace TEN::Entities::Creatures::TR3 SwapShivaMeshToStone(item, jointIndex); // Continue transition until finished. - item.SetFlags(2, 1); + item.SetFlagField(2, 1); } } @@ -208,10 +291,10 @@ namespace TEN::Entities::Creatures::TR3 if (item->Animation.ActiveState != SHIVA_STATE_DEATH) { SetAnimation(item, SHIVA_ANIM_DEATH); - item->ItemFlags[0] = object.nmeshes - 1; + item->SetFlagField(0, object.nmeshes - 1); // Redo mesh swap to stone. - item->SetFlags(2, 2); + item->SetFlagField(2, 2); } int frameEnd = g_Level.Anims[object.animIndex + SHIVA_ANIM_DEATH].frameEnd - 1; @@ -232,6 +315,7 @@ namespace TEN::Entities::Creatures::TR3 GetCreatureMood(item, &ai, true); CreatureMood(item, &ai, true); + // Shiva don't resent fear. if (creature->Mood == MoodType::Escape) { creature->Target.x = creature->Enemy->Pose.Position.x; @@ -463,107 +547,24 @@ namespace TEN::Entities::Creatures::TR3 void ShivaHit(ItemInfo& target, ItemInfo& source, std::optional pos, int damage, bool isExplosive, int jointIndex) { + if (!pos.has_value()) + return; + const auto& player = *GetLaraInfo(&source); const auto& object = Objects[target.ObjectNumber]; // If guarded, ricochet without damage. if ((target.Animation.ActiveState == SHIVA_STATE_WALK_FORWARD_GUARDING || - target.Animation.ActiveState == SHIVA_STATE_GUARD_IDLE) && pos.has_value()) + target.Animation.ActiveState == SHIVA_STATE_GUARD_IDLE)) { SoundEffect(SFX_TR4_BADDY_SWORD_RICOCHET, &target.Pose); TriggerRicochetSpark(*pos, source.Pose.Orientation.y, 3, 0); } // Do basic hit effect. - else if (object.hitEffect == HitEffect::Blood && pos.has_value()) + else if (object.hitEffect == HitEffect::Blood) { - DoBloodSplat(pos->x, pos->y, pos->z, 10, source.Pose.Orientation.y, pos->RoomNumber); + DoBloodSplat(pos->x, pos->y, pos->z, Random::GenerateInt(4, 8), source.Pose.Orientation.y, pos->RoomNumber); DoItemHit(&target, damage, isExplosive); } } - - void SpawnShivaSmoke(const Vector3& pos, int roomNumber) - { - auto& smoke = *GetFreeParticle(); - - bool isUnderwater = TestEnvironment(ENV_FLAG_WATER, Vector3i(pos), roomNumber); - auto sphere = BoundingSphere(pos, 16); - auto randPos = Random::GeneratePointInSphere(sphere); - - if (isUnderwater) - { - smoke.sR = 144; - smoke.sG = 144; - smoke.sB = 144; - smoke.dR = 64; - smoke.dG = 64; - smoke.dB = 64; - } - else - { - smoke.sR = 0; - smoke.sG = 0; - smoke.sB = 0; - smoke.dR = 192; - smoke.dG = 192; - smoke.dB = 208; - } - - smoke.colFadeSpeed = 8; - smoke.fadeToBlack = 64; - smoke.sLife = - smoke.life = Random::GenerateInt(96, 128); - smoke.blendMode = BLEND_MODES::BLENDMODE_ADDITIVE; - smoke.extras = 0; - smoke.dynamic = -1; - - smoke.x = randPos.x; - smoke.y = randPos.y; - smoke.z = randPos.z; - smoke.xVel = ((GetRandomControl() & 4095) - 2048) / 4; - smoke.yVel = (GetRandomControl() & 255) - 128; - smoke.zVel = ((GetRandomControl() & 4095) - 2048) / 4; - - if (isUnderwater) - { - smoke.yVel /= 16; - smoke.y += 32; - smoke.friction = 4 | (16); - } - else - { - smoke.friction = 6; - } - - smoke.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; - smoke.rotAng = GetRandomControl() & 4095; - - if (Random::TestProbability(1 / 2.0f)) - smoke.rotAdd = -(GetRandomControl() & 15) - 16; - else - smoke.rotAdd = (GetRandomControl() & 15) + 16; - - smoke.scalar = 3; - - if (isUnderwater) - { - smoke.gravity = - smoke.maxYvel = 0; - } - else - { - smoke.gravity = -(GetRandomControl() & 3) - 3; - smoke.maxYvel = -(GetRandomControl() & 3) - 4; - } - - int size = (GetRandomControl() & 31) + 128; - smoke.size = - smoke.sSize = size / 4; - smoke.dSize = size; - - size += (GetRandomControl() & 31) + 32; - smoke.size = - smoke.sSize = size / 8; - smoke.dSize = size; - smoke.on = true; - } } diff --git a/TombEngine/Objects/TR3/Entity/tr3_shiva.h b/TombEngine/Objects/TR3/Entity/tr3_shiva.h index 25f9bc18d..614bb8882 100644 --- a/TombEngine/Objects/TR3/Entity/tr3_shiva.h +++ b/TombEngine/Objects/TR3/Entity/tr3_shiva.h @@ -7,7 +7,5 @@ namespace TEN::Entities::Creatures::TR3 { void InitialiseShiva(short itemNumber); void ShivaControl(short itemNumber); - void ShivaHit(ItemInfo& target, ItemInfo& source, std::optional pos, int damage, bool isExplosive, int jointIndex); - void SpawnShivaSmoke(const Vector3& pos, int roomNumber); } diff --git a/TombEngine/Objects/TR3/Vehicles/quad_bike.cpp b/TombEngine/Objects/TR3/Vehicles/quad_bike.cpp index 8f8a271fa..9a9798fa3 100644 --- a/TombEngine/Objects/TR3/Vehicles/quad_bike.cpp +++ b/TombEngine/Objects/TR3/Vehicles/quad_bike.cpp @@ -1067,7 +1067,8 @@ namespace TEN::Entities::Vehicles if (spark->sLife < 9) spark->sLife = spark->life = 9; - spark->blendMode = BLEND_MODES::BLENDMODE_SCREEN; + // TODO: Switch back to screen blend mode once rendering for it is refactored. -- Sezz 2023.01.14 + spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE; spark->colFadeSpeed = 4; spark->fadeToBlack = 4; spark->extras = 0; diff --git a/TombEngine/Objects/TR3/tr3_objects.cpp b/TombEngine/Objects/TR3/tr3_objects.cpp index cad09b850..e4ac73987 100644 --- a/TombEngine/Objects/TR3/tr3_objects.cpp +++ b/TombEngine/Objects/TR3/tr3_objects.cpp @@ -1,6 +1,7 @@ #include "framework.h" #include "Objects/TR3/tr3_objects.h" +#include "Objects/TR5/Object/tr5_missile.h" #include "Game/collision/collide_item.h" #include "Game/control/box.h" #include "Game/itemdata/creature_info.h" @@ -23,6 +24,11 @@ #include "Objects/TR3/Entity/tr3_tony.h" // OK #include "Objects/TR3/Entity/tr3_trex.h" // OK #include "Objects/TR3/Entity/tr3_tribesman.h" // OK +#include "Objects/TR3/Entity/Lizard.h" // OK +#include "Objects/TR3/Entity/PunaBoss.h" // OK + +// Effects +#include "Objects/Effects/Boss.h" // Traps #include "Objects/TR3/Trap/train.h" @@ -34,8 +40,10 @@ #include "Objects/TR3/Vehicles/quad_bike.h" #include "Objects/TR3/Vehicles/upv.h" #include "Objects/TR3/Vehicles/rubber_boat.h" +#include "Objects/Utils/object_helper.h" using namespace TEN::Entities::Creatures::TR3; +using namespace TEN::Effects::Boss; static void StartEntity(ObjectInfo* obj) { @@ -198,6 +206,8 @@ static void StartEntity(ObjectInfo* obj) obj = &Objects[ID_MONKEY]; if (obj->loaded) { + CheckIfSlotExists(ID_MESHSWAP_MONKEY_KEY, "ID_MONKEY", "ID_MESHSWAP_MONKEY_KEY"); + CheckIfSlotExists(ID_MESHSWAP_MONKEY_MEDIPACK, "ID_MONKEY", "ID_MESHSWAP_MONKEY_MEDIPACK"); obj->initialise = InitialiseMonkey; obj->control = MonkeyControl; obj->collision = CreatureCollision; @@ -248,6 +258,7 @@ static void StartEntity(ObjectInfo* obj) obj = &Objects[ID_SHIVA]; if (obj->loaded) { + CheckIfSlotExists(ID_SHIVA_STATUE, "ID_SHIVA", "ID_SHIVA_STATUE"); obj->initialise = InitialiseShiva; obj->collision = CreatureCollision; obj->control = ShivaControl; @@ -297,11 +308,70 @@ static void StartEntity(ObjectInfo* obj) obj->SetBoneRotationFlags(13, ROT_Y); obj->SetupHitEffect(); } + + obj = &Objects[ID_LIZARD]; + if (obj->loaded) + { + obj->initialise = InitialiseCreature; + obj->control = LizardControl; + obj->collision = CreatureCollision; + obj->shadowType = ShadowMode::All; + obj->HitPoints = 36; + obj->intelligent = true; + obj->pivotLength = 0; + obj->radius = 204; + obj->LotType = LotType::Human; + obj->SetBoneRotationFlags(9, ROT_X | ROT_Z); + obj->SetupHitEffect(); + } + + obj = &Objects[ID_PUNA_BOSS]; + if (obj->loaded) + { + obj->initialise = InitialisePuna; + obj->control = PunaControl; + obj->collision = CreatureCollision; + obj->HitRoutine = PunaHit; + obj->shadowType = ShadowMode::All; + obj->HitPoints = 200; + obj->intelligent = true; + obj->nonLot = true; + obj->radius = 102; + obj->pivotLength = 50; + obj->SetBoneRotationFlags(4, ROT_Y); // Puna quest object. + obj->SetBoneRotationFlags(7, ROT_X | ROT_Y); // Head. + obj->SetupHitEffect(); + } } static void StartObject(ObjectInfo* obj) { + obj = &Objects[ID_BOSS_SHIELD]; + if (obj->loaded) + { + obj->initialise = nullptr; + obj->collision = ObjectCollision; + obj->control = ShieldControl; + obj->shadowType = ShadowMode::None; + } + obj = &Objects[ID_BOSS_EXPLOSION_RING]; + if (obj->loaded) + { + obj->initialise = nullptr; + obj->collision = nullptr; + obj->control = ShockwaveRingControl; + obj->shadowType = ShadowMode::None; + } + + obj = &Objects[ID_BOSS_EXPLOSION_SHOCKWAVE]; + if (obj->loaded) + { + obj->initialise = nullptr; + obj->collision = nullptr; + obj->control = ShockwaveExplosionControl; + obj->shadowType = ShadowMode::None; + } } static void StartTrap(ObjectInfo* obj) diff --git a/TombEngine/Objects/TR4/Entity/tr4_baddy.cpp b/TombEngine/Objects/TR4/Entity/tr4_baddy.cpp index a471552ad..3a031c5e8 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_baddy.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_baddy.cpp @@ -1267,37 +1267,27 @@ namespace TEN::Entities::TR4 switch (vault) { case 2: - item->Animation.AnimNumber = Objects[objectNumber].animIndex + BADDY_ANIM_CLIMB_2_STEPS; - item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase; - item->Animation.ActiveState = BADDY_STATE_CLIMB_2_STEPS; + SetAnimation(item, BADDY_ANIM_CLIMB_2_STEPS); creature->MaxTurn = 0; break; case 3: - item->Animation.AnimNumber = Objects[objectNumber].animIndex + BADDY_ANIM_CLIMB_3_STEPS; - item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase; - item->Animation.ActiveState = BADDY_STATE_CLIMB_3_STEPS; + SetAnimation(item, BADDY_ANIM_CLIMB_3_STEPS); creature->MaxTurn = 0; break; case 4: - item->Animation.AnimNumber = Objects[objectNumber].animIndex + BADDY_ANIM_CLIMB_4_STEPS; - item->Animation.ActiveState = BADDY_STATE_CLIMB_4_STEPS; - item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase; + SetAnimation(item, BADDY_ANIM_CLIMB_4_STEPS); creature->MaxTurn = 0; break; case -3: - item->Animation.AnimNumber = Objects[objectNumber].animIndex + BADDY_ANIM_JUMP_OFF_3_STEPS; - item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase; - item->Animation.ActiveState = BADDY_STATE_JUMP_OFF_3_STEPS; + SetAnimation(item, BADDY_ANIM_JUMP_OFF_3_STEPS); creature->MaxTurn = 0; break; case -4: - item->Animation.AnimNumber = Objects[objectNumber].animIndex + BADDY_ANIM_JUMP_OFF_4_STEPS; - item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase; - item->Animation.ActiveState = BADDY_STATE_JUMP_OFF_4_STEPS; + SetAnimation(item, BADDY_ANIM_JUMP_OFF_4_STEPS); creature->MaxTurn = 0; break; @@ -1307,9 +1297,7 @@ namespace TEN::Entities::TR4 } else { - item->Animation.AnimNumber = Objects[objectNumber].animIndex + BADDY_ANIM_BLIND; - item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase + (GetRandomControl() & 7); - item->Animation.ActiveState = BADDY_STATE_BLIND; + SetAnimation(item, BADDY_ANIM_BLIND, Random::GenerateInt(0, 8)); creature->MaxTurn = 0; } } @@ -1321,21 +1309,21 @@ namespace TEN::Entities::TR4 if (pos.has_value()) { - if ((target.Animation.ActiveState == BADDY_STATE_DODGE || Random::TestProbability(1 / 2.0f)) && + if (target.Animation.ActiveState == BADDY_STATE_DODGE && (player.Control.Weapon.GunType == LaraWeaponType::Pistol || player.Control.Weapon.GunType == LaraWeaponType::Shotgun || player.Control.Weapon.GunType == LaraWeaponType::Uzi || player.Control.Weapon.GunType == LaraWeaponType::HK || player.Control.Weapon.GunType == LaraWeaponType::Revolver)) { - // Baddy2 sword deflecting bullet. + // Baddy2 bullet deflection with sword. SoundEffect(SFX_TR4_BADDY_SWORD_RICOCHET, &target.Pose); TriggerRicochetSpark(*pos, source.Pose.Orientation.y, 3, 0); return; } else if (object.hitEffect == HitEffect::Blood) { - DoBloodSplat(pos->x, pos->y, pos->z, 10, source.Pose.Orientation.y, pos->RoomNumber); + DoBloodSplat(pos->x, pos->y, pos->z, Random::GenerateInt(4, 8), source.Pose.Orientation.y, pos->RoomNumber); } } diff --git a/TombEngine/Objects/TR4/Entity/tr4_big_scorpion.cpp b/TombEngine/Objects/TR4/Entity/tr4_big_scorpion.cpp index 2457cd54a..92b4fc857 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_big_scorpion.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_big_scorpion.cpp @@ -136,11 +136,11 @@ namespace TEN::Entities::TR4 creature->Enemy = nullptr; float minDistance = FLT_MAX; - for (auto* activeCreature : ActiveCreatures) + for (auto& currentCreature : ActiveCreatures) { - if (activeCreature->ItemNumber != NO_ITEM && activeCreature->ItemNumber != itemNumber) + if (currentCreature->ItemNumber != NO_ITEM && currentCreature->ItemNumber != itemNumber) { - auto* currentItem = &g_Level.Items[activeCreature->ItemNumber]; + auto* currentItem = &g_Level.Items[currentCreature->ItemNumber]; if (currentItem->ObjectNumber != ID_LARA) { if (currentItem->ObjectNumber != ID_BIG_SCORPION && diff --git a/TombEngine/Objects/TR4/Entity/tr4_demigod.cpp b/TombEngine/Objects/TR4/Entity/tr4_demigod.cpp index 187b39831..e4873c697 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_demigod.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_demigod.cpp @@ -23,6 +23,12 @@ using namespace TEN::Math::Random; namespace TEN::Entities::TR4 { + constexpr auto DEMIGOD_IDLE_RANGE = SQUARE(BLOCK(2)); + constexpr auto DEMIGOD_WALK_RANGE = SQUARE(BLOCK(3)); + constexpr auto DEMIGOD1_WALK_RANGE = SQUARE(BLOCK(3)); + constexpr auto DEMIGOD2_RADIAL_PROJECTILE_ATTACK_RANGE = SQUARE(BLOCK(5)); + constexpr auto DEMIGOD3_RADIAL_PROJECTILE_ATTACK_RANGE = SQUARE(BLOCK(5)); + enum DemigodState { DEMIGOD_STATE_IDLE = 0, @@ -122,8 +128,8 @@ namespace TEN::Entities::TR4 int dx = LaraItem->Pose.Position.x - fx->pos.Position.x; int dz = LaraItem->Pose.Position.z - fx->pos.Position.z; - if (dx >= -SECTOR(16) && dx <= SECTOR(16) && - dz >= -SECTOR(16) && dz <= SECTOR(16)) + if (dx >= -BLOCK(16) && dx <= BLOCK(16) && + dz >= -BLOCK(16) && dz <= BLOCK(16)) { auto* spark = GetFreeParticle(); @@ -200,7 +206,7 @@ namespace TEN::Entities::TR4 fx->flag1 = flags; fx->speed = (GetRandomControl() & 0x1F) + 96; fx->objectNumber = ID_ENERGY_BUBBLES; - fx->frameNumber = Objects[ID_ENERGY_BUBBLES].meshIndex + (flags >= 4, flags - 1, flags); + fx->frameNumber = Objects[ID_ENERGY_BUBBLES].meshIndex + ((flags >= 4) ? flags - 1 : flags); } } @@ -436,7 +442,7 @@ namespace TEN::Entities::TR4 if (item->ObjectNumber == ID_DEMIGOD1) { - if (AI.distance >= pow(SECTOR(3), 2)) + if (AI.distance >= DEMIGOD1_WALK_RANGE) { item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD; break; @@ -451,7 +457,7 @@ namespace TEN::Entities::TR4 break; } - if (AI.distance <= pow(SECTOR(3), 2)) + if (AI.distance <= DEMIGOD1_WALK_RANGE) { item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD; break; @@ -471,15 +477,15 @@ namespace TEN::Entities::TR4 break; } - if (AI.distance <= pow(SECTOR(2), 2) || - AI.distance >= pow(SECTOR(5), 2)) - { - item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD; - break; - } - if (item->ObjectNumber == ID_DEMIGOD3) { + if (AI.distance <= DEMIGOD_IDLE_RANGE || + AI.distance >= DEMIGOD3_RADIAL_PROJECTILE_ATTACK_RANGE) + { + item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD; + break; + } + if (TestProbability(0.25f)) { item->Animation.TargetState = DEMIGOD3_STATE_RADIAL_AIM; @@ -488,13 +494,17 @@ namespace TEN::Entities::TR4 } } - item->Animation.TargetState = DEMIGOD2_STATE_RADIAL_PROJECTILE_ATTACK; + if (AI.distance > DEMIGOD_WALK_RANGE && item->ObjectNumber == ID_DEMIGOD2) + item->Animation.TargetState = DEMIGOD2_STATE_RADIAL_AIM; + else + item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD; + break; case DEMIGOD_STATE_WALK_FORWARD: creature->MaxTurn = ANGLE(7.0f); - if (AI.distance < pow(SECTOR(2), 2)) + if (AI.distance < DEMIGOD_IDLE_RANGE) { item->Animation.TargetState = DEMIGOD_STATE_IDLE; break; @@ -502,7 +512,7 @@ namespace TEN::Entities::TR4 if (item->ObjectNumber == ID_DEMIGOD1) { - if (AI.distance < pow(SECTOR(3), 2)) + if (AI.distance < DEMIGOD1_WALK_RANGE) { item->Animation.TargetState = DEMIGOD_STATE_IDLE; break; @@ -517,7 +527,7 @@ namespace TEN::Entities::TR4 } } - if (AI.distance > pow(SECTOR(3), 2)) + if (AI.distance > DEMIGOD_WALK_RANGE) { if (item->ObjectNumber == ID_DEMIGOD2) item->Animation.TargetState = DEMIGOD2_STATE_RADIAL_PROJECTILE_ATTACK; @@ -530,7 +540,7 @@ namespace TEN::Entities::TR4 case DEMIGOD_STATE_RUN_FORWARD: creature->MaxTurn = ANGLE(7.0f); - if (AI.distance < pow(SECTOR(2), 2)) + if (AI.distance < DEMIGOD_IDLE_RANGE) { item->Animation.TargetState = DEMIGOD_STATE_IDLE; break; @@ -538,7 +548,7 @@ namespace TEN::Entities::TR4 if (item->ObjectNumber == ID_DEMIGOD1) { - if (AI.distance < pow(SECTOR(3), 2)) + if (AI.distance < DEMIGOD1_WALK_RANGE) { item->Animation.TargetState = DEMIGOD_STATE_IDLE; break; @@ -546,13 +556,13 @@ namespace TEN::Entities::TR4 } else { - if (Targetable(item, &AI) || item->ObjectNumber == ID_DEMIGOD3 && AI.distance > pow(SECTOR(2), 2)) + if (Targetable(item, &AI) || item->ObjectNumber == ID_DEMIGOD3 && AI.distance > DEMIGOD_IDLE_RANGE) { item->Animation.TargetState = DEMIGOD_STATE_IDLE; break; } - if (AI.distance < pow(SECTOR(3), 2)) + if (AI.distance < DEMIGOD_WALK_RANGE) item->Animation.TargetState = DEMIGOD_STATE_WALK_FORWARD; } @@ -594,7 +604,7 @@ namespace TEN::Entities::TR4 case DEMIGOD2_STATE_RADIAL_PROJECTILE_ATTACK: creature->MaxTurn = ANGLE(7.0f); - + if (Targetable(item, &AI)) item->Animation.TargetState = DEMIGOD2_STATE_RADIAL_UNAIM; @@ -602,17 +612,25 @@ namespace TEN::Entities::TR4 case DEMIGOD3_STATE_RADIAL_AIM: creature->MaxTurn = ANGLE(7.0f); - if (!Targetable(item, &AI) && AI.distance < pow(SECTOR(5), 2)) + + if (!Targetable(item, &AI) && AI.distance < DEMIGOD3_RADIAL_PROJECTILE_ATTACK_RANGE) item->Animation.TargetState = DEMIGOD3_STATE_RADIAL_PROJECTILE_ATTACK; break; + case DEMIGOD2_STATE_RADIAL_AIM: + creature->MaxTurn = ANGLE(7.0f); + if (!Targetable(item, &AI) && AI.distance < DEMIGOD2_RADIAL_PROJECTILE_ATTACK_RANGE) + item->Animation.TargetState = DEMIGOD2_STATE_RADIAL_PROJECTILE_ATTACK; + + break; + case DEMIGOD3_STATE_RADIAL_PROJECTILE_ATTACK: creature->MaxTurn = ANGLE(7.0f); DoDemigodEffects(itemNumber); - if (!Targetable(item, &AI) || AI.distance < pow(SECTOR(5), 2) || !GetRandomControl()) + if (!Targetable(item, &AI) || AI.distance < DEMIGOD3_RADIAL_PROJECTILE_ATTACK_RANGE || !GetRandomControl()) { item->Animation.TargetState = DEMIGOD_STATE_IDLE; break; @@ -663,7 +681,7 @@ namespace TEN::Entities::TR4 else item->Pose.Orientation.y += AI.angle; - if (AI.distance >= pow(SECTOR(3), 2) || + if (AI.distance >= DEMIGOD1_WALK_RANGE || !AI.bite && (LaraItem->Animation.ActiveState < LS_LADDER_IDLE || LaraItem->Animation.ActiveState > LS_LADDER_DOWN || diff --git a/TombEngine/Objects/TR4/Entity/tr4_guide.cpp b/TombEngine/Objects/TR4/Entity/tr4_guide.cpp index f0a9904c8..dde8a1838 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_guide.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_guide.cpp @@ -191,18 +191,15 @@ namespace TEN::Entities::TR4 { int minDistance = INT_MAX; - for (int i = 0; i < ActiveCreatures.size(); i++) + for (auto& currentCreature : ActiveCreatures) { - auto* currentCreatureInfo = ActiveCreatures[i]; - - if (currentCreatureInfo->ItemNumber == NO_ITEM || - currentCreatureInfo->ItemNumber == itemNumber) + if (currentCreature->ItemNumber == NO_ITEM || + currentCreature->ItemNumber == itemNumber) { continue; } - auto* currentItem = &g_Level.Items[currentCreatureInfo->ItemNumber]; - + auto* currentItem = &g_Level.Items[currentCreature->ItemNumber]; if (currentItem->ObjectNumber != ID_GUIDE && abs(currentItem->Pose.Position.y - item->Pose.Position.y) <= 512) { diff --git a/TombEngine/Objects/TR4/Entity/tr4_troops.cpp b/TombEngine/Objects/TR4/Entity/tr4_troops.cpp index a0232b40a..490cb701a 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_troops.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_troops.cpp @@ -148,12 +148,11 @@ namespace TEN::Entities::TR4 float minDistance = FLT_MAX; - for (auto* activeCreature : ActiveCreatures) + for (auto& currentCreature : ActiveCreatures) { - if (activeCreature->ItemNumber != NO_ITEM && activeCreature->ItemNumber != itemNumber) + if (currentCreature->ItemNumber != NO_ITEM && currentCreature->ItemNumber != itemNumber) { - auto* currentItem = &g_Level.Items[activeCreature->ItemNumber]; - + auto* currentItem = &g_Level.Items[currentCreature->ItemNumber]; if (currentItem->ObjectNumber != ID_LARA) { if (currentItem->ObjectNumber != ID_TROOPS && diff --git a/TombEngine/Objects/TR4/Entity/tr4_von_croy.cpp b/TombEngine/Objects/TR4/Entity/tr4_von_croy.cpp index e83e26fb0..6cd82ee80 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_von_croy.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_von_croy.cpp @@ -230,10 +230,9 @@ namespace TEN::Entities::TR4 int distance; auto* targetCreature = ActiveCreatures[0]; - for (int i = 0; i < ActiveCreatures.size(); i++) + for (auto& currentCreature : ActiveCreatures) { - targetCreature = ActiveCreatures[i]; - + targetCreature = currentCreature; if (targetCreature->ItemNumber == NO_ITEM || targetCreature->ItemNumber == itemNumber || g_Level.Items[targetCreature->ItemNumber].ObjectNumber == ID_VON_CROY || diff --git a/TombEngine/Objects/TR4/Entity/tr4_wild_boar.cpp b/TombEngine/Objects/TR4/Entity/tr4_wild_boar.cpp index 263dd70e9..111921c79 100644 --- a/TombEngine/Objects/TR4/Entity/tr4_wild_boar.cpp +++ b/TombEngine/Objects/TR4/Entity/tr4_wild_boar.cpp @@ -84,10 +84,9 @@ namespace TEN::Entities::TR4 int minDistance = INT_MAX; - for (int i = 0; i < ActiveCreatures.size(); i++) + for (auto& currentCreature : ActiveCreatures) { - auto* currentItem = ActiveCreatures[i]; - + auto* currentItem = currentCreature; if (currentItem->ItemNumber == NO_ITEM || currentItem->ItemNumber == itemNumber) continue; diff --git a/TombEngine/Objects/TR4/tr4_objects.cpp b/TombEngine/Objects/TR4/tr4_objects.cpp index 4c002f5cd..c41985a23 100644 --- a/TombEngine/Objects/TR4/tr4_objects.cpp +++ b/TombEngine/Objects/TR4/tr4_objects.cpp @@ -196,9 +196,7 @@ namespace TEN::Entities obj = &Objects[ID_BADDY1]; if (obj->loaded) { - if (!Objects[ID_MESHSWAP_BADDY1].loaded) - TENLog("ID_MESHSWAP_BADDY1 missing; ID_BADDY1 will not use its sword and uzi meshes.", LogLevel::Warning); - + AssignObjectMeshSwap(*obj, ID_MESHSWAP_BADDY1, "ID_BADDY1", "ID_MESHSWAP_BADDY1"); obj->initialise = InitialiseBaddy; obj->control = BaddyControl; obj->collision = CreatureCollision; @@ -218,9 +216,7 @@ namespace TEN::Entities obj = &Objects[ID_BADDY2]; if (obj->loaded) { - if (!Objects[ID_MESHSWAP_BADDY2].loaded) - TENLog("ID_MESHSWAP_BADDY2 missing; ID_BADDY1 will not use its sword and uzi meshes.", LogLevel::Warning); - + AssignObjectMeshSwap(*obj, ID_MESHSWAP_BADDY2, "ID_BADDY2", "ID_MESHSWAP_BADDY2"); obj->initialise = InitialiseBaddy; obj->control = BaddyControl; obj->collision = CreatureCollision; @@ -539,8 +535,7 @@ namespace TEN::Entities obj = &Objects[ID_BABOON_INV]; if (obj->loaded) { - if (Objects[ID_BABOON_NORMAL].loaded) - obj->animIndex = Objects[ID_BABOON_NORMAL].animIndex; + AssignObjectAnimations(*obj, ID_BABOON_NORMAL, "ID_BABOON_INV", "ID_BABOON_NORMAL"); obj->initialise = InitialiseBaboon; obj->control = BaboonControl; obj->collision = CreatureCollision; @@ -555,8 +550,7 @@ namespace TEN::Entities obj = &Objects[ID_BABOON_SILENT]; if (obj->loaded) { - if (Objects[ID_BABOON_NORMAL].loaded) - obj->animIndex = Objects[ID_BABOON_NORMAL].animIndex; + AssignObjectAnimations(*obj, ID_BABOON_NORMAL, "ID_BABOON_SILENT", "ID_BABOON_NORMAL"); obj->initialise = InitialiseBaboon; obj->control = BaboonControl; obj->collision = CreatureCollision; diff --git a/TombEngine/Objects/TR5/tr5_objects.cpp b/TombEngine/Objects/TR5/tr5_objects.cpp index 22ad7b299..c655bc969 100644 --- a/TombEngine/Objects/TR5/tr5_objects.cpp +++ b/TombEngine/Objects/TR5/tr5_objects.cpp @@ -124,10 +124,7 @@ static void StartEntity(ObjectInfo *obj) obj = &Objects[ID_GUARD1]; if (obj->loaded) { - // Object required. - if (Objects[ID_SWAT].loaded) - obj->animIndex = Objects[ID_SWAT].animIndex; - + AssignObjectAnimations(*obj, ID_SWAT, "ID_GUARD1", "ID_SWAT"); obj->initialise = InitialiseGuard; obj->collision = CreatureCollision; obj->control = GuardControl; @@ -147,10 +144,8 @@ static void StartEntity(ObjectInfo *obj) obj = &Objects[ID_SWAT_PLUS]; if (obj->loaded) { - if (!Objects[ID_SWAT].loaded) - obj->animIndex = Objects[ID_GUARD1].animIndex; - else - obj->animIndex = Objects[ID_SWAT].animIndex; + if (!AssignObjectAnimations(*obj, ID_SWAT, "ID_SWAT_PLUS", "ID_SWAT")) + AssignObjectAnimations(*obj, ID_GUARD1, "ID_SWAT_PLUS", "ID_GUARD1"); obj->initialise = InitialiseGuard; obj->collision = CreatureCollision; @@ -170,10 +165,8 @@ static void StartEntity(ObjectInfo *obj) obj = &Objects[ID_MAFIA]; if (obj->loaded) { - if (!Objects[ID_SWAT].loaded) - obj->animIndex = Objects[ID_GUARD1].animIndex; - else - obj->animIndex = Objects[ID_SWAT].animIndex; + if (!AssignObjectAnimations(*obj, ID_SWAT, "ID_MAFIA", "ID_SWAT")) + AssignObjectAnimations(*obj, ID_GUARD1, "ID_MAFIA", "ID_GUARD1"); obj->initialise = InitialiseGuard; obj->collision = CreatureCollision; @@ -194,10 +187,8 @@ static void StartEntity(ObjectInfo *obj) obj = &Objects[ID_SCIENTIST]; if (obj->loaded) { - if (!Objects[ID_SWAT].loaded) - obj->animIndex = Objects[ID_GUARD1].animIndex; - else - obj->animIndex = Objects[ID_SWAT].animIndex; + if (!AssignObjectAnimations(*obj, ID_SWAT, "ID_SCIENTIST", "ID_SWAT")) + AssignObjectAnimations(*obj, ID_GUARD1, "ID_SCIENTIST", "ID_GUARD1"); obj->initialise = InitialiseGuard; obj->control = GuardControl; @@ -216,10 +207,8 @@ static void StartEntity(ObjectInfo *obj) obj = &Objects[ID_GUARD2]; if (obj->loaded) { - if (!Objects[ID_SWAT].loaded) - obj->animIndex = Objects[ID_GUARD1].animIndex; - else - obj->animIndex = Objects[ID_SWAT].animIndex; + if (!AssignObjectAnimations(*obj, ID_SWAT, "ID_GUARD2", "ID_SWAT")) + AssignObjectAnimations(*obj, ID_GUARD1, "ID_GUARD2", "ID_GUARD1"); obj->initialise = InitialiseGuard; obj->control = GuardControl; @@ -240,10 +229,8 @@ static void StartEntity(ObjectInfo *obj) obj = &Objects[ID_GUARD3]; if (obj->loaded) { - if (!Objects[ID_SWAT].loaded) - obj->animIndex = Objects[ID_GUARD1].animIndex; - else - obj->animIndex = Objects[ID_SWAT].animIndex; + if (!AssignObjectAnimations(*obj, ID_SWAT, "ID_GUARD3", "ID_SWAT")) + AssignObjectAnimations(*obj, ID_GUARD1, "ID_GUARD3", "ID_GUARD1"); obj->initialise = InitialiseGuard; obj->control = GuardControl; @@ -923,7 +910,7 @@ static void StartTrap(ObjectInfo *obj) obj->control = AnimatingControl; } - // TODO: Why commented? -- TokyoSU, 2022.12.24 + // TODO: Seem not decompiled. -- TokyoSU, 2023.01.12 obj = &Objects[ID_GEN_SLOT4]; if (obj->loaded) { diff --git a/TombEngine/Objects/Utils/object_helper.cpp b/TombEngine/Objects/Utils/object_helper.cpp index 76ebdbbdf..1048c7c2c 100644 --- a/TombEngine/Objects/Utils/object_helper.cpp +++ b/TombEngine/Objects/Utils/object_helper.cpp @@ -1,146 +1,191 @@ #include "framework.h" +#include "Objects/Utils/object_helper.h" #include "Game/collision/collide_item.h" #include "Game/Lara/lara_flare.h" #include "Game/pickup/pickup.h" -#include "Objects/Utils/object_helper.h" #include "Objects/Generic/Object/objects.h" #include "Objects/Generic/puzzles_keys.h" #include "Objects/TR5/Object/tr5_pushableblock.h" #include "Specific/level.h" -void InitSmashObject(ObjectInfo* obj, int objectNumber) +void AssignObjectMeshSwap(ObjectInfo& object, int requiredMeshSwap, const std::string& baseName, const std::string& requiredName) { - obj = &Objects[objectNumber]; - if (obj->loaded) + if (Objects[requiredMeshSwap].loaded) + object.meshSwapSlot = requiredMeshSwap; + else + TENLog("Slot " + requiredName + " not loaded. Meshswap issues with " + baseName + " may result in incorrect behaviour.", LogLevel::Warning); +} + +bool AssignObjectAnimations(ObjectInfo& object, int requiredObject, const std::string& baseName, const std::string& requiredName) +{ + // Check if the object has at least 1 animation with more than 1 frame. + const auto& anim = g_Level.Anims[object.animIndex]; + if ((anim.frameEnd - anim.frameBase) > 1) + return true; + + // Use slot if loaded. + if (Objects[requiredObject].loaded) { - obj->initialise = InitialiseSmashObject; - obj->collision = ObjectCollision; - obj->control = SmashObjectControl; - obj->SetupHitEffect(true); + // Check if the required object has at least 1 animation with more than 1 frame. + const auto& anim = g_Level.Anims[Objects[requiredObject].animIndex]; + if ((anim.frameEnd - anim.frameBase) > 1) + { + object.animIndex = Objects[requiredObject].animIndex; + object.frameBase = Objects[requiredObject].frameBase; + return true; + } + else + { + TENLog("Slot " + requiredName + " has no animation data. " + baseName + " will have no animations.", LogLevel::Warning); + } + } + else + { + TENLog("Slot " + requiredName + " not loaded. " + baseName + " will have no animations.", LogLevel::Warning); + } + + return false; +} + +void CheckIfSlotExists(int requiredObject, const std::string& baseName, const std::string& requiredName) +{ + if (!Objects[requiredObject].loaded) + TENLog("Slot " + requiredName + " not loaded. " + baseName + " may work incorrectly or crash.", LogLevel::Warning); +} + +void InitSmashObject(ObjectInfo* object, int objectNumber) +{ + object = &Objects[objectNumber]; + if (object->loaded) + { + object->initialise = InitialiseSmashObject; + object->collision = ObjectCollision; + object->control = SmashObjectControl; + object->SetupHitEffect(true); } } -void InitKeyHole(ObjectInfo* obj, int objectNumber) +void InitKeyHole(ObjectInfo* object, int objectNumber) { - obj = &Objects[objectNumber]; - if (obj->loaded) + object = &Objects[objectNumber]; + if (object->loaded) { - obj->collision = KeyHoleCollision; - obj->SetupHitEffect(true); + object->collision = KeyHoleCollision; + object->SetupHitEffect(true); } } -void InitPuzzleHole(ObjectInfo* obj, int objectNumber) +void InitPuzzleHole(ObjectInfo* object, int objectNumber) { - obj = &Objects[objectNumber]; - if (obj->loaded) + object = &Objects[objectNumber]; + if (object->loaded) { - obj->collision = PuzzleHoleCollision; - obj->control = AnimatingControl; - obj->isPuzzleHole = true; - obj->SetupHitEffect(true); + object->collision = PuzzleHoleCollision; + object->control = AnimatingControl; + object->isPuzzleHole = true; + object->SetupHitEffect(true); } } -void InitPuzzleDone(ObjectInfo* obj, int objectNumber) +void InitPuzzleDone(ObjectInfo* object, int objectNumber) { - obj = &Objects[objectNumber]; - if (obj->loaded) + object = &Objects[objectNumber]; + if (object->loaded) { - obj->collision = PuzzleDoneCollision; - obj->control = AnimatingControl; - obj->SetupHitEffect(true); + object->collision = PuzzleDoneCollision; + object->control = AnimatingControl; + object->SetupHitEffect(true); } } -void InitAnimating(ObjectInfo* obj, int objectNumber) +void InitAnimating(ObjectInfo* object, int objectNumber) { - obj = &Objects[objectNumber]; - if (obj->loaded) + object = &Objects[objectNumber]; + if (object->loaded) { - obj->initialise = InitialiseAnimating; - obj->control = AnimatingControl; - obj->collision = ObjectCollision; - obj->SetupHitEffect(true); + object->initialise = InitialiseAnimating; + object->control = AnimatingControl; + object->collision = ObjectCollision; + object->SetupHitEffect(true); } } -void InitPickup(ObjectInfo* obj, int objectNumber) +void InitPickup(ObjectInfo* object, int objectNumber) { - obj = &Objects[objectNumber]; - if (obj->loaded) + object = &Objects[objectNumber]; + if (object->loaded) { - obj->initialise = InitialisePickup; - obj->collision = PickupCollision; - obj->control = PickupControl; - obj->isPickup = true; - obj->SetupHitEffect(true); + object->initialise = InitialisePickup; + object->collision = PickupCollision; + object->control = PickupControl; + object->isPickup = true; + object->SetupHitEffect(true); } } -void InitPickup(ObjectInfo* obj, int objectNumber, std::function func) +void InitPickup(ObjectInfo* object, int objectNumber, std::function func) { - obj = &Objects[objectNumber]; - if (obj->loaded) + object = &Objects[objectNumber]; + if (object->loaded) { - obj->initialise = InitialisePickup; + object->initialise = InitialisePickup; - obj->collision = PickupCollision; - obj->control = (func != nullptr) ? func : PickupControl; - obj->isPickup = true; - obj->SetupHitEffect(true); + object->collision = PickupCollision; + object->control = (func != nullptr) ? func : PickupControl; + object->isPickup = true; + object->SetupHitEffect(true); } } -void InitFlare(ObjectInfo* obj, int objectNumber) +void InitFlare(ObjectInfo* object, int objectNumber) { - obj = &Objects[objectNumber]; - if (obj->loaded) + object = &Objects[objectNumber]; + if (object->loaded) { - obj->collision = PickupCollision; - obj->control = FlareControl; - obj->pivotLength = 256; - obj->HitPoints = 256; // Time. - obj->usingDrawAnimatingItem = false; - obj->isPickup = true; + object->collision = PickupCollision; + object->control = FlareControl; + object->pivotLength = 256; + object->HitPoints = 256; // Time. + object->usingDrawAnimatingItem = false; + object->isPickup = true; } } -void InitProjectile(ObjectInfo* obj, std::function func, int objectNumber, bool noLoad) +void InitProjectile(ObjectInfo* object, std::function func, int objectNumber, bool noLoad) { - obj = &Objects[objectNumber]; - if (obj->loaded || noLoad) + object = &Objects[objectNumber]; + if (object->loaded || noLoad) { - obj->initialise = nullptr; - obj->collision = nullptr; - obj->control = func; + object->initialise = nullptr; + object->collision = nullptr; + object->control = func; } } -void InitSearchObject(ObjectInfo* obj, int objectNumber) +void InitSearchObject(ObjectInfo* object, int objectNumber) { - obj = &Objects[objectNumber]; - if (obj->loaded) + object = &Objects[objectNumber]; + if (object->loaded) { - obj->initialise = InitialiseSearchObject; - obj->collision = SearchObjectCollision; - obj->control = SearchObjectControl; + object->initialise = InitialiseSearchObject; + object->collision = SearchObjectCollision; + object->control = SearchObjectControl; } } -void InitPushableObject(ObjectInfo* obj, int objectNumber) +void InitPushableObject(ObjectInfo* object, int objectNumber) { - obj = &Objects[objectNumber]; - if (obj->loaded) + object = &Objects[objectNumber]; + if (object->loaded) { - obj->initialise = InitialisePushableBlock; - obj->control = PushableBlockControl; - obj->collision = PushableBlockCollision; - obj->floor = PushableBlockFloor; - obj->ceiling = PushableBlockCeiling; - obj->floorBorder = PushableBlockFloorBorder; - obj->ceilingBorder = PushableBlockCeilingBorder; - obj->SetupHitEffect(true); + object->initialise = InitialisePushableBlock; + object->control = PushableBlockControl; + object->collision = PushableBlockCollision; + object->floor = PushableBlockFloor; + object->ceiling = PushableBlockCeiling; + object->floorBorder = PushableBlockFloorBorder; + object->ceilingBorder = PushableBlockCeilingBorder; + object->SetupHitEffect(true); } } diff --git a/TombEngine/Objects/Utils/object_helper.h b/TombEngine/Objects/Utils/object_helper.h index b8851e7da..6ab5be12a 100644 --- a/TombEngine/Objects/Utils/object_helper.h +++ b/TombEngine/Objects/Utils/object_helper.h @@ -4,14 +4,18 @@ #define InitFunction void(short itemNumber) #define ControlFunction void(short itemNumber) -void InitSmashObject(ObjectInfo* obj, int objectNumber); -void InitKeyHole(ObjectInfo* obj, int objectNumber); -void InitPuzzleHole(ObjectInfo* obj, int objectNumber); -void InitPuzzleDone(ObjectInfo* obj, int objectNumber); -void InitAnimating(ObjectInfo* obj, int objectNumber); -void InitPickup(ObjectInfo* obj, int objectNumber); -void InitPickup(ObjectInfo* obj, int objectNumber, std::function func); -void InitFlare(ObjectInfo* obj, int objectNumber); -void InitProjectile(ObjectInfo* obj, std::function func, int objectNumber, bool noLoad = false); -void InitSearchObject(ObjectInfo* obj, int objectNumber); -void InitPushableObject(ObjectInfo* obj, int objectNumber); +void AssignObjectMeshSwap(ObjectInfo& object, int requiredMeshSwap, const std::string& baseName, const std::string& requiredName); +bool AssignObjectAnimations(ObjectInfo& object, int requiredObject, const std::string& baseName = "NOT_SET", const std::string& requiredName = "NOT_SET"); +void CheckIfSlotExists(int requiredObj, const std::string& baseName, const std::string& requiredName); + +void InitSmashObject(ObjectInfo* object, int objectNumber); +void InitKeyHole(ObjectInfo* object, int objectNumber); +void InitPuzzleHole(ObjectInfo* object, int objectNumber); +void InitPuzzleDone(ObjectInfo* object, int objectNumber); +void InitAnimating(ObjectInfo* object, int objectNumber); +void InitPickup(ObjectInfo* object, int objectNumber); +void InitPickup(ObjectInfo* object, int objectNumber, std::function func); +void InitFlare(ObjectInfo* object, int objectNumber); +void InitProjectile(ObjectInfo* object, std::function func, int objectNumber, bool noLoad = false); +void InitSearchObject(ObjectInfo* object, int objectNumber); +void InitPushableObject(ObjectInfo* object, int objectNumber); diff --git a/TombEngine/Objects/game_object_ids.h b/TombEngine/Objects/game_object_ids.h index b6f7e4673..a10ab62d4 100644 --- a/TombEngine/Objects/game_object_ids.h +++ b/TombEngine/Objects/game_object_ids.h @@ -182,7 +182,7 @@ enum GAME_OBJECT_ID : short ID_SWORD_GUARDIAN_STATUE, ID_SHIVA, ID_SHIVA_STATUE, - ID_TRIBEBOSS, + ID_WILLARD, ID_CIVVY, ID_MUTANT2, ID_LIZARD, diff --git a/TombEngine/Renderer/Renderer11Compatibility.cpp b/TombEngine/Renderer/Renderer11Compatibility.cpp index bc8c75d8e..605441c5e 100644 --- a/TombEngine/Renderer/Renderer11Compatibility.cpp +++ b/TombEngine/Renderer/Renderer11Compatibility.cpp @@ -29,6 +29,26 @@ namespace TEN::Renderer m_meshes.clear(); + TENLog("Allocated renderer object memory.", LogLevel::Info); + + m_animatedTextures.resize(g_Level.AnimatedTextures.size()); + for (int i = 0; i < g_Level.AnimatedTextures.size(); i++) + { + TEXTURE* texture = &g_Level.AnimatedTextures[i]; + Texture2D normal; + if (texture->normalMapData.size() < 1) { + normal = CreateDefaultNormalTexture(); + } + else { + normal = Texture2D(m_device.Get(), texture->normalMapData.data(), texture->normalMapData.size()); + } + TexturePair tex = std::make_tuple(Texture2D(m_device.Get(), texture->colorMapData.data(), texture->colorMapData.size()), normal); + m_animatedTextures[i] = tex; + } + + if (m_animatedTextures.size() > 0) + TENLog("Generated " + std::to_string(m_animatedTextures.size()) + " animated textures.", LogLevel::Info); + std::transform(g_Level.AnimatedTexturesSequences.begin(), g_Level.AnimatedTexturesSequences.end(), std::back_inserter(m_animatedTextureSets), [](ANIMATED_TEXTURES_SEQUENCE& sequence) { RendererAnimatedTextureSet set{}; set.NumTextures = sequence.numFrames; @@ -48,6 +68,9 @@ namespace TEN::Renderer return set; }); + if (m_animatedTextureSets.size() > 0) + TENLog("Generated " + std::to_string(m_animatedTextureSets.size()) + " animated texture sets.", LogLevel::Info); + m_roomTextures.resize(g_Level.RoomTextures.size()); for (int i = 0; i < g_Level.RoomTextures.size(); i++) { @@ -70,20 +93,8 @@ namespace TEN::Renderer #endif } - m_animatedTextures.resize(g_Level.AnimatedTextures.size()); - for (int i = 0; i < g_Level.AnimatedTextures.size(); i++) - { - TEXTURE *texture = &g_Level.AnimatedTextures[i]; - Texture2D normal; - if (texture->normalMapData.size() < 1) { - normal = CreateDefaultNormalTexture(); - } - else { - normal = Texture2D(m_device.Get(), texture->normalMapData.data(), texture->normalMapData.size()); - } - TexturePair tex = std::make_tuple(Texture2D(m_device.Get(), texture->colorMapData.data(), texture->colorMapData.size()), normal); - m_animatedTextures[i] = tex; - } + if (m_roomTextures.size() > 0) + TENLog("Generated " + std::to_string(m_roomTextures.size()) + " room texture atlases.", LogLevel::Info); m_moveablesTextures.resize(g_Level.MoveablesTextures.size()); for (int i = 0; i < g_Level.MoveablesTextures.size(); i++) @@ -107,6 +118,9 @@ namespace TEN::Renderer #endif } + if (m_moveablesTextures.size() > 0) + TENLog("Generated " + std::to_string(m_moveablesTextures.size()) + " moveable texture atlases.", LogLevel::Info); + m_staticsTextures.resize(g_Level.StaticsTextures.size()); for (int i = 0; i < g_Level.StaticsTextures.size(); i++) { @@ -129,6 +143,9 @@ namespace TEN::Renderer #endif } + if (m_staticsTextures.size() > 0) + TENLog("Generated " + std::to_string(m_staticsTextures.size()) + " static mesh texture atlases.", LogLevel::Info); + m_spritesTextures.resize(g_Level.SpritesTextures.size()); for (int i = 0; i < g_Level.SpritesTextures.size(); i++) { @@ -136,8 +153,13 @@ namespace TEN::Renderer m_spritesTextures[i] = Texture2D(m_device.Get(), texture->colorMapData.data(), texture->colorMapData.size()); } + if (m_spritesTextures.size() > 0) + TENLog("Generated " + std::to_string(m_spritesTextures.size()) + " sprite atlases.", LogLevel::Info); + m_skyTexture = Texture2D(m_device.Get(), g_Level.SkyTexture.colorMapData.data(), g_Level.SkyTexture.colorMapData.size()); + TENLog("Loaded sky texture.", LogLevel::Info); + int totalVertices = 0; int totalIndices = 0; for (auto& room : g_Level.Rooms) @@ -153,9 +175,13 @@ namespace TEN::Renderer roomsVertices.resize(totalVertices); roomsIndices.resize(totalIndices); + TENLog("Loaded total " + std::to_string(totalVertices) + " room vertices.", LogLevel::Info); + int lastVertex = 0; int lastIndex = 0; + TENLog("Preparing room data...", LogLevel::Info); + for (int i = 0; i < g_Level.Rooms.size(); i++) { ROOM_INFO& room = g_Level.Rooms[i]; @@ -404,6 +430,8 @@ namespace TEN::Renderer } ); + TENLog("Preparing object data...", LogLevel::Info); + bool skinPresent = false; bool hairsPresent = false; @@ -707,6 +735,8 @@ namespace TEN::Renderer m_moveablesVertexBuffer = VertexBuffer(m_device.Get(), moveablesVertices.size(), moveablesVertices.data()); m_moveablesIndexBuffer = IndexBuffer(m_device.Get(), moveablesIndices.size(), moveablesIndices.data()); + TENLog("Preparing static mesh data...", LogLevel::Info); + totalVertices = 0; totalIndices = 0; for (int i = 0; i < StaticObjectsIds.size(); i++) @@ -752,6 +782,8 @@ namespace TEN::Renderer m_staticsVertexBuffer = VertexBuffer(m_device.Get(), 1); m_staticsIndexBuffer = IndexBuffer(m_device.Get(), 1); } + + TENLog("Preparing sprite data...", LogLevel::Info); // Step 5: prepare sprites m_sprites.resize(g_Level.Sprites.size()); diff --git a/TombEngine/Renderer/Renderer11DrawEffect.cpp b/TombEngine/Renderer/Renderer11DrawEffect.cpp index 7fab036d9..41156df6d 100644 --- a/TombEngine/Renderer/Renderer11DrawEffect.cpp +++ b/TombEngine/Renderer/Renderer11DrawEffect.cpp @@ -1368,7 +1368,7 @@ namespace TEN::Renderer if (!smoke.active) continue; - // TODO: Switch back to alpha blend mode once rendering for it is refactored. -- Sezz 21.01.2023 + // TODO: Switch back to alpha blend mode once rendering for it is refactored. -- Sezz 2023.01.14 AddSpriteBillboard( &m_sprites[Objects[ID_SMOKE_SPRITES].meshIndex + smoke.sprite], smoke.position, diff --git a/TombEngine/Scripting/Internal/TEN/Objects/ObjectIDs.h b/TombEngine/Scripting/Internal/TEN/Objects/ObjectIDs.h index 8459aeef8..b53127dd6 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/ObjectIDs.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/ObjectIDs.h @@ -1356,7 +1356,7 @@ static const std::unordered_map kObjIDs { { "SWORD_GUARDIAN_STATUE", ID_SWORD_GUARDIAN_STATUE }, { "SHIVA", ID_SHIVA }, { "SHIVA_STATUE", ID_SHIVA_STATUE }, - { "TRIBEBOSS", ID_TRIBEBOSS }, + { "WILLARD", ID_WILLARD }, { "CIVVY", ID_CIVVY }, { "MUTANT2", ID_MUTANT2 }, { "LIZARD", ID_LIZARD }, diff --git a/TombEngine/Specific/setup.h b/TombEngine/Specific/setup.h index 750d8de50..72ea5cf1f 100644 --- a/TombEngine/Specific/setup.h +++ b/TombEngine/Specific/setup.h @@ -97,7 +97,7 @@ struct ObjectInfo } /// - /// Use this to set up a hit rffect for the slot based on its value. + /// Use this to set up a hit effect for the slot based on its value. /// /// Use this if the object is alive but not intelligent to set up blood effects. void SetupHitEffect(bool isSolid = false, bool isAlive = false) diff --git a/TombEngine/TombEngine.vcxproj b/TombEngine/TombEngine.vcxproj index 62b89f850..77aa1d26f 100644 --- a/TombEngine/TombEngine.vcxproj +++ b/TombEngine/TombEngine.vcxproj @@ -167,6 +167,9 @@ CALL gen.bat + + + @@ -639,6 +642,9 @@ CALL gen.bat + + +