Merge branch 'MontyTRC89:develop' into develop

This commit is contained in:
davidmarr 2024-08-13 22:46:38 +02:00 committed by GitHub
commit c3b4ecf9b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
80 changed files with 2295 additions and 1786 deletions

View file

@ -20,7 +20,8 @@ This is the credit list of **all** the people who contributed to TombEngine in a
- TokyoSU (entity and vehicle decompilation) - TokyoSU (entity and vehicle decompilation)
- Tomo (general coding, bug fixing) - Tomo (general coding, bug fixing)
- Troye (general coding, refactoring) - Troye (general coding, refactoring)
- WolfCheese (general coding) - Nickelony (general coding)
- JesseG, aka WolfCheese (general coding)
## Testers ## Testers
- Adngel - Adngel

View file

@ -25,6 +25,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed sentry gun joint rotation. * Fixed sentry gun joint rotation.
* Fixed teeth spikes not triggering the player impale animation. * Fixed teeth spikes not triggering the player impale animation.
* Fixed TR4 mine crash with OCB 1 when triggered. * Fixed TR4 mine crash with OCB 1 when triggered.
* Fixed cases where Atlantean mutant's bombs cause the game to crash.
### Features/Amendments ### Features/Amendments
* Changed Rome Hammer to not hurt player whilst deactivated. * Changed Rome Hammer to not hurt player whilst deactivated.
@ -32,14 +33,25 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Enhaced Rolling Spindle detection to avoid them going down through pits. * Enhaced Rolling Spindle detection to avoid them going down through pits.
* Enhaced Sentry Guns, with a new ItemFlags[3], to contain the ID of the inventory item that deactivates the sentry guns ( by default PUZZLE_ITEM5 ) * Enhaced Sentry Guns, with a new ItemFlags[3], to contain the ID of the inventory item that deactivates the sentry guns ( by default PUZZLE_ITEM5 )
* Enhaced Dart Emitter, with a new ItemFlags[0], to contain the number of frames between shots ( by default 32 in dart emitter, and 24 in homing dar emitter ). * Enhaced Dart Emitter, with a new ItemFlags[0], to contain the number of frames between shots ( by default 32 in dart emitter, and 24 in homing dar emitter ).
* Add new sound conditions: quicksand and Underwater. * Enhanced raptor behaviour and handling.
- OCB 0: Classic behaviour
- OCB 1: Can jump up/down up to 4 steps and jump across gaps up to 2 blocks wide.
- You must use the download version found on the TombEngine website.
* Added TR3 seal mutant.
- OCB 0: Normal enemy behaviour. (TR3 RX-Tech mines level)
- OCB 1: Trap like behaviour. (TR3 Antarctica level)
* Add new sound conditions: Quicksand and Underwater.
- Quicksand - sound effect plays when a moveable is in quicksand. - Quicksand - sound effect plays when a moveable is in quicksand.
- Underwater - sound plays when the camera is submerged. - Underwater - sound plays when the camera is submerged.
* Changed Water sound condition to ShallowWater. * Changed Water sound condition to ShallowWater.
* Added option to enable or disable menu option looping.
* Menu scrolling using held inputs will stop at the last option until a new input is made.
* Added the ability to display "Lara's Home" entry in the main menu.
### Lua API changes ### Lua API changes
* Added Inventory.GetUsedItem(), Inventory.SetUsedItem() and Inventory.ClearUsedItem() functions. * Added Inventory.GetUsedItem(), Inventory.SetUsedItem() and Inventory.ClearUsedItem() functions.
* Added Input.KeyClearAll() * Added Input.KeyClearAll()
* Added Flow.EnableHomeLevel()
* Removed anims.monkeyAutoJump. It is now a player menu configuration. * Removed anims.monkeyAutoJump. It is now a player menu configuration.
* Fixed Volume:GetActive() method * Fixed Volume:GetActive() method

View file

@ -129,6 +129,10 @@ scripts too.</p>
<td class="summary">Enable or disable level selection in title flyby.</td> <td class="summary">Enable or disable level selection in title flyby.</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#EnableHomeLevel">EnableHomeLevel(enabled)</a></td>
<td class="summary">Enable or disable Home Level entry in the main menu.</td>
</tr>
<tr>
<td class="name" ><a href="#EnableLoadSave">EnableLoadSave(enabled)</a></td> <td class="name" ><a href="#EnableLoadSave">EnableLoadSave(enabled)</a></td>
<td class="summary">Enable or disable saving and loading of savegames.</td> <td class="summary">Enable or disable saving and loading of savegames.</td>
</tr> </tr>
@ -361,6 +365,28 @@ Must be true or false
</dd>
<dt>
<a name = "EnableHomeLevel"></a>
<strong>EnableHomeLevel(enabled)</strong>
</dt>
<dd>
Enable or disable Home Level entry in the main menu.
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">enabled</span>
<span class="types"><span class="type">bool</span></span>
true or false.
</li>
</ul>
</dd> </dd>
<dt> <dt>
<a name = "EnableLoadSave"></a> <a name = "EnableLoadSave"></a>

View file

@ -357,7 +357,7 @@ WASP_MUTANT
SKATEBOARD SKATEBOARD
SKATEBOARD_KID SKATEBOARD_KID
WINSTON WINSTON
ARMY_WINSTON SEAL_MUTANT
SPRINGBOARD SPRINGBOARD
ROLLING_SPINDLE ROLLING_SPINDLE
DISK_SHOOTER DISK_SHOOTER
@ -943,6 +943,7 @@ MESHSWAP_ROMAN_GOD1
MESHSWAP_ROMAN_GOD2 MESHSWAP_ROMAN_GOD2
MESHSWAP_MONKEY_MEDIPACK MESHSWAP_MONKEY_MEDIPACK
MESHSWAP_MONKEY_KEY MESHSWAP_MONKEY_KEY
MESHSWAP_WINSTON_ARMY_OUTFIT
ANIMATING1 ANIMATING1
ANIMATING2 ANIMATING2
ANIMATING3 ANIMATING3

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 TombEngine Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -58,7 +58,7 @@ local strings =
-- Level name strings -- Level name strings
lara_home = { "Lara's Home" }, home_level = { "Home Level" },
test_level = { "Test Level" }, test_level = { "Test Level" },
title = { "Title" }, title = { "Title" },
} }

View file

@ -80,8 +80,11 @@ local strings =
low = { "Low" }, low = { "Low" },
medium = { "Medium" }, medium = { "Medium" },
menu_actions = { "Menu Actions" }, menu_actions = { "Menu Actions" },
menu_option_looping = { "Menu Option Looping" },
menu_option_looping_all_menus = { "All Menus" },
menu_option_looping_disabled = { "Disabled" },
menu_option_looping_save_load_only = { "Save/Load Only" },
mouse_sensitivity = { "Mouse Sensitivity" }, mouse_sensitivity = { "Mouse Sensitivity" },
mouse_smoothing = { "Mouse Smoothing" },
music_volume = { "Music Volume" }, music_volume = { "Music Volume" },
new_game = { "New Game" }, new_game = { "New Game" },
none = { "None" }, none = { "None" },

View file

@ -166,7 +166,7 @@ void LookCamera(ItemInfo& item, const CollisionInfo& coll)
bool isInSwamp = TestEnvironment(ENV_FLAG_SWAMP, item.RoomNumber); bool isInSwamp = TestEnvironment(ENV_FLAG_SWAMP, item.RoomNumber);
auto basePos = Vector3i( auto basePos = Vector3i(
item.Pose.Position.x, item.Pose.Position.x,
isInSwamp ? g_Level.Rooms[item.RoomNumber].maxceiling : item.Pose.Position.y, isInSwamp ? g_Level.Rooms[item.RoomNumber].TopHeight : item.Pose.Position.y,
item.Pose.Position.z); item.Pose.Position.z);
// Define landmarks. // Define landmarks.
@ -343,7 +343,7 @@ void MoveCamera(GameVector* ideal, int speed)
int y = Camera.pos.y; int y = Camera.pos.y;
if (TestEnvironment(ENV_FLAG_SWAMP, Camera.pos.RoomNumber)) if (TestEnvironment(ENV_FLAG_SWAMP, Camera.pos.RoomNumber))
y = g_Level.Rooms[Camera.pos.RoomNumber].y - CLICK(1); y = g_Level.Rooms[Camera.pos.RoomNumber].Position.y - CLICK(1);
auto pointColl = GetPointCollision(Vector3i(Camera.pos.x, y, Camera.pos.z), Camera.pos.RoomNumber); auto pointColl = GetPointCollision(Vector3i(Camera.pos.x, y, Camera.pos.z), Camera.pos.RoomNumber);
if (y < pointColl.GetCeilingHeight() || if (y < pointColl.GetCeilingHeight() ||
@ -534,7 +534,7 @@ void ChaseCamera(ItemInfo* item)
auto pointColl = GetPointCollision(Vector3i(Camera.target.x, Camera.target.y + CLICK(1), Camera.target.z), Camera.target.RoomNumber); auto pointColl = GetPointCollision(Vector3i(Camera.target.x, Camera.target.y + CLICK(1), Camera.target.z), Camera.target.RoomNumber);
if (TestEnvironment(ENV_FLAG_SWAMP, pointColl.GetRoomNumber())) if (TestEnvironment(ENV_FLAG_SWAMP, pointColl.GetRoomNumber()))
Camera.target.y = g_Level.Rooms[pointColl.GetRoomNumber()].maxceiling - CLICK(1); Camera.target.y = g_Level.Rooms[pointColl.GetRoomNumber()].TopHeight - CLICK(1);
int y = Camera.target.y; int y = Camera.target.y;
pointColl = GetPointCollision(Vector3i(Camera.target.x, y, Camera.target.z), Camera.target.RoomNumber); pointColl = GetPointCollision(Vector3i(Camera.target.x, y, Camera.target.z), Camera.target.RoomNumber);
@ -652,7 +652,7 @@ void CombatCamera(ItemInfo* item)
auto pointColl = GetPointCollision(Vector3i(Camera.target.x, Camera.target.y + CLICK(1), Camera.target.z), Camera.target.RoomNumber); auto pointColl = GetPointCollision(Vector3i(Camera.target.x, Camera.target.y + CLICK(1), Camera.target.z), Camera.target.RoomNumber);
if (TestEnvironment(ENV_FLAG_SWAMP, pointColl.GetRoomNumber())) if (TestEnvironment(ENV_FLAG_SWAMP, pointColl.GetRoomNumber()))
Camera.target.y = g_Level.Rooms[pointColl.GetRoomNumber()].y - CLICK(1); Camera.target.y = g_Level.Rooms[pointColl.GetRoomNumber()].Position.y - CLICK(1);
pointColl = GetPointCollision(Camera.target.ToVector3i(), Camera.target.RoomNumber); pointColl = GetPointCollision(Camera.target.ToVector3i(), Camera.target.RoomNumber);
Camera.target.RoomNumber = pointColl.GetRoomNumber(); Camera.target.RoomNumber = pointColl.GetRoomNumber();
@ -1393,7 +1393,7 @@ bool CheckItemCollideCamera(ItemInfo* item)
static std::vector<int> FillCollideableItemList() static std::vector<int> FillCollideableItemList()
{ {
auto itemList = std::vector<int>{}; auto itemList = std::vector<int>{};
auto& roomList = g_Level.Rooms[Camera.pos.RoomNumber].neighbors; auto& roomList = g_Level.Rooms[Camera.pos.RoomNumber].NeighborRoomNumbers;
for (short i = 0; i < g_Level.NumItems; i++) for (short i = 0; i < g_Level.NumItems; i++)
{ {
@ -1443,7 +1443,7 @@ bool CheckStaticCollideCamera(MESH_INFO* mesh)
std::vector<MESH_INFO*> FillCollideableStaticsList() std::vector<MESH_INFO*> FillCollideableStaticsList()
{ {
std::vector<MESH_INFO*> staticList; std::vector<MESH_INFO*> staticList;
auto& roomList = g_Level.Rooms[Camera.pos.RoomNumber].neighbors; auto& roomList = g_Level.Rooms[Camera.pos.RoomNumber].NeighborRoomNumbers;
for (int i : roomList) for (int i : roomList)
{ {

View file

@ -57,7 +57,7 @@ namespace TEN::Collision::Point
int roomNumber = roomNumberBelow.value_or(bottomSector->RoomNumber); int roomNumber = roomNumberBelow.value_or(bottomSector->RoomNumber);
auto& room = g_Level.Rooms[roomNumber]; auto& room = g_Level.Rooms[roomNumber];
bottomSector = Room::GetSector(&room, _position.x - room.x, _position.z - room.z); bottomSector = Room::GetSector(&room, _position.x - room.Position.x, _position.z - room.Position.z);
roomNumberBelow = bottomSector->GetNextRoomNumber(_position, true); roomNumberBelow = bottomSector->GetNextRoomNumber(_position, true);
} }
_bottomSector = bottomSector; _bottomSector = bottomSector;
@ -78,7 +78,7 @@ namespace TEN::Collision::Point
int roomNumber = roomNumberAbove.value_or(topSector->RoomNumber); int roomNumber = roomNumberAbove.value_or(topSector->RoomNumber);
auto& room = g_Level.Rooms[roomNumber]; auto& room = g_Level.Rooms[roomNumber];
topSector = Room::GetSector(&room, _position.x - room.x, _position.z - room.z); topSector = Room::GetSector(&room, _position.x - room.Position.x, _position.z - room.Position.z);
roomNumberAbove = topSector->GetNextRoomNumber(_position, false); roomNumberAbove = topSector->GetNextRoomNumber(_position, false);
} }
_topSector = topSector; _topSector = topSector;

View file

@ -123,7 +123,7 @@ CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible,
// Run through neighboring rooms. // Run through neighboring rooms.
const auto& room = g_Level.Rooms[collidingItem.RoomNumber]; const auto& room = g_Level.Rooms[collidingItem.RoomNumber];
for (int roomNumber : room.neighbors) for (int roomNumber : room.NeighborRoomNumbers)
{ {
auto& neighborRoom = g_Level.Rooms[roomNumber]; auto& neighborRoom = g_Level.Rooms[roomNumber];
if (!neighborRoom.Active()) if (!neighborRoom.Active())
@ -307,7 +307,7 @@ void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll)
// DrawDebugSphere(origin, 16, Vector4::One, RendererDebugPage::CollisionStats); // DrawDebugSphere(origin, 16, Vector4::One, RendererDebugPage::CollisionStats);
for (auto i : g_Level.Rooms[item->RoomNumber].neighbors) for (auto i : g_Level.Rooms[item->RoomNumber].NeighborRoomNumbers)
{ {
if (!g_Level.Rooms[i].Active()) if (!g_Level.Rooms[i].Active())
continue; continue;
@ -940,7 +940,7 @@ void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll)
{ {
coll->HitTallObject = false; coll->HitTallObject = false;
for (auto i : g_Level.Rooms[item->RoomNumber].neighbors) for (auto i : g_Level.Rooms[item->RoomNumber].NeighborRoomNumbers)
{ {
if (!g_Level.Rooms[i].Active()) if (!g_Level.Rooms[i].Active())
continue; continue;
@ -1809,7 +1809,7 @@ void DoObjectCollision(ItemInfo* item, CollisionInfo* coll)
return; return;
const auto& room = g_Level.Rooms[item->RoomNumber]; const auto& room = g_Level.Rooms[item->RoomNumber];
for (int neighborRoomNumber : room.neighbors) for (int neighborRoomNumber : room.NeighborRoomNumbers)
{ {
auto& neighborRoom = g_Level.Rooms[neighborRoomNumber]; auto& neighborRoom = g_Level.Rooms[neighborRoomNumber];
if (!neighborRoom.Active()) if (!neighborRoom.Active())

View file

@ -1084,7 +1084,7 @@ int GetDistanceToFloor(int itemNumber, bool precise)
int GetWaterSurface(int x, int y, int z, short roomNumber) int GetWaterSurface(int x, int y, int z, short roomNumber)
{ {
auto* room = &g_Level.Rooms[roomNumber]; auto* room = &g_Level.Rooms[roomNumber];
auto* sector = GetSector(room, x - room->x, z - room->z); auto* sector = GetSector(room, x - room->Position.x, z - room->Position.z);
if (TestEnvironment(ENV_FLAG_WATER, room)) if (TestEnvironment(ENV_FLAG_WATER, room))
{ {
@ -1094,7 +1094,7 @@ int GetWaterSurface(int x, int y, int z, short roomNumber)
if (!TestEnvironment(ENV_FLAG_WATER, room)) if (!TestEnvironment(ENV_FLAG_WATER, room))
return (sector->GetSurfaceHeight(x, z, false)); return (sector->GetSurfaceHeight(x, z, false));
sector = GetSector(room, x - room->x, z - room->z); sector = GetSector(room, x - room->Position.x, z - room->Position.z);
} }
return NO_HEIGHT; return NO_HEIGHT;
@ -1107,7 +1107,7 @@ int GetWaterSurface(int x, int y, int z, short roomNumber)
if (TestEnvironment(ENV_FLAG_WATER, room)) if (TestEnvironment(ENV_FLAG_WATER, room))
return (sector->GetSurfaceHeight(x, z, true)); return (sector->GetSurfaceHeight(x, z, true));
sector = GetSector(room, x - room->x, z - room->z); sector = GetSector(room, x - room->Position.x, z - room->Position.z);
} }
} }
@ -1127,8 +1127,8 @@ int GetWaterDepth(int x, int y, int z, short roomNumber)
int adjoiningRoomNumber = NO_VALUE; int adjoiningRoomNumber = NO_VALUE;
do do
{ {
int xFloor = (x - room->x) / BLOCK(1); int xFloor = (x - room->Position.x) / BLOCK(1);
int zFloor = (z - room->z) / BLOCK(1); int zFloor = (z - room->Position.z) / BLOCK(1);
if (zFloor <= 0) if (zFloor <= 0)
{ {
@ -1137,33 +1137,33 @@ int GetWaterDepth(int x, int y, int z, short roomNumber)
{ {
xFloor = 1; xFloor = 1;
} }
else if (xFloor > (room->xSize - 2)) else if (xFloor > (room->XSize - 2))
{ {
xFloor = room->xSize - 2; xFloor = room->XSize - 2;
} }
} }
else if (zFloor >= (room->zSize - 1)) else if (zFloor >= (room->ZSize - 1))
{ {
zFloor = room->zSize - 1; zFloor = room->ZSize - 1;
if (xFloor < 1) if (xFloor < 1)
{ {
xFloor = 1; xFloor = 1;
} }
else if (xFloor > (room->xSize - 2)) else if (xFloor > (room->XSize - 2))
{ {
xFloor = room->xSize - 2; xFloor = room->XSize - 2;
} }
} }
else if (xFloor < 0) else if (xFloor < 0)
{ {
xFloor = 0; xFloor = 0;
} }
else if (xFloor >= room->xSize) else if (xFloor >= room->XSize)
{ {
xFloor = room->xSize - 1; xFloor = room->XSize - 1;
} }
sector = &room->floor[zFloor + (xFloor * room->zSize)]; sector = &room->Sectors[zFloor + (xFloor * room->ZSize)];
adjoiningRoomNumber = sector->SidePortalRoomNumber; adjoiningRoomNumber = sector->SidePortalRoomNumber;
if (adjoiningRoomNumber != NO_VALUE) if (adjoiningRoomNumber != NO_VALUE)
{ {
@ -1188,7 +1188,7 @@ int GetWaterDepth(int x, int y, int z, short roomNumber)
return (floorHeight - waterHeight); return (floorHeight - waterHeight);
} }
sector = GetSector(room, x - room->x, z - room->z); sector = GetSector(room, x - room->Position.x, z - room->Position.z);
} }
return DEEP_WATER; return DEEP_WATER;
@ -1207,7 +1207,7 @@ int GetWaterDepth(int x, int y, int z, short roomNumber)
return (GetFloorHeight(sector, x, y, z) - waterHeight); return (GetFloorHeight(sector, x, y, z) - waterHeight);
} }
sector = GetSector(room, x - room->x, z - room->z); sector = GetSector(room, x - room->Position.x, z - room->Position.z);
} }
return NO_HEIGHT; return NO_HEIGHT;
@ -1227,8 +1227,8 @@ int GetWaterHeight(int x, int y, int z, short roomNumber)
int adjoiningRoomNumber = NO_VALUE; int adjoiningRoomNumber = NO_VALUE;
do do
{ {
int xBlock = (x - room->x) / BLOCK(1); int xBlock = (x - room->Position.x) / BLOCK(1);
int zBlock = (z - room->z) / BLOCK(1); int zBlock = (z - room->Position.z) / BLOCK(1);
if (zBlock <= 0) if (zBlock <= 0)
{ {
@ -1237,33 +1237,33 @@ int GetWaterHeight(int x, int y, int z, short roomNumber)
{ {
xBlock = 1; xBlock = 1;
} }
else if (xBlock > (room->xSize - 2)) else if (xBlock > (room->XSize - 2))
{ {
xBlock = room->xSize - 2; xBlock = room->XSize - 2;
} }
} }
else if (zBlock >= (room->zSize - 1)) else if (zBlock >= (room->ZSize - 1))
{ {
zBlock = room->zSize - 1; zBlock = room->ZSize - 1;
if (xBlock < 1) if (xBlock < 1)
{ {
xBlock = 1; xBlock = 1;
} }
else if (xBlock > (room->xSize - 2)) else if (xBlock > (room->XSize - 2))
{ {
xBlock = room->xSize - 2; xBlock = room->XSize - 2;
} }
} }
else if (xBlock < 0) else if (xBlock < 0)
{ {
xBlock = 0; xBlock = 0;
} }
else if (xBlock >= room->xSize) else if (xBlock >= room->XSize)
{ {
xBlock = room->xSize - 1; xBlock = room->XSize - 1;
} }
sector = &room->floor[zBlock + (xBlock * room->zSize)]; sector = &room->Sectors[zBlock + (xBlock * room->ZSize)];
adjoiningRoomNumber = sector->SidePortalRoomNumber; adjoiningRoomNumber = sector->SidePortalRoomNumber;
if (adjoiningRoomNumber != NO_VALUE) if (adjoiningRoomNumber != NO_VALUE)
@ -1290,7 +1290,7 @@ int GetWaterHeight(int x, int y, int z, short roomNumber)
break; break;
} }
sector = GetSector(room, x - room->x, z - room->z); sector = GetSector(room, x - room->Position.x, z - room->Position.z);
} }
return sector->GetSurfaceHeight(Vector3i(x, y, z), false); return sector->GetSurfaceHeight(Vector3i(x, y, z), false);
@ -1307,7 +1307,7 @@ int GetWaterHeight(int x, int y, int z, short roomNumber)
break; break;
} }
sector = GetSector(room2, x - room2->x, z - room2->z); sector = GetSector(room2, x - room2->Position.x, z - room2->Position.z);
} }
return sector->GetSurfaceHeight(Vector3i(x, y, z), true); return sector->GetSurfaceHeight(Vector3i(x, y, z), true);

View file

@ -368,17 +368,17 @@ namespace TEN::Collision::Floordata
const auto& room = g_Level.Rooms[roomNumber]; const auto& room = g_Level.Rooms[roomNumber];
// Calculate room grid coord. // Calculate room grid coord.
auto roomGridCoord = Vector2i((x - room.x) / BLOCK(1), (z - room.z) / BLOCK(1)); auto roomGridCoord = Vector2i((x - room.Position.x) / BLOCK(1), (z - room.Position.z) / BLOCK(1));
if (x < room.x) if (x < room.Position.x)
roomGridCoord.x -= 1; roomGridCoord.x -= 1;
if (z < room.z) if (z < room.Position.z)
roomGridCoord.y -= 1; roomGridCoord.y -= 1;
// Clamp room grid coord to room bounds (if applicable). // Clamp room grid coord to room bounds (if applicable).
if (clampToBounds) if (clampToBounds)
{ {
roomGridCoord.x = std::clamp(roomGridCoord.x, 0, room.xSize - 1); roomGridCoord.x = std::clamp(roomGridCoord.x, 0, room.XSize - 1);
roomGridCoord.y = std::clamp(roomGridCoord.y, 0, room.zSize - 1); roomGridCoord.y = std::clamp(roomGridCoord.y, 0, room.ZSize - 1);
} }
return roomGridCoord; return roomGridCoord;
@ -397,8 +397,8 @@ namespace TEN::Collision::Floordata
const auto& room = g_Level.Rooms[roomNumber]; const auto& room = g_Level.Rooms[roomNumber];
// Search area out of range; return empty vector. // Search area out of range; return empty vector.
if (xMax <= 0 || xMin >= (room.xSize - 1) || if (xMax <= 0 || xMin >= (room.XSize - 1) ||
xMax <= 0 || xMin >= (room.xSize - 1)) xMax <= 0 || xMin >= (room.XSize - 1))
{ {
return {}; return {};
} }
@ -408,13 +408,13 @@ namespace TEN::Collision::Floordata
for (int x = xMin; x <= xMax; x++) for (int x = xMin; x <= xMax; x++)
{ {
// Test if out of room X range. // Test if out of room X range.
if (x <= 0 || x >= (room.xSize - 1)) if (x <= 0 || x >= (room.XSize - 1))
continue; continue;
for (int z = zMin; z <= zMax; z++) for (int z = zMin; z <= zMax; z++)
{ {
// Test if out of room Z range. // Test if out of room Z range.
if (z <= 0 || z >= (room.zSize - 1)) if (z <= 0 || z >= (room.ZSize - 1))
continue; continue;
roomGridCoords.push_back(Vector2i(x, z)); roomGridCoords.push_back(Vector2i(x, z));
@ -430,7 +430,7 @@ namespace TEN::Collision::Floordata
// Run through neighbor rooms. // Run through neighbor rooms.
auto& room = g_Level.Rooms[roomNumber]; auto& room = g_Level.Rooms[roomNumber];
for (int neighborRoomNumber : room.neighbors) for (int neighborRoomNumber : room.NeighborRoomNumbers)
{ {
// Collect neighbor sectors. // Collect neighbor sectors.
auto roomGridCoords = GetNeighborRoomGridCoords(pos, neighborRoomNumber, searchDepth); auto roomGridCoords = GetNeighborRoomGridCoords(pos, neighborRoomNumber, searchDepth);
@ -446,8 +446,8 @@ namespace TEN::Collision::Floordata
{ {
auto& room = g_Level.Rooms[roomNumber]; auto& room = g_Level.Rooms[roomNumber];
int sectorID = (room.zSize * roomGridCoord.x) + roomGridCoord.y; int sectorID = (room.ZSize * roomGridCoord.x) + roomGridCoord.y;
return room.floor[sectorID]; return room.Sectors[sectorID];
} }
FloorInfo& GetFloor(int roomNumber, int x, int z) FloorInfo& GetFloor(int roomNumber, int x, int z)
@ -831,18 +831,18 @@ namespace TEN::Collision::Floordata
const auto& room = g_Level.Rooms[item.RoomNumber]; const auto& room = g_Level.Rooms[item.RoomNumber];
// Get projected AABB min and max of bridge OBB. // Get projected AABB min and max of bridge OBB.
float xMin = floor((std::min(std::min(std::min(corners[0].x, corners[1].x), corners[4].x), corners[5].x) - room.x) / BLOCK(1)); float xMin = floor((std::min(std::min(std::min(corners[0].x, corners[1].x), corners[4].x), corners[5].x) - room.Position.x) / BLOCK(1));
float zMin = floor((std::min(std::min(std::min(corners[0].z, corners[1].z), corners[4].z), corners[5].z) - room.z) / BLOCK(1)); float zMin = floor((std::min(std::min(std::min(corners[0].z, corners[1].z), corners[4].z), corners[5].z) - room.Position.z) / BLOCK(1));
float xMax = ceil((std::max(std::max(std::max(corners[0].x, corners[1].x), corners[4].x), corners[5].x) - room.x) / BLOCK(1)); float xMax = ceil((std::max(std::max(std::max(corners[0].x, corners[1].x), corners[4].x), corners[5].x) - room.Position.x) / BLOCK(1));
float zMax = ceil((std::max(std::max(std::max(corners[0].z, corners[1].z), corners[4].z), corners[5].z) - room.z) / BLOCK(1)); float zMax = ceil((std::max(std::max(std::max(corners[0].z, corners[1].z), corners[4].z), corners[5].z) - room.Position.z) / BLOCK(1));
// Run through sectors enclosed in projected bridge AABB. // Run through sectors enclosed in projected bridge AABB.
for (int x = 0; x < room.xSize; x++) for (int x = 0; x < room.XSize; x++)
{ {
for (int z = 0; z < room.zSize; z++) for (int z = 0; z < room.ZSize; z++)
{ {
float pX = (room.x + BLOCK(x)) + BLOCK(0.5f); float pX = (room.Position.x + BLOCK(x)) + BLOCK(0.5f);
float pZ = (room.z + BLOCK(z)) + BLOCK(0.5f); float pZ = (room.Position.z + BLOCK(z)) + BLOCK(0.5f);
float offX = pX - item.Pose.Position.x; float offX = pX - item.Pose.Position.x;
float offZ = pZ - item.Pose.Position.z; float offZ = pZ - item.Pose.Position.z;
@ -911,7 +911,7 @@ namespace TEN::Collision::Floordata
// Run through neighboring rooms. // Run through neighboring rooms.
const auto& room = g_Level.Rooms[item.RoomNumber]; const auto& room = g_Level.Rooms[item.RoomNumber];
for (int neighborRoomNumber : room.neighbors) for (int neighborRoomNumber : room.NeighborRoomNumbers)
{ {
const auto& neighborRoom = g_Level.Rooms[neighborRoomNumber]; const auto& neighborRoom = g_Level.Rooms[neighborRoomNumber];
@ -919,8 +919,8 @@ namespace TEN::Collision::Floordata
auto roomGridCoords = GetNeighborRoomGridCoords(item.Pose.Position, neighborRoomNumber, SECTOR_SEARCH_DEPTH); auto roomGridCoords = GetNeighborRoomGridCoords(item.Pose.Position, neighborRoomNumber, SECTOR_SEARCH_DEPTH);
for (const auto& roomGridCoord : roomGridCoords) for (const auto& roomGridCoord : roomGridCoords)
{ {
pos.x = BLOCK(roomGridCoord.x) + neighborRoom.x; pos.x = BLOCK(roomGridCoord.x) + neighborRoom.Position.x;
pos.z = BLOCK(roomGridCoord.y) + neighborRoom.z; pos.z = BLOCK(roomGridCoord.y) + neighborRoom.Position.z;
pointColl = GetPointCollision(pos, neighborRoomNumber); pointColl = GetPointCollision(pos, neighborRoomNumber);
pos.y = pointColl.GetFloorHeight(); pos.y = pointColl.GetFloorHeight();

View file

@ -154,13 +154,13 @@ bool SameZone(CreatureInfo* creature, ItemInfo* target)
auto* zone = g_Level.Zones[(int)creature->LOT.Zone][(int)FlipStatus].data(); auto* zone = g_Level.Zones[(int)creature->LOT.Zone][(int)FlipStatus].data();
auto& roomSource = g_Level.Rooms[item.RoomNumber]; auto& roomSource = g_Level.Rooms[item.RoomNumber];
auto& boxSource = GetSector(&roomSource, item.Pose.Position.x - roomSource.x, item.Pose.Position.z - roomSource.z)->PathfindingBoxID; auto& boxSource = GetSector(&roomSource, item.Pose.Position.x - roomSource.Position.x, item.Pose.Position.z - roomSource.Position.z)->PathfindingBoxID;
if (boxSource == NO_VALUE) if (boxSource == NO_VALUE)
return false; return false;
item.BoxNumber = boxSource; item.BoxNumber = boxSource;
auto& roomTarget = g_Level.Rooms[target->RoomNumber]; auto& roomTarget = g_Level.Rooms[target->RoomNumber];
auto& boxTarget = GetSector(&roomTarget, target->Pose.Position.x - roomTarget.x, target->Pose.Position.z - roomTarget.z)->PathfindingBoxID; auto& boxTarget = GetSector(&roomTarget, target->Pose.Position.x - roomTarget.Position.x, target->Pose.Position.z - roomTarget.Position.z)->PathfindingBoxID;
if (boxTarget == NO_VALUE) if (boxTarget == NO_VALUE)
return false; return false;
target->BoxNumber = boxTarget; target->BoxNumber = boxTarget;
@ -1460,9 +1460,9 @@ void FindAITargetObject(CreatureInfo* creature, int objectNumber, int ocb, bool
int* zone = g_Level.Zones[(int)creature->LOT.Zone][(int)FlipStatus].data(); int* zone = g_Level.Zones[(int)creature->LOT.Zone][(int)FlipStatus].data();
auto* room = &g_Level.Rooms[item.RoomNumber]; auto* room = &g_Level.Rooms[item.RoomNumber];
item.BoxNumber = GetSector(room, item.Pose.Position.x - room->x, item.Pose.Position.z - room->z)->PathfindingBoxID; item.BoxNumber = GetSector(room, item.Pose.Position.x - room->Position.x, item.Pose.Position.z - room->Position.z)->PathfindingBoxID;
room = &g_Level.Rooms[aiObject.roomNumber]; room = &g_Level.Rooms[aiObject.roomNumber];
aiObject.boxNumber = GetSector(room, aiObject.pos.Position.x - room->x, aiObject.pos.Position.z - room->z)->PathfindingBoxID; aiObject.boxNumber = GetSector(room, aiObject.pos.Position.x - room->Position.x, aiObject.pos.Position.z - room->Position.z)->PathfindingBoxID;
if (item.BoxNumber == NO_VALUE || aiObject.boxNumber == NO_VALUE) if (item.BoxNumber == NO_VALUE || aiObject.boxNumber == NO_VALUE)
return; return;
@ -1504,7 +1504,7 @@ int TargetReachable(ItemInfo* item, ItemInfo* enemy)
{ {
const auto& creature = *GetCreatureInfo(item); const auto& creature = *GetCreatureInfo(item);
auto& room = g_Level.Rooms[enemy->RoomNumber]; auto& room = g_Level.Rooms[enemy->RoomNumber];
auto* floor = GetSector(&room, enemy->Pose.Position.x - room.x, enemy->Pose.Position.z - room.z); auto* floor = GetSector(&room, enemy->Pose.Position.x - room.Position.x, enemy->Pose.Position.z - room.Position.z);
// NEW: Only update enemy box number if it is actually reachable by the 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 // This prevents enemies from running to the player and attacking nothing when they are hanging or shimmying. -- Lwmte, 27.06.22
@ -1545,7 +1545,7 @@ void CreatureAIInfo(ItemInfo* item, AI_INFO* AI)
auto* zone = g_Level.Zones[(int)creature->LOT.Zone][(int)FlipStatus].data(); auto* zone = g_Level.Zones[(int)creature->LOT.Zone][(int)FlipStatus].data();
auto* room = &g_Level.Rooms[item->RoomNumber]; auto* room = &g_Level.Rooms[item->RoomNumber];
item->BoxNumber = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z)->PathfindingBoxID; item->BoxNumber = GetSector(room, item->Pose.Position.x - room->Position.x, item->Pose.Position.z - room->Position.z)->PathfindingBoxID;
AI->zoneNumber = zone[item->BoxNumber]; AI->zoneNumber = zone[item->BoxNumber];
enemy->BoxNumber = TargetReachable(item, enemy); enemy->BoxNumber = TargetReachable(item, enemy);
@ -2129,14 +2129,14 @@ void AdjustStopperFlag(ItemInfo* item, int direction)
int z = item->Pose.Position.z; int z = item->Pose.Position.z;
auto* room = &g_Level.Rooms[item->RoomNumber]; auto* room = &g_Level.Rooms[item->RoomNumber];
auto* floor = GetSector(room, x - room->x, z - room->z); auto* floor = GetSector(room, x - room->Position.x, z - room->Position.z);
floor->Stopper = !floor->Stopper; floor->Stopper = !floor->Stopper;
x = item->Pose.Position.x + BLOCK(1) * phd_sin(direction); x = item->Pose.Position.x + BLOCK(1) * phd_sin(direction);
z = item->Pose.Position.z + BLOCK(1) * phd_cos(direction); z = item->Pose.Position.z + BLOCK(1) * phd_cos(direction);
room = &g_Level.Rooms[GetPointCollision(Vector3i(x, item->Pose.Position.y, z), item->RoomNumber).GetRoomNumber()]; room = &g_Level.Rooms[GetPointCollision(Vector3i(x, item->Pose.Position.y, z), item->RoomNumber).GetRoomNumber()];
floor = GetSector(room, x - room->x, z - room->z); floor = GetSector(room, x - room->Position.x, z - room->Position.z);
floor->Stopper = !floor->Stopper; floor->Stopper = !floor->Stopper;
} }
@ -2151,11 +2151,11 @@ void InitializeItemBoxData()
{ {
for (const auto& mesh : room.mesh) for (const auto& mesh : room.mesh)
{ {
long index = ((mesh.pos.Position.z - room.z) / BLOCK(1)) + room.zSize * ((mesh.pos.Position.x - room.x) / BLOCK(1)); long index = ((mesh.pos.Position.z - room.Position.z) / BLOCK(1)) + room.ZSize * ((mesh.pos.Position.x - room.Position.x) / BLOCK(1));
if (index > room.floor.size()) if (index > room.Sectors.size())
continue; continue;
auto* floor = &room.floor[index]; auto* floor = &room.Sectors[index];
if (floor->PathfindingBoxID == NO_VALUE) if (floor->PathfindingBoxID == NO_VALUE)
continue; continue;
@ -2184,19 +2184,7 @@ bool CanCreatureJump(ItemInfo& item, JumpDistance jumpDistType)
if (creature.Enemy == nullptr) if (creature.Enemy == nullptr)
return false; return false;
float stepDist = 0.0f; float stepDist = BLOCK(0.92f);
switch (jumpDistType)
{
default:
case JumpDistance::Block1:
stepDist = BLOCK(0.51f);
break;
case JumpDistance::Block2:
stepDist = BLOCK(0.76f);
break;
}
int vPos = item.Pose.Position.y; int vPos = item.Pose.Position.y;
auto pointCollA = GetPointCollision(item, item.Pose.Orientation.y, stepDist); auto pointCollA = GetPointCollision(item, item.Pose.Orientation.y, stepDist);
auto pointCollB = GetPointCollision(item, item.Pose.Orientation.y, stepDist * 2); auto pointCollB = GetPointCollision(item, item.Pose.Orientation.y, stepDist * 2);

View file

@ -397,6 +397,7 @@ void KillMoveEffects()
ItemNewRoomNo = 0; ItemNewRoomNo = 0;
} }
// NOTE: No one should use this ever again.
int GetRandomControl() int GetRandomControl()
{ {
return Random::GenerateInt(); return Random::GenerateInt();
@ -578,6 +579,10 @@ GameStatus DoGameLoop(int levelIndex)
status = GameStatus::NewGame; status = GameStatus::NewGame;
break; break;
case InventoryResult::HomeLevel:
status = GameStatus::HomeLevel;
break;
case InventoryResult::LoadGame: case InventoryResult::LoadGame:
status = GameStatus::LoadGame; status = GameStatus::LoadGame;
break; break;

View file

@ -17,6 +17,7 @@ enum class GameStatus
{ {
Normal, Normal,
NewGame, NewGame,
HomeLevel,
LoadGame, LoadGame,
SaveGame, SaveGame,
ExitToTitle, ExitToTitle,

View file

@ -784,7 +784,7 @@ std::optional<Vector3> GetStaticObjectLos(const Vector3& origin, int roomNumber,
{ {
// Run through neighbor rooms. // Run through neighbor rooms.
const auto& room = g_Level.Rooms[roomNumber]; const auto& room = g_Level.Rooms[roomNumber];
for (int neighborRoomNumber : room.neighbors) for (int neighborRoomNumber : room.NeighborRoomNumbers)
{ {
// Get neighbor room. // Get neighbor room.
const auto& neighborRoom = g_Level.Rooms[neighborRoomNumber]; const auto& neighborRoom = g_Level.Rooms[neighborRoomNumber];

View file

@ -240,7 +240,7 @@ void CreateZone(ItemInfo* item)
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
auto* room = &g_Level.Rooms[item->RoomNumber]; auto* room = &g_Level.Rooms[item->RoomNumber];
item->BoxNumber = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z)->PathfindingBoxID; item->BoxNumber = GetSector(room, item->Pose.Position.x - room->Position.x, item->Pose.Position.z - room->Position.z)->PathfindingBoxID;
if (creature->LOT.Fly) if (creature->LOT.Fly)
{ {

View file

@ -139,14 +139,14 @@ namespace TEN::Control::Volumes
if (roomNumber == NO_VALUE) if (roomNumber == NO_VALUE)
return; return;
for (int currentRoomIndex : g_Level.Rooms[roomNumber].neighbors) for (int currentRoomIndex : g_Level.Rooms[roomNumber].NeighborRoomNumbers)
{ {
auto& room = g_Level.Rooms[currentRoomIndex]; auto& room = g_Level.Rooms[currentRoomIndex];
if (!room.Active()) if (!room.Active())
continue; continue;
for (auto& volume : room.triggerVolumes) for (auto& volume : room.TriggerVolumes)
{ {
if (!volume.Enabled) if (!volume.Enabled)
continue; continue;

View file

@ -158,7 +158,7 @@ namespace TEN::Effects::Bubble
{ {
// Hit water surface; spawn ripple. // Hit water surface; spawn ripple.
SpawnRipple( SpawnRipple(
Vector3(bubble.Position.x, g_Level.Rooms[prevRoomNumber].maxceiling, bubble.Position.z), Vector3(bubble.Position.x, g_Level.Rooms[prevRoomNumber].TopHeight, bubble.Position.z),
roomNumber, roomNumber,
((bubble.SizeMax.x + bubble.SizeMax.y) / 2) * 0.5f, ((bubble.SizeMax.x + bubble.SizeMax.y) / 2) * 0.5f,
(int)RippleFlags::SlowFade); (int)RippleFlags::SlowFade);
@ -168,7 +168,7 @@ namespace TEN::Effects::Bubble
} }
// Hit ceiling. NOTE: This is a hacky check. New collision fetching should provide fast info on a need-to-know basis. // Hit ceiling. NOTE: This is a hacky check. New collision fetching should provide fast info on a need-to-know basis.
else if (bubble.RoomNumber == prevRoomNumber && else if (bubble.RoomNumber == prevRoomNumber &&
bubble.Position.y <= g_Level.Rooms[prevRoomNumber].maxceiling) bubble.Position.y <= g_Level.Rooms[prevRoomNumber].TopHeight)
{ {
bubble.Life = 0.0f; bubble.Life = 0.0f;
continue; continue;

View file

@ -256,30 +256,23 @@ void TriggerPilotFlame(int itemNumber, int nodeIndex)
spark->dSize = size; spark->dSize = size;
} }
Particle* SetupPoisonSpark(Vector3 color) static Particle& SetupPoisonParticle(const Color& colorStart, const Color& colorEnd)
{ {
auto* spark = GetFreeParticle(); auto& part = *GetFreeParticle();
part.sR = std::clamp<unsigned char>(colorStart.x * UCHAR_MAX, 0, UCHAR_MAX);
part.sG = std::clamp<unsigned char>(colorStart.y * UCHAR_MAX, 0, UCHAR_MAX);
part.sB = std::clamp<unsigned char>(colorStart.z * UCHAR_MAX, 0, UCHAR_MAX);
part.dR = std::clamp<unsigned char>(colorEnd.x * UCHAR_MAX, 0, UCHAR_MAX);
part.dG = std::clamp<unsigned char>(colorEnd.y * UCHAR_MAX, 0, UCHAR_MAX);
part.dB = std::clamp<unsigned char>(colorEnd.z * UCHAR_MAX, 0, UCHAR_MAX);
part.colFadeSpeed = 14;
part.fadeToBlack = 8;
part.blendMode = BlendMode::Screen;
bool rMax = color.x > color.y && color.x > color.z; return part;
bool gMax = color.y > color.x && color.y > color.z;
bool bMax = color.z > color.x && color.z > color.y;
char seed = (GetRandomControl() & 0x1F) + 220;
spark->sR = (rMax ? seed : 255) * (color.x * 0.4);
spark->sG = (gMax ? seed : 255) * (color.y * 0.4);
spark->sB = (bMax ? seed : 255) * (color.z * 0.4);
spark->dR = 255 * color.x;
spark->dG = 255 * color.y;
spark->dB = 255 * color.z;
spark->colFadeSpeed = 14;
spark->fadeToBlack = 8;
spark->blendMode = BlendMode::Screen;
return spark;
} }
Particle* SetupFireSpark() static Particle* SetupFireSpark()
{ {
auto* spark = GetFreeParticle(); auto* spark = GetFreeParticle();
@ -296,19 +289,20 @@ Particle* SetupFireSpark()
return spark; return spark;
} }
void AttachAndCreateSpark(Particle* spark, ItemInfo* item, int meshIndex, Vector3i offset, Vector3i vel) static void AttachAndCreateSpark(Particle* spark, const ItemInfo* item, int meshID, Vector3i offset, Vector3i vel, int spriteID = 0)
{ {
auto pos1 = GetJointPosition(item, meshIndex, Vector3i(-4, -30, -4) + offset); auto pos1 = GetJointPosition(*item, meshID, Vector3i(-4, -30, -4) + offset);
spark->x = (GetRandomControl() & 0x1F) + pos1.x - 16; spark->x = (GetRandomControl() & 0x1F) + pos1.x - 16;
spark->y = (GetRandomControl() & 0x1F) + pos1.y - 16; spark->y = (GetRandomControl() & 0x1F) + pos1.y - 16;
spark->z = (GetRandomControl() & 0x1F) + pos1.z - 16; spark->z = (GetRandomControl() & 0x1F) + pos1.z - 16;
auto pos2 = GetJointPosition(item, meshIndex, Vector3i(-4, -30, -4) + offset + vel); auto pos2 = GetJointPosition(*item, meshID, Vector3i(-4, -30, -4) + offset + vel);
int v = (GetRandomControl() & 0x3F) + 192; int v = (GetRandomControl() & 0x3F) + 192;
spark->life = spark->sLife = v / 6; spark->life = spark->sLife = v / 6;
spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + spriteID;
spark->xVel = v * (pos2.x - pos1.x) / 10; spark->xVel = v * (pos2.x - pos1.x) / 10;
spark->yVel = v * (pos2.y - pos1.y) / 10; spark->yVel = v * (pos2.y - pos1.y) / 10;
@ -327,39 +321,39 @@ void AttachAndCreateSpark(Particle* spark, ItemInfo* item, int meshIndex, Vector
spark->on = 1; spark->on = 1;
} }
void ThrowFire(int itemNumber, int meshIndex, const Vector3i& offset, const Vector3i& vel) void ThrowFire(int itemNumber, int meshID, const Vector3i& offset, const Vector3i& vel, int spriteID)
{ {
auto& item = g_Level.Items[itemNumber]; auto& item = g_Level.Items[itemNumber];
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
{ {
auto& spark = *SetupFireSpark(); auto& part = *SetupFireSpark();
AttachAndCreateSpark(&spark, &item, meshIndex, offset, vel); AttachAndCreateSpark(&part, &item, meshID, offset, vel, spriteID);
spark.flags = SP_FIRE | SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; part.flags = SP_FIRE | SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF;
} }
} }
void ThrowFire(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel) void ThrowFire(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel, int spriteID)
{ {
ThrowFire(itemNumber, bite.BoneID, bite.Position, vel); ThrowFire(itemNumber, bite.BoneID, bite.Position, vel, spriteID);
} }
void ThrowPoison(int itemNumber, int meshIndex, const Vector3i& offset, const Vector3i& vel, const Vector3& color) void ThrowPoison(const ItemInfo& item, int boneID, const Vector3& offset, const Vector3& vel, const Color& colorStart, const Color& colorEnd, int spriteID)
{ {
auto* item = &g_Level.Items[itemNumber]; constexpr auto COUNT = 2;
for (int i = 0; i < 2; i++) for (int i = 0; i < COUNT; i++)
{ {
auto* spark = SetupPoisonSpark(color); auto& part = SetupPoisonParticle(colorStart, colorEnd);
AttachAndCreateSpark(spark, item, meshIndex, offset, vel); AttachAndCreateSpark(&part, &item, boneID, offset, vel, spriteID);
spark->flags = SP_POISON | SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF; part.flags = SP_POISON | SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF;
} }
} }
void ThrowPoison(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel, const Vector3& color) void ThrowPoison(const ItemInfo& item, const CreatureBiteInfo& bite, const Vector3& vel, const Color& colorStart, const Color& colorEnd, int spriteID)
{ {
ThrowPoison(itemNumber, bite.BoneID, bite.Position, vel, color); ThrowPoison(item, bite.BoneID, bite.Position, vel, colorStart, colorEnd, spriteID);
} }
void UpdateFireProgress() void UpdateFireProgress()
@ -993,10 +987,10 @@ void UpdateGunShells()
!TestEnvironment(ENV_FLAG_WATER, prevRoomNumber)) !TestEnvironment(ENV_FLAG_WATER, prevRoomNumber))
{ {
SpawnSplashDrips(Vector3(gunshell->pos.Position.x, g_Level.Rooms[gunshell->roomNumber].maxceiling, gunshell->pos.Position.z), gunshell->roomNumber, 3, true); SpawnSplashDrips(Vector3(gunshell->pos.Position.x, g_Level.Rooms[gunshell->roomNumber].TopHeight, gunshell->pos.Position.z), gunshell->roomNumber, 3, true);
//AddWaterSparks(gs->pos.Position.x, g_Level.Rooms[gs->roomNumber].maxceiling, gs->pos.Position.z, 8); //AddWaterSparks(gs->pos.Position.x, g_Level.Rooms[gs->roomNumber].maxceiling, gs->pos.Position.z, 8);
SpawnRipple( SpawnRipple(
Vector3(gunshell->pos.Position.x, g_Level.Rooms[gunshell->roomNumber].maxceiling, gunshell->pos.Position.z), Vector3(gunshell->pos.Position.x, g_Level.Rooms[gunshell->roomNumber].TopHeight, gunshell->pos.Position.z),
gunshell->roomNumber, gunshell->roomNumber,
Random::GenerateFloat(8.0f, 12.0f), Random::GenerateFloat(8.0f, 12.0f),
(int)RippleFlags::SlowFade); (int)RippleFlags::SlowFade);

View file

@ -230,10 +230,10 @@ void TriggerGlobalStaticFlame();
void TriggerGlobalFireSmoke(); void TriggerGlobalFireSmoke();
void TriggerGlobalFireFlame(); void TriggerGlobalFireFlame();
void TriggerPilotFlame(int itemNumber, int nodeIndex); void TriggerPilotFlame(int itemNumber, int nodeIndex);
void ThrowFire(int itemNumber, int meshIndex, const Vector3i& offset, const Vector3i& vel); void ThrowFire(int itemNumber, int meshID, const Vector3i& offset, const Vector3i& vel, int spriteID = 0);
void ThrowFire(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel); void ThrowFire(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel, int spriteID = 0);
void ThrowPoison(int itemNumber, int meshIndex, const Vector3i& offset, const Vector3i& vel, const Vector3& color); void ThrowPoison(const ItemInfo& item, int boneID, const Vector3& offset, const Vector3& vel, const Color& colorStart, const Color& colorEnd, int spriteID = 0);
void ThrowPoison(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel, const Vector3& color); void ThrowPoison(const ItemInfo& item, const CreatureBiteInfo& bite, const Vector3& vel, const Color& colorStart, const Color& colorEnd, int spriteID = 0);
void UpdateFireProgress(); void UpdateFireProgress();
void ClearFires(); void ClearFires();
void AddFire(int x, int y, int z, short roomNum, float size, short fade); void AddFire(int x, int y, int z, short roomNum, float size, short fade);

View file

@ -281,14 +281,17 @@ namespace TEN::Gui
enum TitleOption enum TitleOption
{ {
NewGame, NewGame,
HomeLevel,
LoadGame, LoadGame,
Options, Options,
ExitGame ExitGame,
Count
}; };
static const int numTitleOptions = 3; constexpr auto TITLE_OPTION_COUNT = TitleOption::Count - 1;
static const int numLoadGameOptions = SAVEGAME_MAX - 1; constexpr auto LOAD_GAME_OPTION_COUNT = SAVEGAME_MAX - 1;
static const int numOptionsOptions = 2; constexpr auto OPTION_OPTION_COUNT = 2;
static int selectedOptionBackup; static int selectedOptionBackup;
auto inventoryResult = InventoryResult::None; auto inventoryResult = InventoryResult::None;
@ -300,20 +303,31 @@ namespace TEN::Gui
switch (MenuToDisplay) switch (MenuToDisplay)
{ {
case Menu::Title: case Menu::Title:
OptionCount = g_GameFlow->IsLoadSaveEnabled() ? numTitleOptions : (numTitleOptions - 1); OptionCount = TITLE_OPTION_COUNT;
if (!g_GameFlow->IsHomeLevelEnabled())
OptionCount--;
if (!g_GameFlow->IsLoadSaveEnabled())
OptionCount--;
break; break;
case Menu::SelectLevel: case Menu::SelectLevel:
inventoryResult = InventoryResult::None; inventoryResult = InventoryResult::None;
OptionCount = g_GameFlow->GetNumLevels() - 2; OptionCount = g_GameFlow->GetNumLevels() - 2;
if (g_GameFlow->IsHomeLevelEnabled())
OptionCount--;
break; break;
case Menu::LoadGame: case Menu::LoadGame:
OptionCount = numLoadGameOptions; OptionCount = LOAD_GAME_OPTION_COUNT;
break; break;
case Menu::Options: case Menu::Options:
OptionCount = numOptionsOptions; OptionCount = OPTION_OPTION_COUNT;
break; break;
case Menu::Display: case Menu::Display:
@ -354,21 +368,7 @@ namespace TEN::Gui
MenuToDisplay == Menu::SelectLevel || MenuToDisplay == Menu::SelectLevel ||
MenuToDisplay == Menu::Options) MenuToDisplay == Menu::Options)
{ {
if (GuiIsPulsed(In::Forward)) SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus);
{
SelectedOption = (SelectedOption <= 0) ? OptionCount : (SelectedOption - 1);
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
if (GuiIsPulsed(In::Back))
{
if (SelectedOption < OptionCount)
SelectedOption++;
else
SelectedOption -= OptionCount;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
if (GuiIsDeselected() && MenuToDisplay != Menu::Title) if (GuiIsDeselected() && MenuToDisplay != Menu::Title)
{ {
@ -383,9 +383,14 @@ namespace TEN::Gui
if (MenuToDisplay == Menu::Title) if (MenuToDisplay == Menu::Title)
{ {
// Skip load game entry if loading and saving is disabled.
int realSelectedOption = SelectedOption; int realSelectedOption = SelectedOption;
if (!g_GameFlow->IsLoadSaveEnabled() && SelectedOption > TitleOption::NewGame)
// Skip Home Level entry if home level is disabled.
if (!g_GameFlow->IsHomeLevelEnabled() && realSelectedOption > TitleOption::NewGame)
realSelectedOption++;
// Skip Load Game entry if loading and saving is disabled.
if (!g_GameFlow->IsLoadSaveEnabled() && realSelectedOption > TitleOption::HomeLevel)
realSelectedOption++; realSelectedOption++;
switch (realSelectedOption) switch (realSelectedOption)
@ -404,6 +409,10 @@ namespace TEN::Gui
break; break;
case TitleOption::HomeLevel:
inventoryResult = InventoryResult::HomeLevel;
break;
case TitleOption::LoadGame: case TitleOption::LoadGame:
selectedOptionBackup = SelectedOption; selectedOptionBackup = SelectedOption;
SelectedOption = 0; SelectedOption = 0;
@ -424,8 +433,13 @@ namespace TEN::Gui
} }
else if (MenuToDisplay == Menu::SelectLevel) else if (MenuToDisplay == Menu::SelectLevel)
{ {
// Level 0 is the title level, so increment the option by 1 to offset it. // Level 0 is Title Level; increment option to offset it.
g_GameFlow->SelectedLevelForNewGame = SelectedOption + 1; g_GameFlow->SelectedLevelForNewGame = SelectedOption + 1;
// Level 1 reserved for Home Level; increment option if enabled to offset it.
if (g_GameFlow->IsHomeLevelEnabled())
g_GameFlow->SelectedLevelForNewGame++;
MenuToDisplay = Menu::Title; MenuToDisplay = Menu::Title;
SelectedOption = 0; SelectedOption = 0;
inventoryResult = InventoryResult::NewGameSelectedLevel; inventoryResult = InventoryResult::NewGameSelectedLevel;
@ -577,25 +591,7 @@ namespace TEN::Gui
} }
} }
if (GuiIsPulsed(In::Forward)) SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus);
{
if (SelectedOption <= 0)
SelectedOption += OptionCount;
else
SelectedOption--;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
if (GuiIsPulsed(In::Back))
{
if (SelectedOption < OptionCount)
SelectedOption++;
else
SelectedOption -= OptionCount;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
if (GuiIsSelected()) if (GuiIsSelected())
{ {
@ -743,25 +739,7 @@ namespace TEN::Gui
} }
else else
{ {
if (GuiIsPulsed(In::Forward)) SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus);
{
if (SelectedOption <= 0)
SelectedOption += OptionCount;
else
SelectedOption--;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
if (GuiIsPulsed(In::Back))
{
if (SelectedOption < OptionCount)
SelectedOption++;
else
SelectedOption -= OptionCount;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
// HACK: Menu screen scroll. // HACK: Menu screen scroll.
if (GuiIsPulsed(In::Left) || GuiIsPulsed(In::Right)) if (GuiIsPulsed(In::Left) || GuiIsPulsed(In::Right))
@ -893,8 +871,9 @@ namespace TEN::Gui
TargetHighlighter, TargetHighlighter,
ToggleRumble, ToggleRumble,
ThumbstickCameraControl, ThumbstickCameraControl,
MouseSensitivity, MouseSensitivity,
MouseSmoothing, MenuOptionLooping,
Apply, Apply,
Cancel, Cancel,
@ -1000,14 +979,23 @@ namespace TEN::Gui
break; break;
case OtherSettingsOption::MouseSmoothing: case OtherSettingsOption::MenuOptionLooping:
if (CurrentSettings.Configuration.MouseSmoothing > MOUSE_SMOOTHING_MIN)
{
CurrentSettings.Configuration.MouseSmoothing -= 1;
if (CurrentSettings.Configuration.MouseSmoothing < MOUSE_SMOOTHING_MIN)
CurrentSettings.Configuration.MouseSmoothing = MOUSE_SMOOTHING_MIN;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
switch (CurrentSettings.Configuration.MenuOptionLoopingMode)
{
default:
case MenuOptionLoopingMode::AllMenus:
CurrentSettings.Configuration.MenuOptionLoopingMode = MenuOptionLoopingMode::Disabled;
break;
case MenuOptionLoopingMode::SaveLoadOnly:
CurrentSettings.Configuration.MenuOptionLoopingMode = MenuOptionLoopingMode::AllMenus;
break;
case MenuOptionLoopingMode::Disabled:
CurrentSettings.Configuration.MenuOptionLoopingMode = MenuOptionLoopingMode::SaveLoadOnly;
break;
} }
break; break;
@ -1063,14 +1051,23 @@ namespace TEN::Gui
break; break;
case OtherSettingsOption::MouseSmoothing: case OtherSettingsOption::MenuOptionLooping:
if (CurrentSettings.Configuration.MouseSmoothing < MOUSE_SMOOTHING_MAX)
{
CurrentSettings.Configuration.MouseSmoothing += 1;
if (CurrentSettings.Configuration.MouseSmoothing > MOUSE_SMOOTHING_MAX)
CurrentSettings.Configuration.MouseSmoothing = MOUSE_SMOOTHING_MAX;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
switch (CurrentSettings.Configuration.MenuOptionLoopingMode)
{
default:
case MenuOptionLoopingMode::AllMenus:
CurrentSettings.Configuration.MenuOptionLoopingMode = MenuOptionLoopingMode::SaveLoadOnly;
break;
case MenuOptionLoopingMode::SaveLoadOnly:
CurrentSettings.Configuration.MenuOptionLoopingMode = MenuOptionLoopingMode::Disabled;
break;
case MenuOptionLoopingMode::Disabled:
CurrentSettings.Configuration.MenuOptionLoopingMode = MenuOptionLoopingMode::AllMenus;
break;
} }
break; break;
@ -1083,33 +1080,7 @@ namespace TEN::Gui
} }
} }
if (GuiIsPulsed(In::Forward)) SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus);
{
if (SelectedOption <= 0)
{
SelectedOption += OptionCount;
}
else
{
SelectedOption--;
}
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
if (GuiIsPulsed(In::Back))
{
if (SelectedOption < OptionCount)
{
SelectedOption++;
}
else
{
SelectedOption -= OptionCount;
}
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
if (GuiIsSelected()) if (GuiIsSelected())
{ {
@ -1191,25 +1162,7 @@ namespace TEN::Gui
if (MenuToDisplay == Menu::Pause || if (MenuToDisplay == Menu::Pause ||
MenuToDisplay == Menu::Options) MenuToDisplay == Menu::Options)
{ {
if (GuiIsPulsed(In::Forward)) SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus);
{
if (SelectedOption <= 0)
SelectedOption += OptionCount;
else
SelectedOption--;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
if (GuiIsPulsed(In::Back))
{
if (SelectedOption < OptionCount)
SelectedOption++;
else
SelectedOption -= OptionCount;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
} }
if (GuiIsDeselected() || IsClicked(In::Pause)) if (GuiIsDeselected() || IsClicked(In::Pause))
@ -2515,51 +2468,10 @@ namespace TEN::Gui
!invRing.ObjectListMovement && !invRing.ObjectListMovement &&
!ammoRing.ObjectListMovement) !ammoRing.ObjectListMovement)
{ {
CurrentSelectedOption = GetLoopedSelectedOption(CurrentSelectedOption, n - 1, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus);
if (AmmoActive) if (AmmoActive)
{
if (GuiIsPulsed(In::Forward))
{
if (CurrentSelectedOption <= 0)
CurrentSelectedOption = n - 1;
else
CurrentSelectedOption--;
SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always);
}
if (GuiIsPulsed(In::Back))
{
if (CurrentSelectedOption >= (n - 1))
CurrentSelectedOption = 0;
else
CurrentSelectedOption++;
SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always);
}
*CurrentAmmoType = CurrentSelectedOption; *CurrentAmmoType = CurrentSelectedOption;
}
else
{
if (GuiIsPulsed(In::Forward))
{
if (CurrentSelectedOption <= 0)
CurrentSelectedOption = n - 1;
else
CurrentSelectedOption--;
SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always);
}
else if (GuiIsPulsed(In::Back))
{
if (CurrentSelectedOption >= (n - 1))
CurrentSelectedOption = 0;
else
CurrentSelectedOption++;
SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always);
}
}
if (GuiIsSelected(false)) if (GuiIsSelected(false))
{ {
@ -3459,27 +3371,49 @@ namespace TEN::Gui
return SelectedSaveSlot; return SelectedSaveSlot;
} }
LoadResult GuiController::DoLoad() int GuiController::GetLoopedSelectedOption(int selectedOption, int optionCount, bool canLoop)
{ {
if (GuiIsPulsed(In::Back))
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
if (SelectedSaveSlot == (SAVEGAME_MAX - 1))
SelectedSaveSlot -= SAVEGAME_MAX - 1;
else
SelectedSaveSlot++;
}
if (GuiIsPulsed(In::Forward)) if (GuiIsPulsed(In::Forward))
{
if (selectedOption <= 0)
{
if (IsClicked(In::Forward) && canLoop)
{ {
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
return optionCount;
if (SelectedSaveSlot == 0)
SelectedSaveSlot += SAVEGAME_MAX - 1;
else
SelectedSaveSlot--;
} }
}
else
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
return (selectedOption - 1);
}
}
else if (GuiIsPulsed(In::Back))
{
if (selectedOption >= optionCount)
{
if (IsClicked(In::Back) && canLoop)
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
return 0;
}
}
else
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
return (selectedOption + 1);
}
}
return selectedOption;
}
LoadResult GuiController::DoLoad()
{
bool canLoop = g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::SaveLoadOnly ||
g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus;
SelectedSaveSlot = GetLoopedSelectedOption(SelectedSaveSlot, SAVEGAME_MAX - 1, canLoop);
if (GuiIsSelected()) if (GuiIsSelected())
{ {
@ -3501,25 +3435,9 @@ namespace TEN::Gui
bool GuiController::DoSave() bool GuiController::DoSave()
{ {
if (GuiIsPulsed(In::Back)) bool canLoop = g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::SaveLoadOnly ||
{ g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); SelectedSaveSlot = GetLoopedSelectedOption(SelectedSaveSlot, SAVEGAME_MAX - 1, canLoop);
if (SelectedSaveSlot == (SAVEGAME_MAX - 1))
SelectedSaveSlot -= SAVEGAME_MAX - 1;
else
SelectedSaveSlot++;
}
if (GuiIsPulsed(In::Forward))
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
if (SelectedSaveSlot == 0)
SelectedSaveSlot += SAVEGAME_MAX - 1;
else
SelectedSaveSlot--;
}
if (GuiIsSelected()) if (GuiIsSelected())
{ {

View file

@ -30,6 +30,7 @@ namespace TEN::Gui
None, None,
UseItem, UseItem,
NewGame, NewGame,
HomeLevel,
LoadGame, LoadGame,
SaveGame, SaveGame,
ExitGame, ExitGame,
@ -184,6 +185,7 @@ namespace TEN::Gui
void UseItem(ItemInfo& item, int objectNumber); void UseItem(ItemInfo& item, int objectNumber);
// Getters // Getters
const InventoryRing& GetRing(RingTypes ringType); const InventoryRing& GetRing(RingTypes ringType);
int GetSelectedOption(); int GetSelectedOption();
Menu GetMenuToDisplay(); Menu GetMenuToDisplay();
@ -193,8 +195,10 @@ namespace TEN::Gui
int GetLastInventoryItem(); int GetLastInventoryItem();
SettingsData& GetCurrentSettings(); SettingsData& GetCurrentSettings();
int GetLoadSaveSelection(); int GetLoadSaveSelection();
int GetLoopedSelectedOption(int selectedOption, int optionCount, bool canLoop);
// Setters // Setters
void SetSelectedOption(int menu); void SetSelectedOption(int menu);
void SetMenuToDisplay(Menu menu); void SetMenuToDisplay(Menu menu);
void SetInventoryMode(InventoryMode mode); void SetInventoryMode(InventoryMode mode);

View file

@ -83,12 +83,6 @@ struct CreatureBiteInfo
Position = pos; Position = pos;
BoneID = boneID; BoneID = boneID;
} }
CreatureBiteInfo(float x, float y, float z, int boneID)
{
Position = Vector3(x, y, z);
BoneID = boneID;
}
}; };
struct CreatureMuzzleFlashInfo struct CreatureMuzzleFlashInfo

View file

@ -584,7 +584,7 @@ void InitializeItem(short itemNumber)
item->NextItem = room->itemNumber; item->NextItem = room->itemNumber;
room->itemNumber = itemNumber; room->itemNumber = itemNumber;
FloorInfo* floor = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z); FloorInfo* floor = GetSector(room, item->Pose.Position.x - room->Position.x, item->Pose.Position.z - room->Position.z);
item->Floor = floor->GetSurfaceHeight(item->Pose.Position.x, item->Pose.Position.z, true); item->Floor = floor->GetSurfaceHeight(item->Pose.Position.x, item->Pose.Position.z, true);
item->BoxNumber = floor->PathfindingBoxID; item->BoxNumber = floor->PathfindingBoxID;

View file

@ -8,34 +8,36 @@
#include "Game/items.h" #include "Game/items.h"
#include "Game/Setup.h" #include "Game/Setup.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/trutils.h"
using namespace TEN::Collision::Point; using namespace TEN::Collision::Point;
using namespace TEN::Utils;
CreatureInfo* GetCreatureInfo(ItemInfo* item) CreatureInfo* GetCreatureInfo(ItemInfo* item)
{ {
return (CreatureInfo*)item->Data; return (CreatureInfo*)item->Data;
} }
void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature) void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature, const std::vector<GAME_OBJECT_ID>& keyObjectIds, bool ignoreKeyObjectIds)
{ {
float nearestDistance = INFINITY; float closestDistSqr = INFINITY;
for (int itemNumber = 0; itemNumber < g_Level.NumItems; itemNumber++)
for (int i = 0; i < g_Level.NumItems; i++)
{ {
auto* targetEntity = &g_Level.Items[i]; auto* targetItem = &g_Level.Items[itemNumber];
if (targetItem == nullptr || targetItem->Index == item->Index)
if (targetEntity == nullptr)
continue; continue;
if (targetEntity != item && // Ignore or specifically target key object IDs.
targetEntity->HitPoints > 0 && if (ignoreKeyObjectIds ? Contains(keyObjectIds, targetItem->ObjectNumber) : !Contains(keyObjectIds, targetItem->ObjectNumber))
targetEntity->Status != ITEM_INVISIBLE) continue;
if (targetItem != item && targetItem->HitPoints > 0 && targetItem->Status != ITEM_INVISIBLE)
{ {
float distance = Vector3i::Distance(item->Pose.Position, targetEntity->Pose.Position); float distSqr = Vector3i::DistanceSquared(item->Pose.Position, targetItem->Pose.Position);
if (distance < nearestDistance) if (distSqr < closestDistSqr)
{ {
creature->Enemy = targetEntity; creature->Enemy = targetItem;
nearestDistance = distance; closestDistSqr = distSqr;
} }
} }
} }

View file

@ -18,5 +18,5 @@ enum LaraMeshMask
}; };
CreatureInfo* GetCreatureInfo(ItemInfo* item); CreatureInfo* GetCreatureInfo(ItemInfo* item);
void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature); void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature, const std::vector<GAME_OBJECT_ID>& keyObjectIds = {}, bool ignoreKeyObjectIds = true);
bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist, bool canFloat); bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist, bool canFloat);

View file

@ -66,6 +66,7 @@ void ControlMissile(short fxNumber)
// Check whether something was hit. // Check whether something was hit.
if (fx.pos.Position.y >= pointColl.GetFloorHeight() || if (fx.pos.Position.y >= pointColl.GetFloorHeight() ||
fx.pos.Position.y <= pointColl.GetCeilingHeight() || fx.pos.Position.y <= pointColl.GetCeilingHeight() ||
pointColl.IsWall() ||
hasHitPlayer) hasHitPlayer)
{ {
if (fx.objectNumber == ID_KNIFETHROWER_KNIFE || if (fx.objectNumber == ID_KNIFETHROWER_KNIFE ||
@ -111,6 +112,7 @@ void ControlMissile(short fxNumber)
} }
KillEffect(fxNumber); KillEffect(fxNumber);
return;
} }
if (pointColl.GetRoomNumber() != fx.roomNumber) if (pointColl.GetRoomNumber() != fx.roomNumber)

View file

@ -169,8 +169,8 @@ bool TargetVisible(ItemInfo* item, AI_INFO* ai, float maxAngleInDegrees)
auto target = GameVector( auto target = GameVector(
enemy->Pose.Position.x, enemy->Pose.Position.x,
enemy->Pose.Position.y + ((((bounds.Y1 * 2) + bounds.Y1) + bounds.Y2) / 4), enemy->Pose.Position.y + ((((bounds.Y1 * 2) + bounds.Y1) + bounds.Y2) / 4),
enemy->Pose.Position.z, enemy->Pose.Position.z);
enemy->RoomNumber); // TODO: Check why this line didn't exist before. -- TokyoSU, 10/8/2022
return LOS(&origin, &target); return LOS(&origin, &target);
} }

View file

@ -32,8 +32,8 @@ bool ROOM_INFO::Active() const
// Since engine swaps whole room memory block but substitutes flippedRoom, // Since engine swaps whole room memory block but substitutes flippedRoom,
// must check both original room number and flippedRoom equality, // must check both original room number and flippedRoom equality,
// as well as NO_VALUE if checking non-flipped rooms. // as well as NO_VALUE if checking non-flipped rooms.
return (!FlipStats[flipNumber] && flippedRoom != index && flippedRoom != NO_VALUE) || return (!FlipStats[flipNumber] && flippedRoom != RoomNumber && flippedRoom != NO_VALUE) ||
( FlipStats[flipNumber] && flippedRoom == index); ( FlipStats[flipNumber] && flippedRoom == RoomNumber);
} }
static void AddRoomFlipItems(const ROOM_INFO& room) static void AddRoomFlipItems(const ROOM_INFO& room)
@ -105,11 +105,11 @@ void DoFlipMap(int group)
g_Renderer.FlipRooms(roomNumber, room.flippedRoom); g_Renderer.FlipRooms(roomNumber, room.flippedRoom);
// Update active room sectors. // Update active room sectors.
for (auto& sector : room.floor) for (auto& sector : room.Sectors)
sector.RoomNumber = roomNumber; sector.RoomNumber = roomNumber;
// Update flipped room sectors. // Update flipped room sectors.
for (auto& sector : flippedRoom.floor) for (auto& sector : flippedRoom.Sectors)
sector.RoomNumber = room.flippedRoom; sector.RoomNumber = room.flippedRoom;
} }
} }
@ -158,9 +158,9 @@ int IsRoomOutside(int x, int y, int z)
int roomNumber = OutsideRoomTable[xTable][zTable][i]; int roomNumber = OutsideRoomTable[xTable][zTable][i];
const auto& room = g_Level.Rooms[roomNumber]; const auto& room = g_Level.Rooms[roomNumber];
if ((x > (room.x + BLOCK(1)) && x < (room.x + (room.xSize - 1) * BLOCK(1))) && if ((x > (room.Position.x + BLOCK(1)) && x < (room.Position.x + (room.XSize - 1) * BLOCK(1))) &&
(y > room.maxceiling && y < room.minfloor) && (y > room.TopHeight && y < room.BottomHeight) &&
(z > (room.z + BLOCK(1)) && z < (room.z + (room.zSize - 1) * BLOCK(1)))) (z > (room.Position.z + BLOCK(1)) && z < (room.Position.z + (room.ZSize - 1) * BLOCK(1))))
{ {
auto pointColl = GetPointCollision(Vector3i(x, y, z), roomNumber); auto pointColl = GetPointCollision(Vector3i(x, y, z), roomNumber);
@ -188,14 +188,14 @@ namespace TEN::Collision::Room
// TODO: Can use floordata's GetRoomGridCoord()? // TODO: Can use floordata's GetRoomGridCoord()?
FloorInfo* GetSector(ROOM_INFO* room, int x, int z) FloorInfo* GetSector(ROOM_INFO* room, int x, int z)
{ {
int sectorX = std::clamp(x / BLOCK(1), 0, room->xSize - 1); int sectorX = std::clamp(x / BLOCK(1), 0, room->XSize - 1);
int sectorZ = std::clamp(z / BLOCK(1), 0, room->zSize - 1); int sectorZ = std::clamp(z / BLOCK(1), 0, room->ZSize - 1);
int sectorID = sectorZ + (sectorX * room->zSize); int sectorID = sectorZ + (sectorX * room->ZSize);
if (sectorID > room->floor.size()) if (sectorID > room->Sectors.size())
return nullptr; return nullptr;
return &room->floor[sectorID]; return &room->Sectors[sectorID];
} }
} }
@ -219,9 +219,9 @@ bool IsPointInRoom(const Vector3i& pos, int roomNumber)
{ {
const auto& room = g_Level.Rooms[roomNumber]; const auto& room = g_Level.Rooms[roomNumber];
if (pos.z >= (room.z + BLOCK(1)) && pos.z <= (room.z + BLOCK(room.zSize - 1)) && if (pos.z >= (room.Position.z + BLOCK(1)) && pos.z <= (room.Position.z + BLOCK(room.ZSize - 1)) &&
pos.y <= room.minfloor && pos.y > room.maxceiling && pos.y <= room.BottomHeight && pos.y > room.TopHeight &&
pos.x >= (room.x + BLOCK(1)) && pos.x <= (room.x + BLOCK(room.xSize - 1))) pos.x >= (room.Position.x + BLOCK(1)) && pos.x <= (room.Position.x + BLOCK(room.XSize - 1)))
{ {
return true; return true;
} }
@ -234,7 +234,7 @@ int FindRoomNumber(const Vector3i& pos, int startRoomNumber)
if (startRoomNumber != NO_VALUE && startRoomNumber < g_Level.Rooms.size()) if (startRoomNumber != NO_VALUE && startRoomNumber < g_Level.Rooms.size())
{ {
const auto& room = g_Level.Rooms[startRoomNumber]; const auto& room = g_Level.Rooms[startRoomNumber];
for (int neighborRoomNumber : room.neighbors) for (int neighborRoomNumber : room.NeighborRoomNumbers)
{ {
const auto& neighborRoom = g_Level.Rooms[neighborRoomNumber]; const auto& neighborRoom = g_Level.Rooms[neighborRoomNumber];
if (neighborRoomNumber != startRoomNumber && neighborRoom.Active() && if (neighborRoomNumber != startRoomNumber && neighborRoom.Active() &&
@ -258,15 +258,15 @@ Vector3i GetRoomCenter(int roomNumber)
{ {
const auto& room = g_Level.Rooms[roomNumber]; const auto& room = g_Level.Rooms[roomNumber];
int halfLength = BLOCK(room.xSize) / 2; int halfLength = BLOCK(room.XSize) / 2;
int halfDepth = BLOCK(room.zSize) / 2; int halfDepth = BLOCK(room.ZSize) / 2;
int halfHeight = (room.maxceiling - room.minfloor) / 2; int halfHeight = (room.TopHeight - room.BottomHeight) / 2;
// Calculate and return center. // Calculate and return center.
return Vector3i( return Vector3i(
room.x + halfLength, room.Position.x + halfLength,
room.minfloor + halfHeight, room.BottomHeight + halfHeight,
room.z + halfDepth); room.Position.z + halfDepth);
} }
static std::vector<int> GetNeighborRoomNumbers(int roomNumber, unsigned int searchDepth, std::vector<int>& visitedRoomNumbers = std::vector<int>{}) static std::vector<int> GetNeighborRoomNumbers(int roomNumber, unsigned int searchDepth, std::vector<int>& visitedRoomNumbers = std::vector<int>{})
@ -316,7 +316,7 @@ void InitializeNeighborRoomList()
for (int roomNumber = 0; roomNumber < g_Level.Rooms.size(); roomNumber++) for (int roomNumber = 0; roomNumber < g_Level.Rooms.size(); roomNumber++)
{ {
auto& room = g_Level.Rooms[roomNumber]; auto& room = g_Level.Rooms[roomNumber];
room.neighbors = GetNeighborRoomNumbers(roomNumber, NEIGHBOR_ROOM_SEARCH_DEPTH); room.NeighborRoomNumbers = GetNeighborRoomNumbers(roomNumber, NEIGHBOR_ROOM_SEARCH_DEPTH);
} }
// Add flipped variations of itself. // Add flipped variations of itself.
@ -326,11 +326,11 @@ void InitializeNeighborRoomList()
if (room.flippedRoom == NO_VALUE) if (room.flippedRoom == NO_VALUE)
continue; continue;
if (!Contains(room.neighbors, room.flippedRoom)) if (!Contains(room.NeighborRoomNumbers, room.flippedRoom))
room.neighbors.push_back(room.flippedRoom); room.NeighborRoomNumbers.push_back(room.flippedRoom);
auto& flippedRoom = g_Level.Rooms[room.flippedRoom]; auto& flippedRoom = g_Level.Rooms[room.flippedRoom];
if (!Contains(flippedRoom.neighbors, roomNumber)) if (!Contains(flippedRoom.NeighborRoomNumbers, roomNumber))
flippedRoom.neighbors.push_back(roomNumber); flippedRoom.NeighborRoomNumbers.push_back(roomNumber);
} }
} }

View file

@ -87,31 +87,32 @@ struct MESH_INFO
struct ROOM_INFO struct ROOM_INFO
{ {
int index; int RoomNumber = 0;
int x; std::string Name = {};
int y; std::vector<std::string> Tags = {};
int z;
int minfloor; Vector3i Position = Vector3i::Zero;
int maxceiling; int BottomHeight = 0;
int xSize; int TopHeight = 0;
int zSize; int XSize = 0;
int ZSize = 0;
Vector3 ambient; Vector3 ambient;
int flippedRoom;
int flags; int flags;
int meshEffect; int meshEffect;
ReverbType reverbType; ReverbType reverbType;
int flippedRoom;
int flipNumber; int flipNumber;
short itemNumber; short itemNumber;
short fxNumber; short fxNumber;
bool boundActive; bool boundActive;
std::string name = {}; std::vector<int> NeighborRoomNumbers = {};
std::vector<std::string> tags = {};
std::vector<FloorInfo> floor = {}; std::vector<FloorInfo> Sectors = {};
std::vector<ROOM_LIGHT> lights = {}; std::vector<ROOM_LIGHT> lights = {};
std::vector<MESH_INFO> mesh = {}; std::vector<MESH_INFO> mesh = {}; // Statics
std::vector<TriggerVolume> triggerVolumes = {}; std::vector<TriggerVolume> TriggerVolumes = {};
std::vector<Vector3> positions = {}; std::vector<Vector3> positions = {};
std::vector<Vector3> normals = {}; std::vector<Vector3> normals = {};
@ -120,8 +121,6 @@ struct ROOM_INFO
std::vector<BUCKET> buckets = {}; std::vector<BUCKET> buckets = {};
std::vector<ROOM_DOOR> doors = {}; std::vector<ROOM_DOOR> doors = {};
std::vector<int> neighbors = {};
bool Active() const; bool Active() const;
}; };

View file

@ -544,11 +544,11 @@ const std::vector<byte> SaveGame::Build()
std::vector<flatbuffers::Offset<Save::Room>> rooms; std::vector<flatbuffers::Offset<Save::Room>> rooms;
for (auto& room : g_Level.Rooms) for (auto& room : g_Level.Rooms)
{ {
auto nameOffset = fbb.CreateString(room.name); auto nameOffset = fbb.CreateString(room.Name);
Save::RoomBuilder serializedInfo{ fbb }; Save::RoomBuilder serializedInfo{ fbb };
serializedInfo.add_name(nameOffset); serializedInfo.add_name(nameOffset);
serializedInfo.add_index(room.index); serializedInfo.add_index(room.RoomNumber);
serializedInfo.add_reverb_type((int)room.reverbType); serializedInfo.add_reverb_type((int)room.reverbType);
serializedInfo.add_flags(room.flags); serializedInfo.add_flags(room.flags);
auto serializedInfoOffset = serializedInfo.Finish(); auto serializedInfoOffset = serializedInfo.Finish();
@ -991,14 +991,14 @@ const std::vector<byte> SaveGame::Build()
staticMesh.add_flags(room->mesh[j].flags); staticMesh.add_flags(room->mesh[j].flags);
staticMesh.add_hit_points(room->mesh[j].HitPoints); staticMesh.add_hit_points(room->mesh[j].HitPoints);
staticMesh.add_room_number(room->index); staticMesh.add_room_number(room->RoomNumber);
staticMesh.add_number(j); staticMesh.add_number(j);
staticMeshes.push_back(staticMesh.Finish()); staticMeshes.push_back(staticMesh.Finish());
} }
for (int j = 0; j < room->triggerVolumes.size(); j++) for (int j = 0; j < room->TriggerVolumes.size(); j++)
{ {
auto& currVolume = room->triggerVolumes[j]; auto& currVolume = room->TriggerVolumes[j];
std::vector<flatbuffers::Offset<Save::VolumeState>> queue; std::vector<flatbuffers::Offset<Save::VolumeState>> queue;
for (int k = 0; k < currVolume.StateQueue.size(); k++) for (int k = 0; k < currVolume.StateQueue.size(); k++)
@ -1022,7 +1022,7 @@ const std::vector<byte> SaveGame::Build()
auto nameOffset = fbb.CreateString(currVolume.Name); auto nameOffset = fbb.CreateString(currVolume.Name);
Save::VolumeBuilder volume{ fbb }; Save::VolumeBuilder volume{ fbb };
volume.add_room_number(room->index); volume.add_room_number(room->RoomNumber);
volume.add_number(j); volume.add_number(j);
volume.add_name(nameOffset); volume.add_name(nameOffset);
volume.add_enabled(currVolume.Enabled); volume.add_enabled(currVolume.Enabled);
@ -2155,7 +2155,7 @@ static void ParseLevel(const Save::SaveGame* s, bool hubMode)
for (int i = 0; i < s->rooms()->size(); i++) for (int i = 0; i < s->rooms()->size(); i++)
{ {
auto room = s->rooms()->Get(i); auto room = s->rooms()->Get(i);
g_Level.Rooms[room->index()].name = room->name()->str(); g_Level.Rooms[room->index()].Name = room->name()->str();
g_Level.Rooms[room->index()].flags = room->flags(); g_Level.Rooms[room->index()].flags = room->flags();
g_Level.Rooms[room->index()].reverbType = (ReverbType)room->reverb_type(); g_Level.Rooms[room->index()].reverbType = (ReverbType)room->reverb_type();
} }
@ -2191,18 +2191,18 @@ static void ParseLevel(const Save::SaveGame* s, bool hubMode)
auto room = &g_Level.Rooms[volume->room_number()]; auto room = &g_Level.Rooms[volume->room_number()];
int number = volume->number(); int number = volume->number();
room->triggerVolumes[number].Enabled = volume->enabled(); room->TriggerVolumes[number].Enabled = volume->enabled();
room->triggerVolumes[number].Name = volume->name()->str(); room->TriggerVolumes[number].Name = volume->name()->str();
room->triggerVolumes[number].Box.Center = room->TriggerVolumes[number].Box.Center =
room->triggerVolumes[number].Sphere.Center = ToVector3(volume->position()); room->TriggerVolumes[number].Sphere.Center = ToVector3(volume->position());
room->triggerVolumes[number].Box.Orientation = ToVector4(volume->rotation()); room->TriggerVolumes[number].Box.Orientation = ToVector4(volume->rotation());
room->triggerVolumes[number].Box.Extents = ToVector3(volume->scale()); room->TriggerVolumes[number].Box.Extents = ToVector3(volume->scale());
room->triggerVolumes[number].Sphere.Radius = room->triggerVolumes[number].Box.Extents.x; room->TriggerVolumes[number].Sphere.Radius = room->TriggerVolumes[number].Box.Extents.x;
for (int j = 0; j < volume->queue()->size(); j++) for (int j = 0; j < volume->queue()->size(); j++)
{ {
auto state = volume->queue()->Get(j); auto state = volume->queue()->Get(j);
room->triggerVolumes[number].StateQueue.push_back( room->TriggerVolumes[number].StateQueue.push_back(
VolumeState VolumeState
{ {
(VolumeStateStatus)state->status(), (VolumeStateStatus)state->status(),

View file

@ -1,4 +1,5 @@
#pragma once #pragma once
#include "Math/Constants.h" #include "Math/Constants.h"
constexpr auto FP_SHIFT = 16; constexpr auto FP_SHIFT = 16;
@ -8,14 +9,19 @@ constexpr auto PREDICTIVE_SCALE_FACTOR = 14;
constexpr auto SHORTS_TO_1_DEGREE = 65536.0f / 360.0f; constexpr auto SHORTS_TO_1_DEGREE = 65536.0f / 360.0f;
constexpr auto DEGREES_TO_1_SHORT = 360.0f / 65536.0f; constexpr auto DEGREES_TO_1_SHORT = 360.0f / 65536.0f;
constexpr float ROUND(float value)
{
return ((value > 0.0f) ? int(value + 0.5f) : int(value - 0.5f));
}
constexpr short ANGLE(float degrees) constexpr short ANGLE(float degrees)
{ {
return short(degrees * SHORTS_TO_1_DEGREE); return (short)ROUND(degrees * SHORTS_TO_1_DEGREE);
} }
constexpr short FROM_RAD(float radians) constexpr short FROM_RAD(float radians)
{ {
return short((radians / RADIAN) * SHORTS_TO_1_DEGREE); return (short)ROUND((radians / RADIAN) * SHORTS_TO_1_DEGREE);
} }
constexpr float TO_DEGREES(short shortAngle) constexpr float TO_DEGREES(short shortAngle)

View file

@ -77,7 +77,7 @@ namespace TEN::Entities::Doors
xOffset = BLOCK(1); xOffset = BLOCK(1);
auto* r = &g_Level.Rooms[doorItem->RoomNumber]; auto* r = &g_Level.Rooms[doorItem->RoomNumber];
doorData->d1.floor = GetSector(r, doorItem->Pose.Position.x - r->x + xOffset, doorItem->Pose.Position.z - r->z + zOffset); doorData->d1.floor = GetSector(r, doorItem->Pose.Position.x - r->Position.x + xOffset, doorItem->Pose.Position.z - r->Position.z + zOffset);
auto roomNumber = doorData->d1.floor->SidePortalRoomNumber; auto roomNumber = doorData->d1.floor->SidePortalRoomNumber;
if (roomNumber == NO_VALUE) if (roomNumber == NO_VALUE)
@ -85,7 +85,7 @@ namespace TEN::Entities::Doors
else else
{ {
auto* b = &g_Level.Rooms[roomNumber]; auto* b = &g_Level.Rooms[roomNumber];
boxNumber = GetSector(b, doorItem->Pose.Position.x - b->x + xOffset, doorItem->Pose.Position.z - b->z + zOffset)->PathfindingBoxID; boxNumber = GetSector(b, doorItem->Pose.Position.x - b->Position.x + xOffset, doorItem->Pose.Position.z - b->Position.z + zOffset)->PathfindingBoxID;
} }
doorData->d1.block = (boxNumber != NO_VALUE && g_Level.PathfindingBoxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE; doorData->d1.block = (boxNumber != NO_VALUE && g_Level.PathfindingBoxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE;
@ -94,7 +94,7 @@ namespace TEN::Entities::Doors
if (r->flippedRoom != -1) if (r->flippedRoom != -1)
{ {
r = &g_Level.Rooms[r->flippedRoom]; r = &g_Level.Rooms[r->flippedRoom];
doorData->d1flip.floor = GetSector(r, doorItem->Pose.Position.x - r->x + xOffset, doorItem->Pose.Position.z - r->z + zOffset); doorData->d1flip.floor = GetSector(r, doorItem->Pose.Position.x - r->Position.x + xOffset, doorItem->Pose.Position.z - r->Position.z + zOffset);
roomNumber = doorData->d1flip.floor->SidePortalRoomNumber; roomNumber = doorData->d1flip.floor->SidePortalRoomNumber;
if (roomNumber == NO_VALUE) if (roomNumber == NO_VALUE)
@ -102,7 +102,7 @@ namespace TEN::Entities::Doors
else else
{ {
auto* b = &g_Level.Rooms[roomNumber]; auto* b = &g_Level.Rooms[roomNumber];
boxNumber = GetSector(b, doorItem->Pose.Position.x - b->x + xOffset, doorItem->Pose.Position.z - b->z + zOffset)->PathfindingBoxID; boxNumber = GetSector(b, doorItem->Pose.Position.x - b->Position.x + xOffset, doorItem->Pose.Position.z - b->Position.z + zOffset)->PathfindingBoxID;
} }
doorData->d1flip.block = (boxNumber != NO_VALUE && g_Level.PathfindingBoxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE; doorData->d1flip.block = (boxNumber != NO_VALUE && g_Level.PathfindingBoxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE;
@ -124,7 +124,7 @@ namespace TEN::Entities::Doors
else else
{ {
r = &g_Level.Rooms[twoRoom]; r = &g_Level.Rooms[twoRoom];
doorData->d2.floor = GetSector(r, doorItem->Pose.Position.x - r->x, doorItem->Pose.Position.z - r->z); doorData->d2.floor = GetSector(r, doorItem->Pose.Position.x - r->Position.x, doorItem->Pose.Position.z - r->Position.z);
roomNumber = doorData->d2.floor->SidePortalRoomNumber; roomNumber = doorData->d2.floor->SidePortalRoomNumber;
if (roomNumber == NO_VALUE) if (roomNumber == NO_VALUE)
@ -132,7 +132,7 @@ namespace TEN::Entities::Doors
else else
{ {
auto* b = &g_Level.Rooms[roomNumber]; auto* b = &g_Level.Rooms[roomNumber];
boxNumber = GetSector(b, doorItem->Pose.Position.x - b->x, doorItem->Pose.Position.z - b->z)->PathfindingBoxID; boxNumber = GetSector(b, doorItem->Pose.Position.x - b->Position.x, doorItem->Pose.Position.z - b->Position.z)->PathfindingBoxID;
} }
doorData->d2.block = (boxNumber != NO_VALUE && g_Level.PathfindingBoxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE; doorData->d2.block = (boxNumber != NO_VALUE && g_Level.PathfindingBoxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE;
@ -141,7 +141,7 @@ namespace TEN::Entities::Doors
if (r->flippedRoom != -1) if (r->flippedRoom != -1)
{ {
r = &g_Level.Rooms[r->flippedRoom]; r = &g_Level.Rooms[r->flippedRoom];
doorData->d2flip.floor = GetSector(r, doorItem->Pose.Position.x - r->x, doorItem->Pose.Position.z - r->z); doorData->d2flip.floor = GetSector(r, doorItem->Pose.Position.x - r->Position.x, doorItem->Pose.Position.z - r->Position.z);
roomNumber = doorData->d2flip.floor->SidePortalRoomNumber; roomNumber = doorData->d2flip.floor->SidePortalRoomNumber;
if (roomNumber == NO_VALUE) if (roomNumber == NO_VALUE)
@ -149,7 +149,7 @@ namespace TEN::Entities::Doors
else else
{ {
auto* b = &g_Level.Rooms[roomNumber]; auto* b = &g_Level.Rooms[roomNumber];
boxNumber = GetSector(b, doorItem->Pose.Position.x - b->x, doorItem->Pose.Position.z - b->z)->PathfindingBoxID; boxNumber = GetSector(b, doorItem->Pose.Position.x - b->Position.x, doorItem->Pose.Position.z - b->Position.z)->PathfindingBoxID;
} }
doorData->d2flip.block = (boxNumber != NO_VALUE && g_Level.PathfindingBoxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE; doorData->d2flip.block = (boxNumber != NO_VALUE && g_Level.PathfindingBoxes[boxNumber].flags & BLOCKABLE) ? boxNumber : NO_VALUE;

View file

@ -315,7 +315,7 @@ namespace TEN::Entities::Generic
waterHeight = pointColl.GetWaterSurfaceHeight(); waterHeight = pointColl.GetWaterSurfaceHeight();
if (waterHeight == NO_HEIGHT && TestEnvironment(ENV_FLAG_SWAMP, item.RoomNumber)) if (waterHeight == NO_HEIGHT && TestEnvironment(ENV_FLAG_SWAMP, item.RoomNumber))
waterHeight = g_Level.Rooms[item.RoomNumber].maxceiling; waterHeight = g_Level.Rooms[item.RoomNumber].TopHeight;
AddPushableBridge(item); AddPushableBridge(item);
} }
@ -324,7 +324,7 @@ namespace TEN::Entities::Generic
waterHeight = pointColl.GetWaterSurfaceHeight(); waterHeight = pointColl.GetWaterSurfaceHeight();
if (waterHeight == NO_HEIGHT && TestEnvironment(ENV_FLAG_SWAMP, item.RoomNumber)) if (waterHeight == NO_HEIGHT && TestEnvironment(ENV_FLAG_SWAMP, item.RoomNumber))
waterHeight = g_Level.Rooms[item.RoomNumber].maxceiling; waterHeight = g_Level.Rooms[item.RoomNumber].TopHeight;
} }
auto pushableColl = PushableCollisionData{}; auto pushableColl = PushableCollisionData{};

View file

@ -187,8 +187,8 @@ namespace TEN::Entities::Generic
ForcedFixedCamera.x = trapDoorItem->Pose.Position.x - phd_sin(trapDoorItem->Pose.Orientation.y) * 2048; ForcedFixedCamera.x = trapDoorItem->Pose.Position.x - phd_sin(trapDoorItem->Pose.Orientation.y) * 2048;
ForcedFixedCamera.y = trapDoorItem->Pose.Position.y - 2048; ForcedFixedCamera.y = trapDoorItem->Pose.Position.y - 2048;
if (ForcedFixedCamera.y < g_Level.Rooms[trapDoorItem->RoomNumber].maxceiling) if (ForcedFixedCamera.y < g_Level.Rooms[trapDoorItem->RoomNumber].TopHeight)
ForcedFixedCamera.y = g_Level.Rooms[trapDoorItem->RoomNumber].maxceiling; ForcedFixedCamera.y = g_Level.Rooms[trapDoorItem->RoomNumber].TopHeight;
ForcedFixedCamera.z = trapDoorItem->Pose.Position.z - phd_cos(trapDoorItem->Pose.Orientation.y) * 2048; ForcedFixedCamera.z = trapDoorItem->Pose.Position.z - phd_cos(trapDoorItem->Pose.Orientation.y) * 2048;
ForcedFixedCamera.RoomNumber = trapDoorItem->RoomNumber; ForcedFixedCamera.RoomNumber = trapDoorItem->RoomNumber;

View file

@ -0,0 +1,182 @@
#include "framework.h"
#include "Objects/TR1/Entity/Centaur.h"
#include "Game/animation.h"
#include "Game/control/box.h"
#include "Game/collision/collide_item.h"
#include "Game/collision/collide_room.h"
#include "Game/effects/effects.h"
#include "Game/effects/tomb4fx.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_one_gun.h"
#include "Game/misc.h"
#include "Game/missile.h"
#include "Game/people.h"
#include "Game/Setup.h"
#include "Math/Math.h"
#include "Sound/sound.h"
#include "Specific/level.h"
using namespace TEN::Math;
namespace TEN::Entities::Creatures::TR1
{
constexpr auto CENTAUR_REAR_DAMAGE = 200;
constexpr auto CENTAUR_REAR_RANGE = SQUARE(BLOCK(3 / 2.0f));
constexpr auto CENTAUR_REAR_CHANCE = 1 / 340.0f;
constexpr auto CENTAUR_BOMB_VELOCITY = CLICK(1);
constexpr auto CENTAUR_TURN_RATE_MAX = ANGLE(4.0f);
const auto CentaurRocketBite = CreatureBiteInfo(Vector3(11, 415, 41), 13);
const auto CentaurRearBite = CreatureBiteInfo(Vector3(50, 30, 0), 5);
const auto CentaurAttackJoints = std::vector<unsigned int>{ 0, 3, 4, 7, 8, 16, 17 };
enum CentaurState
{
// No state 0.
CENTAUR_STATE_IDLE = 1,
CENTAUR_PROJECTILE_ATTACK = 2,
CENTAUR_STATE_RUN_FORWARD = 3,
CENTAUR_STATE_AIM = 4,
CENTAUR_STATE_DEATH = 5,
CENTAUR_STATE_WARNING = 6
};
// TODO
enum CentaurAnim
{
CENTAUR_ANIM_DEATH = 8,
};
void ControlCentaur(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto& item = g_Level.Items[itemNumber];
auto& creature = *GetCreatureInfo(&item);
short headingAngle = 0;
auto headOrient = EulerAngles::Identity;
auto torsoOrient = EulerAngles::Identity;
if (item.HitPoints <= 0)
{
if (item.Animation.ActiveState != CENTAUR_STATE_DEATH)
SetAnimation(item, CENTAUR_ANIM_DEATH);
}
else
{
AI_INFO ai;
CreatureAIInfo(&item, &ai);
if (ai.ahead)
{
headOrient.x = ai.xAngle;
headOrient.y = ai.angle;
torsoOrient.x = ai.xAngle / 2;
torsoOrient.y = ai.angle / 2;
}
GetCreatureMood(&item, &ai, true);
CreatureMood(&item, &ai, true);
headingAngle = CreatureTurn(&item, CENTAUR_TURN_RATE_MAX);
switch (item.Animation.ActiveState)
{
case CENTAUR_STATE_IDLE:
if (item.Animation.RequiredState != NO_VALUE)
{
item.Animation.TargetState = item.Animation.RequiredState;
}
else if (ai.bite && ai.distance < CENTAUR_REAR_RANGE)
{
item.Animation.TargetState = CENTAUR_STATE_RUN_FORWARD;
}
else if (Targetable(&item, &ai))
{
item.Animation.TargetState = CENTAUR_STATE_AIM;
}
else
{
item.Animation.TargetState = CENTAUR_STATE_RUN_FORWARD;
}
break;
case CENTAUR_STATE_RUN_FORWARD:
torsoOrient = EulerAngles::Identity;
if (ai.bite && ai.distance < CENTAUR_REAR_RANGE)
{
item.Animation.TargetState = CENTAUR_STATE_IDLE;
item.Animation.RequiredState = CENTAUR_STATE_WARNING;
}
else if (Targetable(&item, &ai))
{
item.Animation.TargetState = CENTAUR_STATE_IDLE;
item.Animation.RequiredState = CENTAUR_STATE_AIM;
}
else if (Random::TestProbability(CENTAUR_REAR_CHANCE))
{
item.Animation.TargetState = CENTAUR_STATE_IDLE;
item.Animation.RequiredState = CENTAUR_STATE_WARNING;
}
break;
case CENTAUR_STATE_AIM:
if (item.Animation.RequiredState != NO_VALUE)
{
item.Animation.TargetState = item.Animation.RequiredState;
}
else if (Targetable(&item, &ai))
{
item.Animation.TargetState = CENTAUR_PROJECTILE_ATTACK;
}
else
{
item.Animation.TargetState = CENTAUR_STATE_IDLE;
}
break;
case CENTAUR_PROJECTILE_ATTACK:
if (item.Animation.RequiredState == NO_VALUE)
{
item.Animation.RequiredState = CENTAUR_STATE_AIM;
CreatureEffect2(&item, CentaurRocketBite, CENTAUR_BOMB_VELOCITY, headOrient.y, BombGun);
}
break;
case CENTAUR_STATE_WARNING:
if (item.Animation.RequiredState == NO_VALUE &&
item.TouchBits.Test(CentaurAttackJoints))
{
DoDamage(creature.Enemy, CENTAUR_REAR_DAMAGE);
CreatureEffect(&item, CentaurRearBite, DoBloodSplat);
item.Animation.RequiredState = CENTAUR_STATE_IDLE;
}
break;
}
}
CreatureJoint(&item, 0, headOrient.y);
CreatureJoint(&item, 1, -headOrient.x);
CreatureJoint(&item, 2, torsoOrient.y);
CreatureJoint(&item, 3, -torsoOrient.x);
CreatureAnimation(itemNumber, headingAngle, 0);
if (item.Status == ITEM_DEACTIVATED)
{
SoundEffect(SFX_TR1_ATLANTEAN_DEATH, &item.Pose);
ExplodingDeath(itemNumber, BODY_DO_EXPLOSION);
KillItem(itemNumber);
item.Status = ITEM_DEACTIVATED;
}
}
}

View file

@ -5,5 +5,5 @@ namespace TEN::Entities::Creatures::TR1
constexpr auto SHARD_VELOCITY = 250; constexpr auto SHARD_VELOCITY = 250;
constexpr auto BOMB_VELOCITY = 220; constexpr auto BOMB_VELOCITY = 220;
void CentaurControl(short itemNumber); void ControlCentaur(short itemNumber);
} }

View file

@ -0,0 +1,573 @@
#include "framework.h"
#include "Objects/TR1/Entity/WingedMutant.h"
#include "Game/collision/collide_room.h"
#include "Game/control/box.h"
#include "Game/control/lot.h"
#include "Game/effects/effects.h"
#include "Game/effects/tomb4fx.h"
#include "Game/itemdata/creature_info.h"
#include "Game/items.h"
#include "Game/misc.h"
#include "Game/missile.h"
#include "Game/people.h"
#include "Math/Math.h"
#include "Sound/sound.h"
#include "Specific/level.h"
using namespace TEN::Math;
namespace TEN::Entities::Creatures::TR1
{
constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE = 150;
constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE = 100;
constexpr auto WINGED_MUTANT_SWIPE_ATTACK_DAMAGE = 200;
constexpr auto WINGED_MUTANT_WALK_RANGE = SQUARE(BLOCK(4.5f));
constexpr auto WINGED_MUTANT_SWIPE_ATTACK_RANGE = SQUARE(BLOCK(0.3f));
constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_RANGE = SQUARE(BLOCK(0.65f));
constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE = SQUARE(BLOCK(2.5f));
constexpr auto WINGED_MUTANT_PROJECTILE_ATTACK_RANGE = SQUARE(BLOCK(3.0f));
constexpr auto WINGED_MUTANT_POSE_RANGE = SQUARE(BLOCK(4.5f));
constexpr auto WINGED_MUTANT_POSE_CHANCE = 1 / 400.0f;
constexpr auto WINGED_MUTANT_UNPOSE_CHANCE = 1 / 164.0f;
constexpr auto WINGED_MUTANT_FLY_VELOCITY = BLOCK(1 / 32.0f);
constexpr auto WINGED_MUTANT_SHARD_VELOCITY = 250;
constexpr auto WINGED_MUTANT_BOMB_VELOCITY = 220;
constexpr auto WINGED_MUTANT_WALK_TURN_RATE_MAX = ANGLE(2.0f);
constexpr auto WINGED_MUTANT_RUN_TURN_RATE_MAX = ANGLE(6.0f);
const auto WingedMutantBiteLeftHand = CreatureBiteInfo(Vector3(0, 0, 0), 7);
const auto WingedMutantBiteRightHand = CreatureBiteInfo(Vector3(0, 0, 0), 10);
const auto WingedMutantRocketBite = CreatureBiteInfo(Vector3(0, 200, 20), 6);
const auto WingedMutantShardBite = CreatureBiteInfo(Vector3(0, 200, 20), 9);
const auto WingedMutantHeadJoints = std::vector<unsigned int>{ 3 };
const auto WingedMutantHandsJoints = std::vector<unsigned int>{ 7, 10 };
const auto WingedMutantWingsJoints = std::vector<unsigned int>{ 15, 16, 17, 18, 19, 20 };
enum WingedMutantState
{
// No state 0.
WMUTANT_STATE_IDLE = 1,
WMUTANT_STATE_WALK_FORWARD = 2,
WMUTANT_STATE_RUN_FORWARD = 3,
WMUTANT_STATE_IDLE_JUMP_ATTACK = 4,
WMUTANT_STATE_DEATH = 5,
WMUTANT_STATE_POSE = 6,
WMUTANT_STATE_RUN_JUMP_ATTACK = 7,
WMUTANT_STATE_SWIPE_ATTACK = 8,
WMUTANT_STATE_AIM_DART = 9,
WMUTANT_STATE_AIM_BOMB = 10,
WMUTANT_STATE_SHOOT = 11,
WMUTANT_STATE_INACTIVE = 12,
WMUTANT_STATE_FLY = 13,
};
enum WingedMutantAnim
{
WMUTANT_ANIM_INACTIVE = 0,
WMUTANT_ANIM_INACTIVE_TO_IDLE = 1,
WMUTANT_ANIM_IDLE = 2,
WMUTANT_ANIM_IDLE_TO_RUN = 3,
WMUTANT_ANIM_RUN_FORWARD = 4,
WMUTANT_ANIM_IDLE_JUMP_ATTACK_START = 5,
WMUTANT_ANIM_IDLE_JUMP_ATTACK_END = 6,
WMUTANT_ANIM_IDLE_TO_POSE = 7,
WMUTANT_ANIM_POSE = 8,
WMUTANT_ANIM_POSE_TO_IDLE = 9,
WMUTANT_ANIM_POSE_TO_WALK_FORWARD = 10,
WMUTANT_ANIM_WALK_FORWARD = 11,
WMUTANT_ANIM_WALK_FORWARD_TO_IDLE = 12,
WMUTANT_ANIM_WALK_FORWARD_TO_POSE = 13,
WMUTANT_ANIM_RUN_JUMP_ATTACK = 14,
WMUTANT_ANIM_IDLE_TO_AIM_1 = 15,
WMUTANT_ANIM_AIM_DART = 16,
WMUTANT_ANIM_SHOOT_DART = 17,
WMUTANT_ANIM_AIM_DART_TO_IDLE = 18,
WMUTANT_ANIM_IDLE_TO_AIM_BOMB = 19,
WMUTANT_ANIM_SHOOT_BOMB = 20,
WMUTANT_ANIM_RUN_FORWARD_TO_IDLE = 21,
WMUTANT_ANIM_AIM_BOMB_TO_IDLE = 22,
WMUTANT_ANIM_IDLE_TO_FLY = 23,
WMUTANT_ANIM_FLY = 24,
WMUTANT_ANIM_FLY_TO_IDLE = 25,
WMUTANT_ANIM_SWIPE_ATTACK = 26
};
enum WingedMutantPathFinding
{
WMUTANT_PATH_GROUND = 1,
WMUTANT_PATH_AERIAL = 2
};
// NOTE: Originally, winged mutants did not have OCBs. -- TokyoSU 5/8/2022
enum WingedMutantOcb
{
WMUTANT_OCB_START_AERIAL = (1 << 0),
WMUTANT_OCB_START_INACTIVE = (1 << 1),
WMUTANT_OCB_START_POSE = (1 << 2),
WMUTANT_OCB_NO_WINGS = (1 << 3),
WMUTANT_OCB_DISABLE_DART_WEAPON = (1 << 4),
WMUTANT_OCB_DISABLE_BOMB_WEAPON = (1 << 5)
};
enum WingedMutantProjectileType
{
WMUTANT_PROJ_NONE,
WMUTANT_PROJ_DART,
WMUTANT_PROJ_BOMB
};
enum WingedMutantConfig
{
WMUTANT_CONF_CAN_FLY,
WMUTANT_CONF_PATHFINDING_MODE,
WMUTANT_CONF_PROJECTILE_MODE,
WMUTANT_CONF_NO_WINGS,
WMUTANT_CONF_DISABLE_DART_WEAPON,
WMUTANT_CONF_DISABLE_BOMB_WEAPON
};
static void SwitchPathfinding(ItemInfo& item, WingedMutantPathFinding path)
{
auto& creature = *GetCreatureInfo(&item);
switch (path)
{
case WMUTANT_PATH_GROUND:
creature.LOT.Step = CLICK(1);
creature.LOT.Drop = -CLICK(1);
creature.LOT.Fly = NO_FLYING;
creature.LOT.Zone = ZoneType::Basic;
break;
case WMUTANT_PATH_AERIAL:
creature.LOT.Step = BLOCK(20);
creature.LOT.Drop = -BLOCK(20);
creature.LOT.Fly = WINGED_MUTANT_FLY_VELOCITY;
creature.LOT.Zone = ZoneType::Flyer;
break;
}
}
static WingedMutantProjectileType CanTargetPlayer(ItemInfo& item, AI_INFO& ai)
{
if (Targetable(&item, &ai) &&
(ai.zoneNumber != ai.enemyZone || ai.distance > WINGED_MUTANT_PROJECTILE_ATTACK_RANGE))
{
if ((ai.angle > 0 && ai.angle < ANGLE(45.0f)) &&
item.TestFlagField(WMUTANT_CONF_DISABLE_DART_WEAPON, false))
{
return WMUTANT_PROJ_DART;
}
else if ((ai.angle < 0 && ai.angle > ANGLE(-45.0f)) &&
item.TestFlagField(WMUTANT_CONF_DISABLE_BOMB_WEAPON, false))
{
return WMUTANT_PROJ_BOMB;
}
}
// Cannot be targeted.
return WMUTANT_PROJ_NONE;
}
static void InitializeWingedMutantOCB(ItemInfo& item)
{
if (item.TestOcb(WMUTANT_OCB_START_AERIAL))
{
SwitchPathfinding(item, WMUTANT_PATH_AERIAL);
SetAnimation(item, WMUTANT_ANIM_FLY);
item.SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
}
else if (item.TestOcb(WMUTANT_OCB_START_INACTIVE))
{
SwitchPathfinding(item, WMUTANT_PATH_GROUND);
SetAnimation(item, WMUTANT_ANIM_INACTIVE);
item.SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
}
else if (item.TestOcb(WMUTANT_OCB_START_POSE))
{
SwitchPathfinding(item, WMUTANT_PATH_GROUND);
SetAnimation(item, WMUTANT_ANIM_INACTIVE);
item.SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
}
// Remove unnecessary OCBs.
if (item.TestOcb(WMUTANT_OCB_START_AERIAL))
item.RemoveOcb(WMUTANT_OCB_START_AERIAL);
if (item.TestOcb(WMUTANT_OCB_START_INACTIVE))
item.RemoveOcb(WMUTANT_OCB_START_INACTIVE);
if (item.TestOcb(WMUTANT_OCB_START_POSE))
item.RemoveOcb(WMUTANT_OCB_START_POSE);
}
void InitializeWingedMutant(short itemNumber)
{
auto& item = g_Level.Items[itemNumber];
InitializeCreature(itemNumber);
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.SetFlagField(WMUTANT_CONF_CAN_FLY, false);
item.MeshBits.Clear(WingedMutantWingsJoints);
}
else
{
item.SetFlagField(WMUTANT_CONF_CAN_FLY, true);
}
if (item.TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON))
item.SetFlagField(WMUTANT_CONF_DISABLE_BOMB_WEAPON, true);
if (item.TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON))
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);
}
void ControlWingedMutant(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto& item = g_Level.Items[itemNumber];
auto& creature = *GetCreatureInfo(&item);
short headingAngle = 0;
short headYOrient = 0;
short torsoYOrient = 0; // Only when shooting.
bool enableFlying = item.TestFlagField(WMUTANT_CONF_CAN_FLY, true);
bool isFlying = item.TestFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
InitializeWingedMutantOCB(item);
if (item.HitPoints <= 0)
{
CreatureDie(itemNumber, true, BODY_DO_EXPLOSION | BODY_PART_EXPLODE | BODY_NO_SMOKE | BODY_NO_SHATTER_EFFECT);
auto pos = item.Pose;
pos.Position.y -= CLICK(3);
TriggerExplosionSparks(pos.Position.x, pos.Position.y, pos.Position.z, 3, -2, 0, item.RoomNumber);
TriggerExplosionSparks(pos.Position.x, pos.Position.y, pos.Position.z, 3, -1, 0, item.RoomNumber);
TriggerShockwave(&pos, 48, 304, (GetRandomControl() & 0x1F) + 112, 128, 32, 32, 32, EulerAngles(2048, 0.0f, 0.0f), 0, true, false, false, (int)ShockwaveStyle::Normal);
SoundEffect(SFX_TR1_ATLANTEAN_EXPLODE, &item.Pose);
return;
}
else
{
AI_INFO ai;
SwitchPathfinding(item, WMUTANT_PATH_GROUND);
CreatureAIInfo(&item, &ai);
bool isSameZoneInGroundMode = (ai.zoneNumber == ai.enemyZone);
auto projectileType = CanTargetPlayer(item, ai);
if (enableFlying && item.Animation.ActiveState == WMUTANT_STATE_FLY)
{
SwitchPathfinding(item, WMUTANT_PATH_AERIAL);
CreatureAIInfo(&item, &ai);
}
if (ai.ahead)
{
headYOrient = ai.angle;
}
else
{
headYOrient = 0;
torsoYOrient = 0;
}
GetCreatureMood(&item, &ai, isFlying);
CreatureMood(&item, &ai, isFlying);
headingAngle = CreatureTurn(&item, creature.MaxTurn);
switch (item.Animation.ActiveState)
{
case WMUTANT_STATE_INACTIVE:
creature.MaxTurn = 0;
creature.Flags = 0;
if (TargetVisible(&item, &ai) || creature.HurtByLara)
item.Animation.TargetState = WMUTANT_STATE_IDLE;
break;
case WMUTANT_STATE_IDLE:
item.SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PROJ_NONE);
creature.MaxTurn = 0;
creature.Flags = 0;
torsoYOrient = 0;
if (enableFlying && !isSameZoneInGroundMode)
{
item.Animation.TargetState = WMUTANT_STATE_FLY;
item.SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
}
else if (item.TouchBits.Test(WingedMutantHeadJoints))
{
item.Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK;
}
else if (ai.bite && ai.distance < WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE)
{
item.Animation.TargetState = WMUTANT_STATE_IDLE_JUMP_ATTACK;
}
else if (ai.bite && ai.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE)
{
item.Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK;
}
else if (projectileType == WMUTANT_PROJ_DART)
{
item.Animation.TargetState = WMUTANT_STATE_AIM_DART;
}
else if (projectileType == WMUTANT_PROJ_BOMB)
{
item.Animation.TargetState = WMUTANT_STATE_AIM_BOMB;
}
else if (creature.Mood == MoodType::Bored ||
(creature.Mood == MoodType::Stalk && ai.distance < WINGED_MUTANT_POSE_RANGE))
{
item.Animation.TargetState = WMUTANT_STATE_POSE;
}
else
{
item.Animation.TargetState = WMUTANT_STATE_RUN_FORWARD;
}
break;
case WMUTANT_STATE_POSE:
creature.Flags = 0;
creature.MaxTurn = 0;
headYOrient = 0; // NOTE: Pose has animation for head.
if (projectileType != WMUTANT_PROJ_NONE || (isFlying && enableFlying))
{
item.Animation.TargetState = WMUTANT_STATE_IDLE;
}
else if (creature.Mood == MoodType::Stalk)
{
if (ai.distance < WINGED_MUTANT_WALK_RANGE)
{
if (isSameZoneInGroundMode ||
Random::TestProbability(WINGED_MUTANT_UNPOSE_CHANCE))
{
item.Animation.TargetState = WMUTANT_STATE_WALK_FORWARD;
}
}
else
{
item.Animation.TargetState = WMUTANT_STATE_IDLE;
}
}
else if (creature.Mood == MoodType::Bored && Random::TestProbability(WINGED_MUTANT_UNPOSE_CHANCE))
{
item.Animation.TargetState = WMUTANT_STATE_WALK_FORWARD;
}
else if (creature.Mood == MoodType::Attack ||
creature.Mood == MoodType::Escape)
{
item.Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_WALK_FORWARD:
creature.MaxTurn = WINGED_MUTANT_WALK_TURN_RATE_MAX;
creature.Flags = 0;
if (projectileType != WMUTANT_PROJ_NONE || (isFlying && enableFlying))
{
item.Animation.TargetState = WMUTANT_STATE_IDLE;
}
else if (creature.Mood == MoodType::Attack || creature.Mood == MoodType::Escape)
{
item.Animation.TargetState = WMUTANT_STATE_IDLE;
}
else if (creature.Mood == MoodType::Bored ||
(creature.Mood == MoodType::Stalk && !isSameZoneInGroundMode))
{
if (Random::TestProbability(WINGED_MUTANT_POSE_CHANCE))
item.Animation.TargetState = WMUTANT_STATE_POSE;
}
else if (creature.Mood == MoodType::Stalk &&
ai.distance > WINGED_MUTANT_WALK_RANGE)
{
item.Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_RUN_FORWARD:
creature.MaxTurn = WINGED_MUTANT_RUN_TURN_RATE_MAX;
creature.Flags = 0;
if (enableFlying && !isSameZoneInGroundMode)
{
item.Animation.TargetState = WMUTANT_STATE_IDLE;
}
else if (projectileType != WMUTANT_PROJ_NONE)
{
item.Animation.TargetState = WMUTANT_STATE_IDLE;
}
else if (item.TouchBits.Test(WingedMutantHeadJoints))
{
item.Animation.TargetState = WMUTANT_STATE_IDLE;
}
else if (ai.bite && ai.distance < WINGED_MUTANT_RUN_JUMP_ATTACK_RANGE)
{
item.Animation.TargetState = WMUTANT_STATE_RUN_JUMP_ATTACK;
}
else if (ai.bite && ai.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE)
{
item.Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK;
}
else if (ai.ahead && ai.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE)
{
item.Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK;
}
else if (creature.Mood == MoodType::Bored ||
(creature.Mood == MoodType::Stalk && ai.distance < WINGED_MUTANT_POSE_RANGE))
{
item.Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_IDLE_JUMP_ATTACK:
if (item.Animation.RequiredState == NO_VALUE &&
(item.TouchBits.Test(WingedMutantHandsJoints) || item.TouchBits.Test(WingedMutantHeadJoints)) && creature.Flags == 0)
{
DoDamage(creature.Enemy, WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE / 2);
CreatureEffect(&item, WingedMutantBiteLeftHand, DoBloodSplat);
DoDamage(creature.Enemy, WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE / 2);
CreatureEffect(&item, WingedMutantBiteRightHand, DoBloodSplat);
item.Animation.TargetState = WMUTANT_STATE_IDLE;
creature.Flags = 1;
}
break;
case WMUTANT_STATE_RUN_JUMP_ATTACK:
if (item.Animation.RequiredState == NO_VALUE &&
(item.TouchBits.Test(WingedMutantHandsJoints) || item.TouchBits.Test(WingedMutantHeadJoints)) && creature.Flags == 0)
{
DoDamage(creature.Enemy, WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE / 2);
CreatureEffect(&item, WingedMutantBiteLeftHand, DoBloodSplat);
DoDamage(creature.Enemy, WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE / 2);
CreatureEffect(&item, WingedMutantBiteRightHand, DoBloodSplat);
item.Animation.TargetState = WMUTANT_STATE_RUN_FORWARD;
creature.Flags = 1;
}
break;
case WMUTANT_STATE_SWIPE_ATTACK:
if (item.Animation.RequiredState == NO_VALUE &&
item.TouchBits.Test(WingedMutantHandsJoints) && creature.Flags == 0)
{
DoDamage(creature.Enemy, WINGED_MUTANT_SWIPE_ATTACK_DAMAGE / 2);
CreatureEffect(&item, WingedMutantBiteLeftHand, DoBloodSplat);
DoDamage(creature.Enemy, WINGED_MUTANT_SWIPE_ATTACK_DAMAGE / 2);
CreatureEffect(&item, WingedMutantBiteRightHand, DoBloodSplat);
item.Animation.TargetState = WMUTANT_STATE_IDLE;
creature.Flags = 1;
}
break;
case WMUTANT_STATE_AIM_DART:
item.SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART);
creature.MaxTurn = 0;
creature.Flags = 0;
torsoYOrient = ai.angle / 2;
if (projectileType == WMUTANT_PROJ_DART)
{
item.Animation.TargetState = WMUTANT_STATE_SHOOT;
}
else
{
item.Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_AIM_BOMB:
item.SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB);
creature.MaxTurn = 0;
creature.Flags = 0;
torsoYOrient = ai.angle / 2;
if (projectileType == WMUTANT_PROJ_BOMB)
{
item.Animation.TargetState = WMUTANT_STATE_SHOOT;
}
else
{
item.Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_SHOOT:
creature.MaxTurn = 0;
torsoYOrient = ai.angle / 2;
if (creature.Flags == 0)
{
if (projectileType == WMUTANT_PROJ_DART)
{
CreatureEffect2(&item, WingedMutantShardBite, WINGED_MUTANT_SHARD_VELOCITY, torsoYOrient, ShardGun);
}
else if (projectileType == WMUTANT_PROJ_BOMB)
{
CreatureEffect2(&item, WingedMutantRocketBite, WINGED_MUTANT_BOMB_VELOCITY, torsoYOrient, BombGun);
}
creature.Flags = 1;
}
item.SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE);
break;
case WMUTANT_STATE_FLY:
if (creature.Mood != MoodType::Escape && isSameZoneInGroundMode)
{
item.Animation.TargetState = WMUTANT_STATE_IDLE; // Switch to ground mode.
item.Pose.Position.y = item.Floor;
item.SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
}
break;
}
}
CreatureJoint(&item, 0, torsoYOrient);
CreatureJoint(&item, 1, headYOrient);
CreatureAnimation(itemNumber, headingAngle, 0);
}
}

View file

@ -3,6 +3,5 @@
namespace TEN::Entities::Creatures::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void InitializeWingedMutant(short itemNumber); void InitializeWingedMutant(short itemNumber);
void WingedMutantControl(short itemNumber); void ControlWingedMutant(short itemNumber);
} }

View file

@ -1,158 +0,0 @@
#include "framework.h"
#include "Objects/TR1/Entity/tr1_centaur.h"
#include "Game/animation.h"
#include "Game/control/box.h"
#include "Game/collision/collide_item.h"
#include "Game/collision/collide_room.h"
#include "Game/effects/effects.h"
#include "Game/effects/tomb4fx.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_one_gun.h"
#include "Game/misc.h"
#include "Game/missile.h"
#include "Game/people.h"
#include "Game/Setup.h"
#include "Math/Math.h"
#include "Sound/sound.h"
#include "Specific/level.h"
using namespace TEN::Math;
namespace TEN::Entities::Creatures::TR1
{
constexpr auto CENTAUR_REAR_DAMAGE = 200;
constexpr auto CENTAUR_REAR_RANGE = BLOCK(3 / 2.0f);
constexpr auto CENTAUR_REAR_CHANCE = 1 / 340.0f;
constexpr auto CENTAUR_BOMB_VELOCITY = CLICK(1);
constexpr auto CENTAUR_TURN_RATE_MAX = ANGLE(4.0f);
const auto CentaurRocketBite = CreatureBiteInfo(Vector3(11, 415, 41), 13);
const auto CentaurRearBite = CreatureBiteInfo(Vector3(50, 30, 0), 5);
const auto CentaurAttackJoints = std::vector<unsigned int>{ 0, 3, 4, 7, 8, 16, 17 };
enum CentaurState
{
// No state 0.
CENTAUR_STATE_IDLE = 1,
CENTAUR_PROJECTILE_ATTACK = 2,
CENTAUR_STATE_RUN_FORWARD = 3,
CENTAUR_STATE_AIM = 4,
CENTAUR_STATE_DEATH = 5,
CENTAUR_STATE_WARNING = 6
};
// TODO
enum CentaurAnim
{
CENTAUR_ANIM_DEATH = 8,
};
void CentaurControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
short head = 0;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != CENTAUR_STATE_DEATH)
SetAnimation(item, CENTAUR_ANIM_DEATH);
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
CreatureMood(item, &AI, true);
angle = CreatureTurn(item, CENTAUR_TURN_RATE_MAX);
switch (item->Animation.ActiveState)
{
case CENTAUR_STATE_IDLE:
CreatureJoint(item, 17, 0);
if (item->Animation.RequiredState != NO_VALUE)
item->Animation.TargetState = item->Animation.RequiredState;
else if (AI.bite && AI.distance < pow(CENTAUR_REAR_RANGE, 2))
item->Animation.TargetState = CENTAUR_STATE_RUN_FORWARD;
else if (Targetable(item, &AI))
item->Animation.TargetState = CENTAUR_STATE_AIM;
else
item->Animation.TargetState = CENTAUR_STATE_RUN_FORWARD;
break;
case CENTAUR_STATE_RUN_FORWARD:
if (AI.bite && AI.distance < pow(CENTAUR_REAR_RANGE, 2))
{
item->Animation.TargetState = CENTAUR_STATE_IDLE;
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
}
else if (Targetable(item, &AI))
{
item->Animation.TargetState = CENTAUR_STATE_IDLE;
item->Animation.RequiredState = CENTAUR_STATE_AIM;
}
else if (Random::TestProbability(CENTAUR_REAR_CHANCE))
{
item->Animation.TargetState = CENTAUR_STATE_IDLE;
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
}
break;
case CENTAUR_STATE_AIM:
if (item->Animation.RequiredState != NO_VALUE)
item->Animation.TargetState = item->Animation.RequiredState;
else if (Targetable(item, &AI))
item->Animation.TargetState = CENTAUR_PROJECTILE_ATTACK;
else
item->Animation.TargetState = CENTAUR_STATE_IDLE;
break;
case CENTAUR_PROJECTILE_ATTACK:
if (item->Animation.RequiredState == NO_VALUE)
{
item->Animation.RequiredState = CENTAUR_STATE_AIM;
CreatureEffect2(item, CentaurRocketBite, CENTAUR_BOMB_VELOCITY, head, BombGun);
}
break;
case CENTAUR_STATE_WARNING:
if (item->Animation.RequiredState == NO_VALUE &&
item->TouchBits.Test(CentaurAttackJoints))
{
DoDamage(creature->Enemy, CENTAUR_REAR_DAMAGE);
CreatureEffect(item, CentaurRearBite, DoBloodSplat);
item->Animation.RequiredState = CENTAUR_STATE_IDLE;
}
break;
}
}
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
if (item->Status == ITEM_DEACTIVATED)
{
SoundEffect(SFX_TR1_ATLANTEAN_DEATH, &item->Pose);
ExplodingDeath(itemNumber, BODY_DO_EXPLOSION);
KillItem(itemNumber);
item->Status = ITEM_DEACTIVATED;
}
}
}

View file

@ -1,571 +0,0 @@
#include "framework.h"
#include "Objects/TR1/Entity/tr1_winged_mutant.h"
#include "Game/collision/collide_room.h"
#include "Game/control/box.h"
#include "Game/control/lot.h"
#include "Game/effects/effects.h"
#include "Game/effects/tomb4fx.h"
#include "Game/itemdata/creature_info.h"
#include "Game/items.h"
#include "Game/misc.h"
#include "Game/missile.h"
#include "Game/people.h"
#include "Math/Math.h"
#include "Sound/sound.h"
#include "Specific/level.h"
using namespace TEN::Math;
namespace TEN::Entities::Creatures::TR1
{
constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE = 150;
constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE = 100;
constexpr auto WINGED_MUTANT_SWIPE_ATTACK_DAMAGE = 200;
constexpr auto WINGED_MUTANT_WALK_RANGE = SQUARE(BLOCK(4.5f));
constexpr auto WINGED_MUTANT_SWIPE_ATTACK_RANGE = SQUARE(BLOCK(0.3f));
constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_RANGE = SQUARE(BLOCK(0.65f));
constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE = SQUARE(BLOCK(2.5f));
constexpr auto WINGED_MUTANT_PROJECTILE_ATTACK_RANGE = SQUARE(BLOCK(3.0f));
constexpr auto WINGED_MUTANT_POSE_RANGE = SQUARE(BLOCK(4.5f));
constexpr auto WINGED_MUTANT_POSE_CHANCE = 1 / 400.0f;
constexpr auto WINGED_MUTANT_UNPOSE_CHANCE = 1 / 164.0f;
constexpr auto WINGED_MUTANT_FLY_VELOCITY = BLOCK(1 / 32.0f);
constexpr auto WINGED_MUTANT_SHARD_VELOCITY = 250;
constexpr auto WINGED_MUTANT_BOMB_VELOCITY = 220;
constexpr auto WINGED_MUTANT_WALK_TURN_RATE_MAX = ANGLE(2.0f);
constexpr auto WINGED_MUTANT_RUN_TURN_RATE_MAX = ANGLE(6.0f);
const auto WingedMutantBiteLeftHand = CreatureBiteInfo(Vector3(0, 0, 0), 7);
const auto WingedMutantBiteRightHand = CreatureBiteInfo(Vector3(0, 0, 0), 10);
const auto WingedMutantRocketBite = CreatureBiteInfo(Vector3(0, 200, 20), 6);
const auto WingedMutantShardBite = CreatureBiteInfo(Vector3(0, 200, 20), 9);
const auto WingedMutantHeadJoints = std::vector<unsigned int>{ 3 };
const auto WingedMutantHandsJoints = std::vector<unsigned int>{ 7, 10 };
const auto WingedMutantWingsJoints = std::vector<unsigned int>{ 15, 16, 17, 18, 19, 20 };
enum WingedMutantState
{
// No state 0.
WMUTANT_STATE_IDLE = 1,
WMUTANT_STATE_WALK_FORWARD = 2,
WMUTANT_STATE_RUN_FORWARD = 3,
WMUTANT_STATE_IDLE_JUMP_ATTACK = 4,
WMUTANT_STATE_DEATH = 5,
WMUTANT_STATE_POSE = 6,
WMUTANT_STATE_RUN_JUMP_ATTACK = 7,
WMUTANT_STATE_SWIPE_ATTACK = 8,
WMUTANT_STATE_AIM_DART = 9,
WMUTANT_STATE_AIM_BOMB = 10,
WMUTANT_STATE_SHOOT = 11,
WMUTANT_STATE_INACTIVE = 12,
WMUTANT_STATE_FLY = 13,
};
enum WingedMutantAnim
{
WMUTANT_ANIM_INACTIVE = 0,
WMUTANT_ANIM_INACTIVE_TO_IDLE = 1,
WMUTANT_ANIM_IDLE = 2,
WMUTANT_ANIM_IDLE_TO_RUN = 3,
WMUTANT_ANIM_RUN_FORWARD = 4,
WMUTANT_ANIM_IDLE_JUMP_ATTACK_START = 5,
WMUTANT_ANIM_IDLE_JUMP_ATTACK_END = 6,
WMUTANT_ANIM_IDLE_TO_POSE = 7,
WMUTANT_ANIM_POSE = 8,
WMUTANT_ANIM_POSE_TO_IDLE = 9,
WMUTANT_ANIM_POSE_TO_WALK_FORWARD = 10,
WMUTANT_ANIM_WALK_FORWARD = 11,
WMUTANT_ANIM_WALK_FORWARD_TO_IDLE = 12,
WMUTANT_ANIM_WALK_FORWARD_TO_POSE = 13,
WMUTANT_ANIM_RUN_JUMP_ATTACK = 14,
WMUTANT_ANIM_IDLE_TO_AIM_1 = 15,
WMUTANT_ANIM_AIM_DART = 16,
WMUTANT_ANIM_SHOOT_DART = 17,
WMUTANT_ANIM_AIM_DART_TO_IDLE = 18,
WMUTANT_ANIM_IDLE_TO_AIM_BOMB = 19,
WMUTANT_ANIM_SHOOT_BOMB = 20,
WMUTANT_ANIM_RUN_FORWARD_TO_IDLE = 21,
WMUTANT_ANIM_AIM_BOMB_TO_IDLE = 22,
WMUTANT_ANIM_IDLE_TO_FLY = 23,
WMUTANT_ANIM_FLY = 24,
WMUTANT_ANIM_FLY_TO_IDLE = 25,
WMUTANT_ANIM_SWIPE_ATTACK = 26
};
enum WingedMutantPathFinding
{
WMUTANT_PATH_GROUND = 1,
WMUTANT_PATH_AERIAL = 2
};
// NOTE: Originally, winged mutants did not have OCBs. -- TokyoSU 5/8/2022
enum WingedMutantOcb
{
WMUTANT_OCB_START_AERIAL = (1 << 0),
WMUTANT_OCB_START_INACTIVE = (1 << 1),
WMUTANT_OCB_START_POSE = (1 << 2),
WMUTANT_OCB_NO_WINGS = (1 << 3),
WMUTANT_OCB_DISABLE_DART_WEAPON = (1 << 4),
WMUTANT_OCB_DISABLE_BOMB_WEAPON = (1 << 5)
};
enum WingedMutantProjectileType
{
WMUTANT_PROJ_NONE,
WMUTANT_PROJ_DART,
WMUTANT_PROJ_BOMB
};
enum WingedMutantConfig
{
WMUTANT_CONF_CAN_FLY,
WMUTANT_CONF_PATHFINDING_MODE,
WMUTANT_CONF_PROJECTILE_MODE,
WMUTANT_CONF_NO_WINGS,
WMUTANT_CONF_DISABLE_DART_WEAPON,
WMUTANT_CONF_DISABLE_BOMB_WEAPON
};
static void SwitchPathfinding(CreatureInfo* creature, WingedMutantPathFinding path)
{
switch (path)
{
case WMUTANT_PATH_GROUND:
creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1);
creature->LOT.Fly = NO_FLYING;
creature->LOT.Zone = ZoneType::Basic;
break;
case WMUTANT_PATH_AERIAL:
creature->LOT.Step = BLOCK(20);
creature->LOT.Drop = -BLOCK(20);
creature->LOT.Fly = WINGED_MUTANT_FLY_VELOCITY;
creature->LOT.Zone = ZoneType::Flyer;
break;
}
}
static WingedMutantProjectileType CanTargetLara(ItemInfo* item, CreatureInfo* creature, AI_INFO* AI)
{
if (Targetable(item, AI) &&
(AI->zoneNumber != AI->enemyZone || AI->distance > WINGED_MUTANT_PROJECTILE_ATTACK_RANGE))
{
if ((AI->angle > 0 && AI->angle < ANGLE(45.0f)) &&
item->TestFlagField(WMUTANT_CONF_DISABLE_DART_WEAPON, false))
{
return WMUTANT_PROJ_DART;
}
else if ((AI->angle < 0 && AI->angle > -ANGLE(45.0f)) &&
item->TestFlagField(WMUTANT_CONF_DISABLE_BOMB_WEAPON, false))
{
return WMUTANT_PROJ_BOMB;
}
}
// Cannot be targeted.
return WMUTANT_PROJ_NONE;
}
static void WingedInitOCB(ItemInfo* item, CreatureInfo* creature)
{
if (item->TestOcb(WMUTANT_OCB_START_AERIAL))
{
SwitchPathfinding(creature, WMUTANT_PATH_AERIAL);
SetAnimation(item, WMUTANT_ANIM_FLY);
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->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->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
}
// Remove unnecessary OCBs.
if (item->TestOcb(WMUTANT_OCB_START_AERIAL))
item->RemoveOcb(WMUTANT_OCB_START_AERIAL);
if (item->TestOcb(WMUTANT_OCB_START_INACTIVE))
item->RemoveOcb(WMUTANT_OCB_START_INACTIVE);
if (item->TestOcb(WMUTANT_OCB_START_POSE))
item->RemoveOcb(WMUTANT_OCB_START_POSE);
}
void InitializeWingedMutant(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
InitializeCreature(itemNumber);
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->SetFlagField(WMUTANT_CONF_CAN_FLY, false);
item->MeshBits.Clear(WingedMutantWingsJoints);
}
else
{
item->SetFlagField(WMUTANT_CONF_CAN_FLY, true);
}
if (item->TestOcb(WMUTANT_OCB_DISABLE_BOMB_WEAPON))
item->SetFlagField(WMUTANT_CONF_DISABLE_BOMB_WEAPON, true);
if (item->TestOcb(WMUTANT_OCB_DISABLE_DART_WEAPON))
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);
}
void WingedMutantControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
short head = 0;
short torso = 0; // Only when shooting.
bool flyEnabled = item->TestFlagField(WMUTANT_CONF_CAN_FLY, true);
bool flyStatus = item->TestFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
WingedInitOCB(item, creature);
if (item->HitPoints <= 0)
{
CreatureDie(itemNumber, true, BODY_DO_EXPLOSION | BODY_PART_EXPLODE | BODY_NO_SMOKE | BODY_NO_SHATTER_EFFECT);
auto pos = item->Pose;
pos.Position.y -= CLICK(3);
TriggerExplosionSparks(pos.Position.x, pos.Position.y, pos.Position.z, 3, -2, 0, item->RoomNumber);
TriggerExplosionSparks(pos.Position.x, pos.Position.y, pos.Position.z, 3, -1, 0, item->RoomNumber);
TriggerShockwave(&pos, 48, 304, (GetRandomControl() & 0x1F) + 112, 128, 32, 32, 32, EulerAngles(2048, 0.0f, 0.0f), 0, true, false, false, (int)ShockwaveStyle::Normal);
SoundEffect(SFX_TR1_ATLANTEAN_EXPLODE, &item->Pose);
return;
}
else
{
AI_INFO ai;
SwitchPathfinding(creature, WMUTANT_PATH_GROUND);
CreatureAIInfo(item, &ai);
bool isSameZoneInGroundMode = (ai.zoneNumber == ai.enemyZone);
auto projectileType = CanTargetLara(item, creature, &ai);
if (flyEnabled && item->Animation.ActiveState == WMUTANT_STATE_FLY)
{
SwitchPathfinding(creature, WMUTANT_PATH_AERIAL);
CreatureAIInfo(item, &ai);
}
if (ai.ahead)
{
head = ai.angle;
}
else
{
head = 0;
torso = 0;
}
GetCreatureMood(item, &ai, flyStatus);
CreatureMood(item, &ai, flyStatus);
angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState)
{
case WMUTANT_STATE_INACTIVE:
creature->MaxTurn = 0;
creature->Flags = 0;
if (TargetVisible(item, &ai) || creature->HurtByLara)
item->Animation.TargetState = WMUTANT_STATE_IDLE;
break;
case WMUTANT_STATE_IDLE:
item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PROJ_NONE);
creature->MaxTurn = 0;
creature->Flags = 0;
torso = 0;
if (flyEnabled && !isSameZoneInGroundMode)
{
item->Animation.TargetState = WMUTANT_STATE_FLY;
item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_AERIAL);
}
else if (item->TouchBits.Test(WingedMutantHeadJoints))
{
item->Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK;
}
else if (ai.bite && ai.distance < WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE)
{
item->Animation.TargetState = WMUTANT_STATE_IDLE_JUMP_ATTACK;
}
else if (ai.bite && ai.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE)
{
item->Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK;
}
else if (projectileType == WMUTANT_PROJ_DART)
{
item->Animation.TargetState = WMUTANT_STATE_AIM_DART;
}
else if (projectileType == WMUTANT_PROJ_BOMB)
{
item->Animation.TargetState = WMUTANT_STATE_AIM_BOMB;
}
else if (creature->Mood == MoodType::Bored ||
(creature->Mood == MoodType::Stalk && ai.distance < WINGED_MUTANT_POSE_RANGE))
{
item->Animation.TargetState = WMUTANT_STATE_POSE;
}
else
{
item->Animation.TargetState = WMUTANT_STATE_RUN_FORWARD;
}
break;
case WMUTANT_STATE_POSE:
creature->Flags = 0;
creature->MaxTurn = 0;
head = 0; // NOTE: Pose has animation for head.
if (projectileType != WMUTANT_PROJ_NONE || (flyStatus && flyEnabled))
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
else if (creature->Mood == MoodType::Stalk)
{
if (ai.distance < WINGED_MUTANT_WALK_RANGE)
{
if (isSameZoneInGroundMode ||
Random::TestProbability(WINGED_MUTANT_UNPOSE_CHANCE))
{
item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD;
}
}
else
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
}
else if (creature->Mood == MoodType::Bored && Random::TestProbability(WINGED_MUTANT_UNPOSE_CHANCE))
{
item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD;
}
else if (creature->Mood == MoodType::Attack ||
creature->Mood == MoodType::Escape)
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_WALK_FORWARD:
creature->MaxTurn = WINGED_MUTANT_WALK_TURN_RATE_MAX;
creature->Flags = 0;
if (projectileType != WMUTANT_PROJ_NONE || (flyStatus && flyEnabled))
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
else if (creature->Mood == MoodType::Attack || creature->Mood == MoodType::Escape)
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
else if (creature->Mood == MoodType::Bored ||
(creature->Mood == MoodType::Stalk && !isSameZoneInGroundMode))
{
if (Random::TestProbability(WINGED_MUTANT_POSE_CHANCE))
item->Animation.TargetState = WMUTANT_STATE_POSE;
}
else if (creature->Mood == MoodType::Stalk &&
ai.distance > WINGED_MUTANT_WALK_RANGE)
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_RUN_FORWARD:
creature->MaxTurn = WINGED_MUTANT_RUN_TURN_RATE_MAX;
creature->Flags = 0;
if (flyEnabled && !isSameZoneInGroundMode)
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
else if (projectileType != WMUTANT_PROJ_NONE)
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
else if (item->TouchBits.Test(WingedMutantHeadJoints))
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
else if (ai.bite && ai.distance < WINGED_MUTANT_RUN_JUMP_ATTACK_RANGE)
{
item->Animation.TargetState = WMUTANT_STATE_RUN_JUMP_ATTACK;
}
else if (ai.bite && ai.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE)
{
item->Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK;
}
else if (ai.ahead && ai.distance < WINGED_MUTANT_SWIPE_ATTACK_RANGE)
{
item->Animation.TargetState = WMUTANT_STATE_SWIPE_ATTACK;
}
else if (creature->Mood == MoodType::Bored ||
(creature->Mood == MoodType::Stalk && ai.distance < WINGED_MUTANT_POSE_RANGE))
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_IDLE_JUMP_ATTACK:
if (item->Animation.RequiredState == NO_VALUE &&
(item->TouchBits.Test(WingedMutantHandsJoints) || item->TouchBits.Test(WingedMutantHeadJoints)) && creature->Flags == 0)
{
DoDamage(creature->Enemy, WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE / 2);
CreatureEffect(item, WingedMutantBiteLeftHand, DoBloodSplat);
DoDamage(creature->Enemy, WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE / 2);
CreatureEffect(item, WingedMutantBiteRightHand, DoBloodSplat);
item->Animation.TargetState = WMUTANT_STATE_IDLE;
creature->Flags = 1;
}
break;
case WMUTANT_STATE_RUN_JUMP_ATTACK:
if (item->Animation.RequiredState == NO_VALUE &&
(item->TouchBits.Test(WingedMutantHandsJoints) || item->TouchBits.Test(WingedMutantHeadJoints)) && creature->Flags == 0)
{
DoDamage(creature->Enemy, WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE / 2);
CreatureEffect(item, WingedMutantBiteLeftHand, DoBloodSplat);
DoDamage(creature->Enemy, WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE / 2);
CreatureEffect(item, WingedMutantBiteRightHand, DoBloodSplat);
item->Animation.TargetState = WMUTANT_STATE_RUN_FORWARD;
creature->Flags = 1;
}
break;
case WMUTANT_STATE_SWIPE_ATTACK:
if (item->Animation.RequiredState == NO_VALUE &&
item->TouchBits.Test(WingedMutantHandsJoints) && creature->Flags == 0)
{
DoDamage(creature->Enemy, WINGED_MUTANT_SWIPE_ATTACK_DAMAGE / 2);
CreatureEffect(item, WingedMutantBiteLeftHand, DoBloodSplat);
DoDamage(creature->Enemy, WINGED_MUTANT_SWIPE_ATTACK_DAMAGE / 2);
CreatureEffect(item, WingedMutantBiteRightHand, DoBloodSplat);
item->Animation.TargetState = WMUTANT_STATE_IDLE;
creature->Flags = 1;
}
break;
case WMUTANT_STATE_AIM_DART:
item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_DART);
creature->MaxTurn = 0;
creature->Flags = 0;
torso = ai.angle / 2;
if (projectileType == WMUTANT_PROJ_DART)
{
item->Animation.TargetState = WMUTANT_STATE_SHOOT;
}
else
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_AIM_BOMB:
item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_BOMB);
creature->MaxTurn = 0;
creature->Flags = 0;
torso = ai.angle / 2;
if (projectileType == WMUTANT_PROJ_BOMB)
{
item->Animation.TargetState = WMUTANT_STATE_SHOOT;
}
else
{
item->Animation.TargetState = WMUTANT_STATE_IDLE;
}
break;
case WMUTANT_STATE_SHOOT:
creature->MaxTurn = 0;
torso = ai.angle / 2;
if (creature->Flags == 0)
{
if (projectileType == WMUTANT_PROJ_DART)
{
CreatureEffect2(item, WingedMutantShardBite, WINGED_MUTANT_SHARD_VELOCITY, torso, ShardGun);
}
else if (projectileType == WMUTANT_PROJ_BOMB)
{
CreatureEffect2(item, WingedMutantRocketBite, WINGED_MUTANT_BOMB_VELOCITY, torso, BombGun);
}
creature->Flags = 1;
}
item->SetFlagField(WMUTANT_CONF_PROJECTILE_MODE, WMUTANT_PROJ_NONE);
break;
case WMUTANT_STATE_FLY:
if (creature->Mood != MoodType::Escape && isSameZoneInGroundMode)
{
item->Animation.TargetState = WMUTANT_STATE_IDLE; // Switch to ground mode.
item->Pose.Position.y = item->Floor;
item->SetFlagField(WMUTANT_CONF_PATHFINDING_MODE, WMUTANT_PATH_GROUND);
}
break;
}
}
CreatureJoint(item, 0, torso);
CreatureJoint(item, 1, head);
CreatureAnimation(itemNumber, angle, 0);
}
}

View file

@ -10,18 +10,18 @@
#include "Specific/level.h" #include "Specific/level.h"
// Creatures // Creatures
#include "Objects/TR1/Entity/Cowboy.h" // OK #include "Objects/TR1/Entity/Centaur.h"
#include "Objects/TR1/Entity/Kold.h" // OK #include "Objects/TR1/Entity/Cowboy.h"
#include "Objects/TR1/Entity/tr1_ape.h" // OK #include "Objects/TR1/Entity/Kold.h"
#include "Objects/TR1/Entity/tr1_bear.h" // OK #include "Objects/TR1/Entity/SkateboardKid.h"
#include "Objects/TR1/Entity/tr1_doppelganger.h" // OK #include "Objects/TR1/Entity/WingedMutant.h"
#include "Objects/TR1/Entity/tr1_natla.h" // OK #include "Objects/TR1/Entity/tr1_ape.h"
#include "Objects/TR1/Entity/tr1_giant_mutant.h" // OK #include "Objects/TR1/Entity/tr1_bear.h"
#include "Objects/TR1/Entity/tr1_wolf.h" // OK #include "Objects/TR1/Entity/tr1_doppelganger.h"
#include "Objects/TR1/Entity/tr1_big_rat.h" // OK #include "Objects/TR1/Entity/tr1_natla.h"
#include "Objects/TR1/Entity/tr1_centaur.h" // OK #include "Objects/TR1/Entity/tr1_giant_mutant.h"
#include "Objects/TR1/Entity/tr1_winged_mutant.h" // OK #include "Objects/TR1/Entity/tr1_wolf.h"
#include "Objects/TR1/Entity/SkateboardKid.h" // OK #include "Objects/TR1/Entity/tr1_big_rat.h"
#include "Objects/Utils/object_helper.h" #include "Objects/Utils/object_helper.h"
// Traps // Traps
@ -149,7 +149,7 @@ static void StartEntity(ObjectInfo* obj)
if (obj->loaded) if (obj->loaded)
{ {
obj->Initialize = InitializeCreature; obj->Initialize = InitializeCreature;
obj->control = CentaurControl; obj->control = ControlCentaur;
obj->collision = CreatureCollision; obj->collision = CreatureCollision;
obj->shadowType = ShadowMode::All; obj->shadowType = ShadowMode::All;
obj->HitPoints = 120; obj->HitPoints = 120;
@ -157,7 +157,8 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = BLOCK(1 / 3.0f); obj->radius = BLOCK(1 / 3.0f);
obj->intelligent = true; obj->intelligent = true;
obj->LotType = LotType::Blockable; obj->LotType = LotType::Blockable;
obj->SetBoneRotationFlags(10, ROT_X | ROT_Y); obj->SetBoneRotationFlags(10, ROT_X | ROT_Y); // Torso
obj->SetBoneRotationFlags(17, ROT_X | ROT_Y); // Head
obj->SetHitEffect(); obj->SetHitEffect();
} }
@ -165,7 +166,7 @@ static void StartEntity(ObjectInfo* obj)
if (obj->loaded) if (obj->loaded)
{ {
obj->Initialize = InitializeWingedMutant; obj->Initialize = InitializeWingedMutant;
obj->control = WingedMutantControl; obj->control = ControlWingedMutant;
obj->collision = CreatureCollision; obj->collision = CreatureCollision;
obj->shadowType = ShadowMode::All; obj->shadowType = ShadowMode::All;
obj->pivotLength = 150; obj->pivotLength = 150;
@ -274,7 +275,6 @@ static void StartProjectiles(ObjectInfo* obj)
{ {
InitProjectile(obj, ControlMissile, ID_PROJ_SHARD); InitProjectile(obj, ControlMissile, ID_PROJ_SHARD);
InitProjectile(obj, ControlMissile, ID_PROJ_BOMB); InitProjectile(obj, ControlMissile, ID_PROJ_BOMB);
InitProjectile(obj, ControlMissile, ID_PROJ_BOMB);
} }
void InitializeTR1Objects() void InitializeTR1Objects()

View file

@ -72,14 +72,17 @@ namespace TEN::Entities::Creatures::TR3
(g_Level.PathfindingBoxes[creature.Enemy->BoxNumber].flags & BLOCKABLE)); (g_Level.PathfindingBoxes[creature.Enemy->BoxNumber].flags & BLOCKABLE));
} }
static void SpawnLizardGas(int itemNumber, const CreatureBiteInfo& bite, int speed) static void SpawnLizardGas(const ItemInfo& item, const CreatureBiteInfo& bite, float vel)
{ {
constexpr auto THROW_COUNT = 2; constexpr auto THROW_COUNT = 3;
auto velVector = Vector3(0.0f, -100.0f, vel * 4);
for (int i = 0; i < THROW_COUNT; i++) for (int i = 0; i < THROW_COUNT; i++)
ThrowPoison(itemNumber, bite, Vector3i(0.0f, -100.0f, speed << 2), Vector3(0.0f, 1.0f, 0.0f)); {
auto colorStart = Color(0.0f, Random::GenerateFloat(0.25f, 0.5f), 0.1f);
ThrowPoison(itemNumber, bite, Vector3i(0.0f, -100.0f, speed << 1), Vector3(0.0f, 1.0f, 0.0f)); auto colorEnd = Color(0.0f, Random::GenerateFloat(0.1f, 0.2f), 0.1f);
ThrowPoison(item, bite, velVector, colorStart, colorEnd);
}
} }
void LizardControl(short itemNumber) void LizardControl(short itemNumber)
@ -319,9 +322,13 @@ namespace TEN::Entities::Creatures::TR3
creature.Flags += 2; creature.Flags += 2;
if (creature.Flags < 24) if (creature.Flags < 24)
SpawnLizardGas(itemNumber, LizardGasBite, creature.Flags); {
SpawnLizardGas(item, LizardGasBite, creature.Flags);
}
else else
SpawnLizardGas(itemNumber, LizardGasBite, (GetRandomControl() & 15) + 8); {
SpawnLizardGas(item, LizardGasBite, (GetRandomControl() & 15) + 8);
}
} }
if (TestAnimFrame(item, 28)) if (TestAnimFrame(item, 28))

View file

@ -0,0 +1,450 @@
#include "framework.h"
#include "Objects/TR3/Entity/Raptor.h"
#include "Game/collision/Point.h"
#include "Game/control/box.h"
#include "Game/control/control.h"
#include "Game/control/lot.h"
#include "Game/effects/effects.h"
#include "Game/items.h"
#include "Game/itemdata/creature_info.h"
#include "Game/Lara/lara.h"
#include "Game/misc.h"
#include "Game/Setup.h"
#include "Math/Math.h"
#include "Specific/level.h"
using namespace TEN::Collision::Point;
using namespace TEN::Math;
namespace TEN::Entities::Creatures::TR3
{
constexpr auto RAPTOR_ATTACK_DAMAGE = 100;
constexpr auto RAPTOR_BITE_ATTACK_RANGE = SQUARE(BLOCK(0.6f));
constexpr auto RAPTOR_JUMP_ATTACK_RANGE = SQUARE(BLOCK(1.5f));
constexpr auto RAPTOR_RUN_ATTACK_RANGE = SQUARE(BLOCK(1.5f));
constexpr auto RAPTOR_ROAR_CHANCE = 1.0f / 256;
constexpr auto RAPTOR_SWITCH_TARGET_CHANCE = 1.0f / 128;
constexpr auto RAPTOR_WALK_TURN_RATE_MAX = ANGLE(2.0f);
constexpr auto RAPTOR_RUN_TURN_RATE_MAX = ANGLE(4.0f);
constexpr auto RAPTOR_ATTACK_TURN_RATE_MAX = ANGLE(2.0f);
const auto RaptorBite = CreatureBiteInfo(Vector3(0, 66, 318), 22);
const auto RaptorAttackJoints = std::vector<unsigned int>{ 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23 };
enum RaptorState
{
RAPTOR_STATE_DEATH = 0,
RAPTOR_STATE_IDLE = 1,
RAPTOR_STATE_WALK_FORWARD = 2,
RAPTOR_STATE_RUN_FORWARD = 3,
RAPTOR_STATE_JUMP_ATTACK = 4,
RAPTOR_STATE_NONE = 5,
RAPTOR_STATE_ROAR = 6,
RAPTOR_STATE_RUN_BITE_ATTACK = 7,
RAPTOR_STATE_BITE_ATTACK = 8,
RAPTOR_STATE_JUMP_START = 9,
RAPTOR_STATE_JUMP_2_BLOCKS = 10,
RAPTOR_STATE_JUMP_1_BLOCK = 11,
RAPTOR_STATE_CLIMB = 12
};
enum RaptorAnim
{
RAPTOR_ANIM_IDLE = 0,
RAPTOR_ANIM_RUN_FORWARD = 1,
RAPTOR_ANIM_RUN_FORWARD_TO_IDLE = 2,
RAPTOR_ANIM_IDLE_TO_RUN_FORWARD = 3,
RAPTOR_ANIM_ROAR = 4,
RAPTOR_ANIM_WALK_FORWARD = 5,
RAPTOR_ANIM_WALK_FORWARD_TO_IDLE = 6,
RAPTOR_ANIM_IDLE_TO_WALK_FORWARD = 7,
RAPTOR_ANIM_RUN_BITE_ATTACK = 8,
RAPTOR_ANIM_DEATH_1 = 9,
RAPTOR_ANIM_DEATH_2 = 10,
RAPTOR_ANIM_JUMP_ATTACK_START = 11,
RAPTOR_ANIM_JUMP_ATTACK_END = 12,
RAPTOR_ANIM_BITE_ATTACK = 13,
RAPTOR_ANIM_JUMP_START = 14,
RAPTOR_ANIM_JUMP_2_BLOCKS = 15,
RAPTOR_ANIM_JUMP_END = 16,
RAPTOR_ANIM_JUMP_1_BLOCK = 17,
RAPTOR_ANIM_VAULT_2_STEPS = 18,
RAPTOR_ANIM_VAULT_3_STEPS = 19,
RAPTOR_ANIM_VAULT_4_STEPS = 20,
RAPTOR_ANIM_VAULT_DROP_2_STEPS = 21,
RAPTOR_ANIM_VAULT_DROP_3_STEPS = 22,
RAPTOR_ANIM_VAULT_DROP_4_STEPS = 23
};
enum RaptorFlags
{
OCB_NORMAL_BEHAVIOUR = 0,
OCB_ENABLE_JUMP = 1
};
const std::array RaptorDeathAnims = { RAPTOR_ANIM_DEATH_1, RAPTOR_ANIM_DEATH_2, };
const std::vector<GAME_OBJECT_ID> RaptorIgnoredObjectIds = { ID_RAPTOR, ID_COMPSOGNATHUS };
void RaptorControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto& item = g_Level.Items[itemNumber];
auto& creature = *GetCreatureInfo(&item);
short headingAngle = 0;
short tiltAngle = 0;
short headYOrient = 0;
short neckYOrient = 0;
bool canJump = item.TestOcb(OCB_ENABLE_JUMP);
if (!canJump)
{
creature.LOT.Step = CLICK(1);
creature.LOT.Drop = -CLICK(2);
creature.LOT.Zone = ZoneType::Basic;
creature.LOT.CanJump = false;
}
bool canJump1block = (canJump && CanCreatureJump(item, JumpDistance::Block1));
bool canJump2blocks = (canJump && !canJump1block && CanCreatureJump(item, JumpDistance::Block2));
// Require Idle state.
if (item.HitPoints <= 0 && item.Animation.ActiveState == RAPTOR_STATE_IDLE)
{
if (item.Animation.ActiveState != RAPTOR_STATE_DEATH)
SetAnimation(item, RaptorDeathAnims[Random::GenerateInt(0, (int)RaptorDeathAnims.size() - 1)]);
}
else
{
// NOTE: Ignores other small dinosaurs.
TargetNearestEntity(&item, &creature, RaptorIgnoredObjectIds);
AI_INFO ai;
if (item.AIBits)
GetAITarget(&creature);
CreatureAIInfo(&item, &ai);
GetCreatureMood(&item, &ai, true);
CreatureMood(&item, &ai, true);
if (creature.Mood == MoodType::Bored)
creature.MaxTurn /= 2;
headingAngle = CreatureTurn(&item, creature.MaxTurn);
if (ai.ahead)
{
headYOrient = ai.angle;
neckYOrient = -headingAngle * 6;
}
switch (item.Animation.ActiveState)
{
case RAPTOR_STATE_IDLE:
creature.MaxTurn = 0;
creature.LOT.IsJumping = false;
creature.Flags &= ~1;
if (canJump1block || canJump2blocks)
{
creature.MaxTurn = 0;
creature.LOT.IsJumping = true;
SetAnimation(item, RAPTOR_ANIM_JUMP_START);
if (canJump1block)
{
item.Animation.TargetState = RAPTOR_STATE_JUMP_1_BLOCK;
}
else
{
item.Animation.TargetState = RAPTOR_STATE_JUMP_2_BLOCKS;
}
}
else if (item.Animation.RequiredState != NO_VALUE)
{
item.Animation.TargetState = item.Animation.RequiredState;
}
else if (creature.Flags & 2)
{
creature.Flags &= ~2;
item.Animation.TargetState = RAPTOR_STATE_ROAR;
}
else if (item.TouchBits.Test(RaptorAttackJoints) ||
(ai.distance < RAPTOR_BITE_ATTACK_RANGE && ai.bite))
{
item.Animation.TargetState = RAPTOR_STATE_BITE_ATTACK;
}
else if (ai.bite && ai.distance < RAPTOR_JUMP_ATTACK_RANGE)
{
item.Animation.TargetState = RAPTOR_STATE_JUMP_ATTACK;
}
else if (creature.Mood == MoodType::Escape &&
Lara.TargetEntity != &item && ai.ahead && !item.HitStatus)
{
item.Animation.TargetState = RAPTOR_STATE_IDLE;
}
else if (creature.Mood == MoodType::Bored)
{
item.Animation.TargetState = RAPTOR_STATE_WALK_FORWARD;
}
else
{
item.Animation.TargetState = RAPTOR_STATE_RUN_FORWARD;
}
break;
case RAPTOR_STATE_WALK_FORWARD:
creature.MaxTurn = RAPTOR_WALK_TURN_RATE_MAX;
creature.LOT.IsJumping = false;
creature.Flags &= ~1;
if (item.HitPoints <= 0)
{
item.Animation.TargetState = RAPTOR_STATE_IDLE;
}
else if (canJump1block || canJump2blocks)
{
creature.MaxTurn = 0;
creature.LOT.IsJumping = true;
SetAnimation(item, RAPTOR_ANIM_JUMP_START);
if (canJump1block)
{
item.Animation.TargetState = RAPTOR_STATE_JUMP_1_BLOCK;
}
else
{
item.Animation.TargetState = RAPTOR_STATE_JUMP_2_BLOCKS;
}
}
else if (creature.Mood != MoodType::Bored)
{
item.Animation.TargetState = RAPTOR_STATE_IDLE;
}
else if (ai.ahead && Random::TestProbability(RAPTOR_ROAR_CHANCE))
{
item.Animation.TargetState = RAPTOR_STATE_IDLE;
item.Animation.RequiredState = RAPTOR_STATE_ROAR;
creature.Flags &= ~2;
}
break;
case RAPTOR_STATE_RUN_FORWARD:
creature.MaxTurn = RAPTOR_RUN_TURN_RATE_MAX;
creature.LOT.IsJumping = false;
creature.Flags &= ~1;
tiltAngle = headingAngle;
if (item.HitPoints <= 0)
{
item.Animation.TargetState = RAPTOR_STATE_IDLE;
}
else if (canJump1block || canJump2blocks)
{
creature.MaxTurn = 0;
creature.LOT.IsJumping = true;
SetAnimation(item, RAPTOR_ANIM_JUMP_START);
if (canJump1block)
{
item.Animation.TargetState = RAPTOR_STATE_JUMP_1_BLOCK;
}
else
{
item.Animation.TargetState = RAPTOR_STATE_JUMP_2_BLOCKS;
}
}
else if (item.TouchBits.Test(RaptorAttackJoints))
{
item.Animation.TargetState = RAPTOR_STATE_IDLE;
}
else if (creature.Flags & 2)
{
item.Animation.TargetState = RAPTOR_STATE_IDLE;
item.Animation.RequiredState = RAPTOR_STATE_ROAR;
creature.Flags &= ~2;
}
else if (ai.bite && ai.distance < RAPTOR_RUN_ATTACK_RANGE)
{
if (Random::TestProbability(1 / 4.0f))
{
item.Animation.TargetState = RAPTOR_STATE_IDLE;
}
else
{
item.Animation.TargetState = RAPTOR_STATE_RUN_BITE_ATTACK;
}
}
else if (ai.ahead && creature.Mood != MoodType::Escape &&
Random::TestProbability(RAPTOR_ROAR_CHANCE))
{
item.Animation.TargetState = RAPTOR_STATE_IDLE;
item.Animation.RequiredState = RAPTOR_STATE_ROAR;
}
else if (creature.Mood == MoodType::Bored ||
(creature.Mood == MoodType::Escape && Lara.TargetEntity != &item && ai.ahead))
{
item.Animation.TargetState = RAPTOR_STATE_IDLE;
}
break;
case RAPTOR_STATE_JUMP_ATTACK:
creature.MaxTurn = RAPTOR_ATTACK_TURN_RATE_MAX;
tiltAngle = headingAngle;
if (creature.Enemy != nullptr)
{
if (creature.Enemy->IsLara() && !(creature.Flags & 1) &&
item.TouchBits.Test(RaptorAttackJoints))
{
DoDamage(creature.Enemy, RAPTOR_ATTACK_DAMAGE);
CreatureEffect(&item, RaptorBite, DoBloodSplat);
creature.Flags |= 1;
if (LaraItem->HitPoints <= 0)
creature.Flags |= 2;
item.Animation.RequiredState = RAPTOR_STATE_IDLE;
}
else if (!(creature.Flags & 1))
{
if (Vector3i::Distance(item.Pose.Position, creature.Enemy->Pose.Position) <= BLOCK(0.5f))
{
if (creature.Enemy->HitPoints <= 0)
creature.Flags |= 2;
DoDamage(creature.Enemy, 25);
CreatureEffect(&item, RaptorBite, DoBloodSplat);
creature.Flags |= 1;
}
}
}
break;
case RAPTOR_STATE_BITE_ATTACK:
creature.MaxTurn = RAPTOR_ATTACK_TURN_RATE_MAX;
tiltAngle = headingAngle;
if (creature.Enemy != nullptr)
{
if (creature.Enemy->IsLara() && !(creature.Flags & 1) &&
item.TouchBits.Test(RaptorAttackJoints))
{
DoDamage(creature.Enemy, RAPTOR_ATTACK_DAMAGE);
CreatureEffect(&item, RaptorBite, DoBloodSplat);
creature.Flags |= 1;
if (LaraItem->HitPoints <= 0)
creature.Flags |= 2;
item.Animation.RequiredState = RAPTOR_STATE_IDLE;
}
else if (!(creature.Flags & 1))
{
if (Vector3i::Distance(item.Pose.Position, creature.Enemy->Pose.Position) <= BLOCK(0.5f))
{
if (creature.Enemy->HitPoints <= 0)
creature.Flags |= 2;
DoDamage(creature.Enemy, 25);
CreatureEffect(&item, RaptorBite, DoBloodSplat);
creature.Flags |= 1;
}
}
}
break;
case RAPTOR_STATE_RUN_BITE_ATTACK:
creature.MaxTurn = RAPTOR_ATTACK_TURN_RATE_MAX;
tiltAngle = headingAngle;
if (creature.Enemy != nullptr)
{
if (creature.Enemy->IsLara() && !(creature.Flags & 1) &&
item.TouchBits.Test(RaptorAttackJoints))
{
DoDamage(creature.Enemy, RAPTOR_ATTACK_DAMAGE);
CreatureEffect(&item, RaptorBite, DoBloodSplat);
item.Animation.RequiredState = RAPTOR_STATE_RUN_FORWARD;
creature.Flags |= 1;
if (LaraItem->HitPoints <= 0)
creature.Flags |= 2;
}
else if (!(creature.Flags & 1))
{
if (Vector3i::Distance(item.Pose.Position, creature.Enemy->Pose.Position) <= BLOCK(0.5f))
{
if (creature.Enemy->HitPoints <= 0)
creature.Flags |= 2;
DoDamage(creature.Enemy, 25);
CreatureEffect(&item, RaptorBite, DoBloodSplat);
creature.Flags |= 1;
}
}
}
break;
}
}
CreatureTilt(&item, tiltAngle);
CreatureJoint(&item, 0, headYOrient / 2);
CreatureJoint(&item, 1, headYOrient / 2);
CreatureJoint(&item, 2, neckYOrient);
CreatureJoint(&item, 3, neckYOrient);
if (item.Animation.ActiveState != RAPTOR_STATE_JUMP_2_BLOCKS &&
item.Animation.ActiveState != RAPTOR_STATE_JUMP_1_BLOCK &&
item.Animation.ActiveState != RAPTOR_STATE_CLIMB &&
item.Animation.ActiveState != RAPTOR_STATE_JUMP_START)
{
switch (CreatureVault(itemNumber, headingAngle, 2, CLICK(2.5f)))
{
case 2:
SetAnimation(item, RAPTOR_ANIM_VAULT_2_STEPS);
creature.MaxTurn = 0;
break;
case 3:
SetAnimation(item, RAPTOR_ANIM_VAULT_3_STEPS);
creature.MaxTurn = 0;
break;
case 4:
SetAnimation(item, RAPTOR_ANIM_VAULT_4_STEPS);
creature.MaxTurn = 0;
break;
case -2:
SetAnimation(item, RAPTOR_ANIM_VAULT_DROP_2_STEPS);
creature.MaxTurn = 0;
break;
case -3:
SetAnimation(item, RAPTOR_ANIM_VAULT_DROP_3_STEPS);
creature.MaxTurn = 0;
break;
case -4:
SetAnimation(item, RAPTOR_ANIM_VAULT_DROP_4_STEPS);
creature.MaxTurn = 0;
break;
}
}
else
{
CreatureAnimation(itemNumber, headingAngle, tiltAngle);
}
}
}

View file

@ -0,0 +1,332 @@
#include "framework.h"
#include "Objects/TR3/Entity/SealMutant.h"
#include "Game/animation.h"
#include "Game/control/box.h"
#include "Game/effects/effects.h"
#include "Game/effects/tomb4fx.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_helpers.h"
#include "Game/misc.h"
#include "Game/people.h"
#include "Game/Setup.h"
#include "Math/Math.h"
#include "Objects/Effects/enemy_missile.h"
using namespace TEN::Math;
// NOTES:
// ItemFlags[0]: Sprite ID for poison effect.
namespace TEN::Entities::Creatures::TR3
{
constexpr auto SEAL_MUTANT_ATTACK_DAMAGE = 1;
constexpr auto SEAL_MUTANT_ALERT_RANGE = SQUARE(BLOCK(1));
constexpr auto SEAL_MUTANT_ATTACK_RANGE = SQUARE(BLOCK(1.5f));
constexpr auto SEAL_MUTANT_WALK_TURN_RATE = ANGLE(3.0f);
constexpr auto SEAL_MUTANT_FLAME_LIGHT_Y_OFFSET = CLICK(2);
constexpr auto SEAL_MUTANT_BURN_END_TIME = 16;
const auto SealMutantGasBite = CreatureBiteInfo(Vector3(0.0f, 48.0f, 140.0f), 10);
const auto SealMutantAttackTargetObjectIds = { ID_LARA, ID_FLAMETHROWER_BADDY, ID_WORKER_FLAMETHROWER };
enum SealMutantState
{
SEAL_MUTANT_STATE_IDLE = 0,
SEAL_MUTANT_STATE_WALK = 1,
SEAL_MUTANT_STATE_ATTACK = 2,
SEAL_MUTANT_STATE_DEATH = 3,
SEAL_MUTANT_STATE_TRAP = 4
};
enum SealMutantAnim
{
SEAL_MUTANT_ANIM_IDLE = 0,
SEAL_MUTANT_ANIM_IDLE_TO_WALK = 1,
SEAL_MUTANT_ANIM_WALK = 2,
SEAL_MUTANT_ANIM_WALK_TO_IDLE = 3,
SEAL_MUTANT_ANIM_ATTACK = 4,
SEAL_MUTANT_ANIM_DEATH = 5,
SEAL_MUTANT_ANIM_TRAP = 6
};
enum SealMutantItemFlags
{
IF_SEAL_MUTANT_FLAME_TIMER = 0
};
enum SealMutantOcb
{
OCB_NORMAL_BEHAVIOUR = 0,
OCB_TRAP = 1
};
static void SpawnSealMutantPoisonGas(ItemInfo& item, float vel)
{
constexpr auto GAS_COUNT = 2;
constexpr auto VEL_MULT = 5.0f;
constexpr auto PLAYER_CROUCH_GRAVITY = 32.0f;
auto& creature = *GetCreatureInfo(&item);
// HACK.
float gravity = 0.0f;
if (creature.Enemy != nullptr)
{
if (creature.Enemy->IsLara())
{
const auto& player = GetLaraInfo(*creature.Enemy);
if (player.Control.IsLow)
gravity = PLAYER_CROUCH_GRAVITY;
}
else
{
DoDamage(creature.Enemy, SEAL_MUTANT_ATTACK_DAMAGE);
}
}
auto velVector = Vector3(0.0f, gravity, vel * VEL_MULT);
auto colorStart = Color(Random::GenerateFloat(0.25f, 0.5f), Random::GenerateFloat(0.25f, 0.5f), 0.1f);
auto colorEnd = Color(Random::GenerateFloat(0.05f, 0.1f), Random::GenerateFloat(0.05f, 0.1f), 0.0f);
for (int i = 0; i < GAS_COUNT; i++)
ThrowPoison(item, SealMutantGasBite, velVector, colorStart, colorEnd, item.ItemFlags[0]);
}
void InitializeSealMutant(short itemNumber)
{
auto& item = g_Level.Items[itemNumber];
item.ItemFlags[0] = 0;
InitializeCreature(itemNumber);
}
void ControlSealMutant(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto& item = g_Level.Items[itemNumber];
auto& creature = *GetCreatureInfo(&item);
short headingAngle = 0;
auto headOrient = EulerAngles::Identity;
auto torsoOrient = EulerAngles::Identity;
float gasVel = 0.0f;
if (item.TestOcb(OCB_TRAP))
{
if (item.Animation.ActiveState != SEAL_MUTANT_STATE_TRAP)
{
SetAnimation(item, SEAL_MUTANT_ANIM_TRAP);
}
else if (TestAnimFrameRange(item, 1, 124))
{
const auto& anim = GetAnimData(item.Animation.AnimNumber);
gasVel = item.Animation.FrameNumber - (anim.frameBase + 1);
if (gasVel > 24.0f)
{
gasVel = item.Animation.FrameNumber - (anim.frameEnd - 8);
if (gasVel <= 0.0f)
gasVel = 1.0f;
if (gasVel > 24.0f)
gasVel = Random::GenerateFloat(8.0f, 24.0f);
SpawnSealMutantPoisonGas(item, gasVel);
}
}
CreatureAnimation(itemNumber, 0, 0);
return;
}
if (item.GetFlagField(IF_SEAL_MUTANT_FLAME_TIMER) > 80)
item.HitPoints = 0;
if (item.HitPoints <= 0)
{
const auto& anim = GetAnimData(item.Animation.AnimNumber);
if (item.Animation.ActiveState != SEAL_MUTANT_STATE_DEATH)
{
SetAnimation(item, SEAL_MUTANT_ANIM_DEATH);
}
else if (item.GetFlagField(IF_SEAL_MUTANT_FLAME_TIMER) > 80)
{
for (int boneID = 9; boneID < 17; boneID++)
{
auto pos = GetJointPosition(item, boneID);
TriggerFireFlame(pos.x, pos.y, pos.z, FlameType::Medium);
}
int burnTimer = item.Animation.FrameNumber - anim.frameBase;
if (burnTimer > SEAL_MUTANT_BURN_END_TIME)
{
burnTimer = item.Animation.FrameNumber - anim.frameEnd;
if (burnTimer > SEAL_MUTANT_BURN_END_TIME)
burnTimer = SEAL_MUTANT_BURN_END_TIME;
}
if (burnTimer != SEAL_MUTANT_BURN_END_TIME)
{
auto pos = GetJointPosition(item, SealMutantGasBite.BoneID, Vector3(0.0f, -SEAL_MUTANT_FLAME_LIGHT_Y_OFFSET, 0.0f));
auto color = Color(Random::GenerateFloat(0.75f, 1.0f), Random::GenerateFloat(0.4f, 0.5f), Random::GenerateFloat(0.0f, 0.25f));
float falloff = Random::GenerateFloat(0.03f, 0.04f);
TriggerDynamicLight(pos.ToVector3(), color, falloff);
}
}
else if (TestAnimFrameRange(item, 1, 124))
{
gasVel = item.Animation.FrameNumber - (anim.frameBase + 1);
if (gasVel > 24.0f)
{
gasVel = item.Animation.FrameNumber - (anim.frameEnd - 8);
if (gasVel <= 0.0f)
gasVel = 1.0f;
if (gasVel > 24.0f)
gasVel = Random::GenerateFloat(16.0f, 24.0f);
SpawnSealMutantPoisonGas(item, gasVel);
}
}
}
else
{
if (item.AIBits)
{
GetAITarget(&creature);
}
else
{
TargetNearestEntity(&item, &creature, SealMutantAttackTargetObjectIds, false);
}
AI_INFO ai;
CreatureAIInfo(&item, &ai);
GetCreatureMood(&item, &ai, ai.zoneNumber == ai.enemyZone);
if (creature.Enemy != nullptr && creature.Enemy->IsLara())
{
const auto& player = GetLaraInfo(*creature.Enemy);
if (player.Status.Poison >= LARA_POISON_MAX)
creature.Mood = MoodType::Escape;
}
CreatureMood(&item, &ai, ai.zoneNumber == ai.enemyZone);
headingAngle = CreatureTurn(&item, creature.MaxTurn);
auto* target = creature.Enemy;
creature.Enemy = LaraItem;
if (ai.distance < SEAL_MUTANT_ALERT_RANGE || item.HitStatus || TargetVisible(&item, &ai))
AlertAllGuards(itemNumber);
creature.Enemy = target;
switch (item.Animation.ActiveState)
{
case SEAL_MUTANT_STATE_IDLE:
creature.MaxTurn = 0;
creature.Flags = 0;
headOrient.x = -ai.xAngle;
headOrient.y = ai.angle;
torsoOrient.x = 0;
torsoOrient.z = 0;
if (item.AIBits & GUARD)
{
item.Animation.TargetState = SEAL_MUTANT_STATE_IDLE;
headOrient.x = 0;
headOrient.y = AIGuard(&creature);
}
else if (item.AIBits & PATROL1)
{
item.Animation.TargetState = SEAL_MUTANT_STATE_WALK;
headOrient.x = 0;
headOrient.y = 0;
}
else if (creature.Mood == MoodType::Escape)
{
item.Animation.TargetState = SEAL_MUTANT_STATE_WALK;
}
else if (Targetable(&item, &ai) && ai.distance < SEAL_MUTANT_ATTACK_RANGE)
{
item.Animation.TargetState = SEAL_MUTANT_STATE_ATTACK;
}
else if (item.Animation.RequiredState != NO_VALUE)
{
item.Animation.TargetState = item.Animation.RequiredState;
}
else
{
item.Animation.TargetState = SEAL_MUTANT_STATE_WALK;
}
break;
case SEAL_MUTANT_STATE_WALK:
creature.MaxTurn = SEAL_MUTANT_WALK_TURN_RATE;
if (ai.ahead)
{
headOrient.x = -ai.xAngle;
headOrient.y = ai.angle;
}
if (item.AIBits & PATROL1)
{
item.Animation.TargetState = SEAL_MUTANT_STATE_WALK;
headOrient.y = 0;
}
else if (Targetable(&item, &ai) && ai.distance < SEAL_MUTANT_ATTACK_RANGE)
{
item.Animation.TargetState = SEAL_MUTANT_STATE_IDLE;
}
break;
case SEAL_MUTANT_STATE_ATTACK:
if (ai.ahead)
{
headOrient.x = -ai.xAngle;
headOrient.y = ai.angle;
torsoOrient.x = -ai.xAngle / 2;
torsoOrient.z = ai.angle / 2;
}
if (TestAnimFrameRange(item, 35, 58))
{
if (creature.Flags < 24)
creature.Flags += 3;
gasVel = 0.0f;
if (creature.Flags < 24.0f)
{
gasVel = creature.Flags;
}
else
{
gasVel = Random::GenerateFloat(16.0f, 24.0f);
}
SpawnSealMutantPoisonGas(item, gasVel);
if (creature.Enemy != nullptr && !creature.Enemy->IsLara())
creature.Enemy->HitStatus = true;
}
break;
}
}
CreatureJoint(&item, 0, torsoOrient.z);
CreatureJoint(&item, 1, torsoOrient.x);
CreatureJoint(&item, 2, headOrient.y);
CreatureAnimation(itemNumber, headingAngle, 0);
}
}

View file

@ -0,0 +1,7 @@
#pragma once
namespace TEN::Entities::Creatures::TR3
{
void InitializeSealMutant(short itemNumber);
void ControlSealMutant(short itemNumber);
}

View file

@ -21,8 +21,8 @@ namespace TEN::Entities::Creatures::TR3
constexpr auto TWIN_AUTO_GUN_SHOT_DAMAGE = 5; constexpr auto TWIN_AUTO_GUN_SHOT_DAMAGE = 5;
constexpr auto TWIN_AUTO_GUN_HIT_POINTS_MAX = 28; constexpr auto TWIN_AUTO_GUN_HIT_POINTS_MAX = 28;
const auto TwinAutoGunLeftBite = CreatureBiteInfo(110, -30, 530, 2); const auto TwinAutoGunLeftBite = CreatureBiteInfo(Vector3(110.0f, -30.0f, 530.0f), 2);
const auto TwinAutoGunRightBite = CreatureBiteInfo(-110, -30, 530, 2); const auto TwinAutoGunRightBite = CreatureBiteInfo(Vector3(-110.0f, -30.0f, 530.0f), 2);
enum TwinAutoGunAnim enum TwinAutoGunAnim
{ {

View file

@ -28,6 +28,7 @@ namespace TEN::Entities::Creatures::TR3
constexpr auto FLAMETHROWER_WALK_TURN_RATE_MAX = ANGLE(5.0f); constexpr auto FLAMETHROWER_WALK_TURN_RATE_MAX = ANGLE(5.0f);
const auto FlamethrowerBite = CreatureBiteInfo(Vector3(0, 340, 64), 7); const auto FlamethrowerBite = CreatureBiteInfo(Vector3(0, 340, 64), 7);
const auto FlamethrowerTargetIds = { ID_LARA, ID_SEAL_MUTANT };
// TODO // TODO
enum FlamethrowerState enum FlamethrowerState
@ -94,30 +95,7 @@ namespace TEN::Entities::Creatures::TR3
} }
else else
{ {
creature->Enemy = nullptr; TargetNearestEntity(item, creature, FlamethrowerTargetIds, false);
ItemInfo* target = nullptr;
int minDistance = INT_MAX;
for (auto& currentCreature : ActiveCreatures)
{
if (currentCreature->ItemNumber == NO_VALUE || currentCreature->ItemNumber == itemNumber)
continue;
target = &g_Level.Items[currentCreature->ItemNumber];
if (target->ObjectNumber == ID_LARA || target->HitPoints <= 0 || target->ObjectNumber == ID_FLAMETHROWER_BADDY)
continue;
int x = target->Pose.Position.x - item->Pose.Position.x;
int z = target->Pose.Position.z - item->Pose.Position.z;
int distance = SQUARE(z) + SQUARE(x);
if (distance < minDistance)
{
creature->Enemy = target;
minDistance = distance;
}
}
} }
AI_INFO AI; AI_INFO AI;
@ -311,10 +289,8 @@ namespace TEN::Entities::Creatures::TR3
else else
{ {
ThrowFire(itemNumber, FlamethrowerBite, Vector3i(0, (Random::GenerateInt() & 63) + 12, 0)); ThrowFire(itemNumber, FlamethrowerBite, Vector3i(0, (Random::GenerateInt() & 63) + 12, 0));
if (realEnemy) if (realEnemy && realEnemy->ObjectNumber == ID_SEAL_MUTANT)
{ realEnemy->ItemFlags[0]++;
/*code*/
}
} }
SoundEffect(SFX_TR4_FLAME_EMITTER, &item->Pose); SoundEffect(SFX_TR4_FLAME_EMITTER, &item->Pose);
@ -345,10 +321,8 @@ namespace TEN::Entities::Creatures::TR3
else else
{ {
ThrowFire(itemNumber, FlamethrowerBite, Vector3i(0, (GetRandomControl() & 63) + 12, 0)); ThrowFire(itemNumber, FlamethrowerBite, Vector3i(0, (GetRandomControl() & 63) + 12, 0));
if (realEnemy) if (realEnemy && realEnemy->ObjectNumber == ID_SEAL_MUTANT)
{ realEnemy->ItemFlags[0]++;
/*code*/
}
} }
SoundEffect(SFX_TR4_FLAME_EMITTER, &item->Pose); SoundEffect(SFX_TR4_FLAME_EMITTER, &item->Pose);

View file

@ -1,348 +0,0 @@
#include "framework.h"
#include "Objects/TR3/Entity/tr3_raptor.h"
#include "Game/control/box.h"
#include "Game/control/control.h"
#include "Game/control/lot.h"
#include "Game/effects/effects.h"
#include "Game/items.h"
#include "Game/itemdata/creature_info.h"
#include "Game/Lara/lara.h"
#include "Game/misc.h"
#include "Game/Setup.h"
#include "Math/Math.h"
#include "Specific/level.h"
using namespace TEN::Math;
namespace TEN::Entities::Creatures::TR3
{
constexpr auto RAPTOR_ATTACK_DAMAGE = 100;
constexpr auto RAPTOR_BITE_ATTACK_RANGE = SQUARE(585);
constexpr auto RAPTOR_JUMP_ATTACK_RANGE = SQUARE(BLOCK(1.5f));
constexpr auto RAPTOR_RUN_ATTACK_RANGE = SQUARE(BLOCK(1.5f));
constexpr auto RAPTOR_ROAR_CHANCE = 1.0f / 256;
constexpr auto RAPTOR_SWITCH_TARGET_CHANCE = 1.0f / 128;
constexpr auto RAPTOR_WALK_TURN_RATE_MAX = ANGLE(2.0f);
constexpr auto RAPTOR_RUN_TURN_RATE_MAX = ANGLE(2.0f);
constexpr auto RAPTOR_ATTACK_TURN_RATE_MAX = ANGLE(2.0f);
const auto RaptorBite = CreatureBiteInfo(Vector3(0, 66, 318), 22);
const auto RaptorAttackJoints = std::vector<unsigned int>{ 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23 };
enum RaptorState
{
RAPTOR_STATE_DEATH = 0,
RAPTOR_STATE_IDLE = 1,
RAPTOR_STATE_WALK_FORWARD = 2,
RAPTOR_STATE_RUN_FORWARD = 3,
RAPTOR_STATE_JUMP_ATTACK = 4,
RAPTOR_STATE_NONE = 5,
RAPTOR_STATE_ROAR = 6,
RAPTOR_STATE_RUN_BITE_ATTACK = 7,
RAPTOR_STATE_BITE_ATTACK = 8
};
enum RaptorAnim
{
RAPTOR_ANIM_IDLE = 0,
RAPTOR_ANIM_RUN_FORWARD = 1,
RAPTOR_ANIM_RUN_FORWARD_TO_IDLE = 2,
RAPTOR_ANIM_IDLE_TO_RUN_FORWARD = 3,
RAPTOR_ANIM_ROAR = 4,
RAPTOR_ANIM_WALK_FORWARD = 5,
RAPTOR_ANIM_WALK_FORWARD_TO_IDLE = 6,
RAPTOR_ANIM_IDLE_TO_WALK_FORWARD = 7,
RAPTOR_ANIM_RUN_BITE_ATTACK = 8,
RAPTOR_ANIM_DEATH_1 = 9,
RAPTOR_ANIM_DEATH_2 = 10,
RAPTOR_ANIM_JUMP_ATTACK_START = 11,
RAPTOR_ANIM_JUMP_ATTACK_END = 12,
RAPTOR_ANIM_BITE_ATTACK = 13
};
// TODO
enum RaptorFlags
{
};
const std::array RaptorDeathAnims = { RAPTOR_ANIM_DEATH_1, RAPTOR_ANIM_DEATH_2, };
void RaptorControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short angle = 0;
short tilt = 0;
short head = 0;
short neck = 0;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != RAPTOR_STATE_DEATH)
SetAnimation(item, RaptorDeathAnims[Random::GenerateInt(0, (int)RaptorDeathAnims.size() - 1)]);
}
else
{
if (creature->Enemy == nullptr || Random::TestProbability(RAPTOR_SWITCH_TARGET_CHANCE))
{
ItemInfo* nearestItem = nullptr;
float minDistance = INFINITY;
for (auto& currentCreature : ActiveCreatures)
{
if (currentCreature->ItemNumber == NO_VALUE || currentCreature->ItemNumber == itemNumber)
continue;
auto* targetItem = &g_Level.Items[currentCreature->ItemNumber];
int distance = Vector3i::Distance(item->Pose.Position, targetItem->Pose.Position);
if (distance < minDistance && item->HitPoints > 0)
{
nearestItem = targetItem;
minDistance = distance;
}
}
if (nearestItem != nullptr &&
(nearestItem->ObjectNumber != ID_RAPTOR ||
(Random::TestProbability(1 / 30.0f) && minDistance < SQUARE(BLOCK(2)))))
{
creature->Enemy = nearestItem;
}
int distance = Vector3i::Distance(item->Pose.Position, LaraItem->Pose.Position);
if (distance <= minDistance)
creature->Enemy = LaraItem;
}
if (item->AIBits)
GetAITarget(creature);
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true);
if (creature->Mood == MoodType::Bored)
creature->MaxTurn /= 2;
angle = CreatureTurn(item, creature->MaxTurn);
neck = -angle * 6;
switch (item->Animation.ActiveState)
{
case RAPTOR_STATE_IDLE:
creature->MaxTurn = 0;
creature->Flags &= ~1;
if (item->Animation.RequiredState != NO_VALUE)
item->Animation.TargetState = item->Animation.RequiredState;
else if (creature->Flags & 2)
{
creature->Flags &= ~2;
item->Animation.TargetState = RAPTOR_STATE_ROAR;
}
else if (item->TouchBits.Test(RaptorAttackJoints) ||
(AI.distance < RAPTOR_BITE_ATTACK_RANGE && AI.bite))
{
item->Animation.TargetState = RAPTOR_STATE_BITE_ATTACK;
}
else if (AI.bite && AI.distance < RAPTOR_JUMP_ATTACK_RANGE)
item->Animation.TargetState = RAPTOR_STATE_JUMP_ATTACK;
else if (creature->Mood == MoodType::Escape &&
Lara.TargetEntity != item && AI.ahead && !item->HitStatus)
{
item->Animation.TargetState = RAPTOR_STATE_IDLE;
}
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = RAPTOR_STATE_WALK_FORWARD;
else
item->Animation.TargetState = RAPTOR_STATE_RUN_FORWARD;
break;
case RAPTOR_STATE_WALK_FORWARD:
creature->MaxTurn = RAPTOR_WALK_TURN_RATE_MAX;
creature->Flags &= ~1;
if (creature->Mood != MoodType::Bored)
item->Animation.TargetState = RAPTOR_STATE_IDLE;
else if (AI.ahead && Random::TestProbability(RAPTOR_ROAR_CHANCE))
{
item->Animation.TargetState = RAPTOR_STATE_IDLE;
item->Animation.RequiredState = RAPTOR_STATE_ROAR;
creature->Flags &= ~2;
}
break;
case RAPTOR_STATE_RUN_FORWARD:
creature->MaxTurn = RAPTOR_RUN_TURN_RATE_MAX;
creature->Flags &= ~1;
tilt = angle;
if (item->TouchBits.Test(RaptorAttackJoints))
item->Animation.TargetState = RAPTOR_STATE_IDLE;
else if (creature->Flags & 2)
{
item->Animation.TargetState = RAPTOR_STATE_IDLE;
item->Animation.RequiredState = RAPTOR_STATE_ROAR;
creature->Flags &= ~2;
}
else if (AI.bite && AI.distance < RAPTOR_RUN_ATTACK_RANGE)
{
if (item->Animation.TargetState == RAPTOR_STATE_RUN_FORWARD)
{
if (Random::TestProbability(0.25f))
item->Animation.TargetState = RAPTOR_STATE_IDLE;
else
item->Animation.TargetState = RAPTOR_STATE_RUN_BITE_ATTACK;
}
}
else if (AI.ahead && creature->Mood != MoodType::Escape &&
Random::TestProbability(RAPTOR_ROAR_CHANCE))
{
item->Animation.TargetState = RAPTOR_STATE_IDLE;
item->Animation.RequiredState = RAPTOR_STATE_ROAR;
}
else if (creature->Mood == MoodType::Bored ||
(creature->Mood == MoodType::Escape && Lara.TargetEntity != item && AI.ahead))
{
item->Animation.TargetState = RAPTOR_STATE_IDLE;
}
break;
case RAPTOR_STATE_JUMP_ATTACK:
creature->MaxTurn = RAPTOR_ATTACK_TURN_RATE_MAX;
tilt = angle;
if (creature->Enemy->IsLara())
{
if (!(creature->Flags & 1) &&
item->TouchBits.Test(RaptorAttackJoints))
{
DoDamage(creature->Enemy, RAPTOR_ATTACK_DAMAGE);
CreatureEffect(item, RaptorBite, DoBloodSplat);
creature->Flags |= 1;
if (LaraItem->HitPoints <= 0)
creature->Flags |= 2;
item->Animation.RequiredState = RAPTOR_STATE_IDLE;
}
}
else
{
if (!(creature->Flags & 1) && creature->Enemy != nullptr)
{
if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= BLOCK(0.5f))
{
if (creature->Enemy->HitPoints <= 0)
creature->Flags |= 2;
DoDamage(creature->Enemy, 25);
CreatureEffect(item, RaptorBite, DoBloodSplat);
creature->Flags |= 1;
}
}
}
break;
case RAPTOR_STATE_BITE_ATTACK:
creature->MaxTurn = RAPTOR_ATTACK_TURN_RATE_MAX;
tilt = angle;
if (creature->Enemy->IsLara())
{
if (!(creature->Flags & 1) &&
item->TouchBits.Test(RaptorAttackJoints))
{
DoDamage(creature->Enemy, RAPTOR_ATTACK_DAMAGE);
CreatureEffect(item, RaptorBite, DoBloodSplat);
creature->Flags |= 1;
if (LaraItem->HitPoints <= 0)
creature->Flags |= 2;
item->Animation.RequiredState = RAPTOR_STATE_IDLE;
}
}
else
{
if (!(creature->Flags & 1) && creature->Enemy != nullptr)
{
if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= BLOCK(0.5f))
{
if (creature->Enemy->HitPoints <= 0)
creature->Flags |= 2;
DoDamage(creature->Enemy, 25);
CreatureEffect(item, RaptorBite, DoBloodSplat);
creature->Flags |= 1;
}
}
}
break;
case RAPTOR_STATE_RUN_BITE_ATTACK:
creature->MaxTurn = RAPTOR_ATTACK_TURN_RATE_MAX;
tilt = angle;
if (creature->Enemy->IsLara())
{
if (!(creature->Flags & 1) &&
item->TouchBits.Test(RaptorAttackJoints))
{
DoDamage(creature->Enemy, RAPTOR_ATTACK_DAMAGE);
CreatureEffect(item, RaptorBite, DoBloodSplat);
item->Animation.RequiredState = RAPTOR_STATE_RUN_FORWARD;
creature->Flags |= 1;
if (LaraItem->HitPoints <= 0)
creature->Flags |= 2;
}
}
else
{
if (!(creature->Flags & 1) && creature->Enemy != nullptr)
{
if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= BLOCK(0.5f))
{
if (creature->Enemy->HitPoints <= 0)
creature->Flags |= 2;
DoDamage(creature->Enemy, 25);
CreatureEffect(item, RaptorBite, DoBloodSplat);
creature->Flags |= 1;
}
}
}
break;
}
}
CreatureTilt(item, tilt);
CreatureJoint(item, 0, head / 2);
CreatureJoint(item, 1, head / 2);
CreatureJoint(item, 2, neck);
CreatureJoint(item, 3, neck);
CreatureAnimation(itemNumber, angle, tilt);
}
}

View file

@ -998,7 +998,7 @@ namespace TEN::Entities::Vehicles
void KayakToItemCollision(ItemInfo* kayakItem, ItemInfo* laraItem) void KayakToItemCollision(ItemInfo* kayakItem, ItemInfo* laraItem)
{ {
for (auto i : g_Level.Rooms[kayakItem->RoomNumber].neighbors) for (auto i : g_Level.Rooms[kayakItem->RoomNumber].NeighborRoomNumbers)
{ {
if (!g_Level.Rooms[i].Active()) if (!g_Level.Rooms[i].Active())
continue; continue;

View file

@ -281,7 +281,7 @@ namespace TEN::Entities::Vehicles
static void MinecartToEntityCollision(ItemInfo* minecartItem, ItemInfo* laraItem) static void MinecartToEntityCollision(ItemInfo* minecartItem, ItemInfo* laraItem)
{ {
for (auto i : g_Level.Rooms[minecartItem->RoomNumber].neighbors) for (auto i : g_Level.Rooms[minecartItem->RoomNumber].NeighborRoomNumbers)
{ {
if (!g_Level.Rooms[i].Active()) if (!g_Level.Rooms[i].Active())
continue; continue;

View file

@ -8,28 +8,29 @@
#include "Specific/level.h" #include "Specific/level.h"
// Creatures // Creatures
#include "Objects/TR3/Entity/Compsognathus.h" // OK #include "Objects/TR3/Entity/Compsognathus.h"
#include "Objects/TR3/Entity/Lizard.h" // OK #include "Objects/TR3/Entity/Lizard.h"
#include "Objects/TR3/Entity/PunaBoss.h" // OK #include "Objects/TR3/Entity/PunaBoss.h"
#include "Objects/TR3/Entity/Shiva.h" // OK #include "Objects/TR3/Entity/SealMutant.h"
#include "Objects/TR3/Entity/SophiaLeigh.h" // OK #include "Objects/TR3/Entity/Shiva.h"
#include "Objects/TR3/Entity/SophiaLeigh.h"
#include "Objects/TR3/Entity/Raptor.h"
#include "Objects/TR3/Entity/TwinAutoGun.h" #include "Objects/TR3/Entity/TwinAutoGun.h"
#include "Objects/TR3/Entity/WaspMutant.h" // OK #include "Objects/TR3/Entity/WaspMutant.h"
#include "Objects/TR3/Entity/Winston.h" // OK #include "Objects/TR3/Entity/Winston.h"
#include "Objects/TR3/Entity/tr3_tony.h" // OK #include "Objects/TR3/Entity/tr3_tony.h"
#include "Objects/TR3/Entity/tr3_civvy.h" // OK #include "Objects/TR3/Entity/tr3_civvy.h"
#include "Objects/TR3/Entity/tr3_claw_mutant.h" // OK #include "Objects/TR3/Entity/tr3_claw_mutant.h"
#include "Objects/TR3/Entity/tr3_cobra.h" // OK #include "Objects/TR3/Entity/tr3_cobra.h"
#include "Objects/TR3/Entity/FishSwarm.h" // OK #include "Objects/TR3/Entity/FishSwarm.h"
#include "Objects/TR3/Entity/tr3_flamethrower.h" // OK #include "Objects/TR3/Entity/tr3_flamethrower.h"
#include "Objects/TR3/Entity/tr3_monkey.h" // OK #include "Objects/TR3/Entity/tr3_monkey.h"
#include "Objects/TR3/Entity/tr3_mp_gun.h" // OK #include "Objects/TR3/Entity/tr3_mp_gun.h"
#include "Objects/TR3/Entity/tr3_mp_stick.h" // OK #include "Objects/TR3/Entity/tr3_mp_stick.h"
#include "Objects/TR3/Entity/tr3_raptor.h" // OK #include "Objects/TR3/Entity/tr3_scuba_diver.h"
#include "Objects/TR3/Entity/tr3_scuba_diver.h" // OK #include "Objects/TR3/Entity/tr3_tiger.h"
#include "Objects/TR3/Entity/tr3_tiger.h" // OK #include "Objects/TR3/Entity/tr3_trex.h"
#include "Objects/TR3/Entity/tr3_trex.h" // OK #include "Objects/TR3/Entity/tr3_tribesman.h"
#include "Objects/TR3/Entity/tr3_tribesman.h" // OK
// Effects // Effects
#include "Objects/Effects/Boss.h" #include "Objects/Effects/Boss.h"
@ -117,6 +118,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 341; obj->radius = 341;
obj->pivotLength = 600; obj->pivotLength = 600;
obj->intelligent = true; obj->intelligent = true;
obj->LotType = LotType::HumanPlusJump;
obj->SetBoneRotationFlags(20, ROT_Y); obj->SetBoneRotationFlags(20, ROT_Y);
obj->SetBoneRotationFlags(21, ROT_Y); obj->SetBoneRotationFlags(21, ROT_Y);
obj->SetBoneRotationFlags(23, ROT_Y); obj->SetBoneRotationFlags(23, ROT_Y);
@ -412,6 +414,22 @@ static void StartEntity(ObjectInfo* obj)
obj->SetHitEffect(); obj->SetHitEffect();
} }
obj = &Objects[ID_SEAL_MUTANT];
if (obj->loaded)
{
obj->Initialize = InitializeSealMutant;
obj->collision = CreatureCollision;
obj->control = ControlSealMutant;
obj->shadowType = ShadowMode::All;
obj->HitPoints = 50;
obj->radius = 204;
obj->pivotLength = 0;
obj->intelligent = true;
obj->SetBoneRotationFlags(8, ROT_X | ROT_Z); // Torso X/Z
obj->SetBoneRotationFlags(9, ROT_Y); // Head
obj->SetHitEffect();
}
obj = &Objects[ID_WINSTON]; obj = &Objects[ID_WINSTON];
if (obj->loaded) if (obj->loaded)
{ {

View file

@ -229,13 +229,13 @@ namespace TEN::Entities::TR4
{ {
const auto& room = g_Level.Rooms[LaraItem->RoomNumber]; const auto& room = g_Level.Rooms[LaraItem->RoomNumber];
x = room.x + room.xSize * BLOCK(1) / 2 - item.Pose.Position.x; x = room.Position.x + room.XSize * BLOCK(1) / 2 - item.Pose.Position.x;
z = room.z + room.zSize * BLOCK(1) / 2 - item.Pose.Position.z; z = room.Position.z + room.ZSize * BLOCK(1) / 2 - item.Pose.Position.z;
distance = SQUARE(x) + SQUARE(z); distance = SQUARE(x) + SQUARE(z);
dy = abs((distance / MAX_VISIBILITY_DISTANCE) - CLICK(1)); dy = abs((distance / MAX_VISIBILITY_DISTANCE) - CLICK(1));
//Prevent Wraiths to go below floor level //Prevent Wraiths to go below floor level
y = room.y + ((room.maxceiling - room.minfloor) / 4); y = room.Position.y + ((room.TopHeight - room.BottomHeight) / 4);
} }
dy = y - item.Pose.Position.y - dy - CLICK(0.5f); dy = y - item.Pose.Position.y - dy - CLICK(0.5f);

View file

@ -186,7 +186,7 @@ namespace TEN::Entities::TR4
auto pos = GetJointPosition(item, LM_LINARM); auto pos = GetJointPosition(item, LM_LINARM);
auto& room = g_Level.Rooms[item->RoomNumber]; auto& room = g_Level.Rooms[item->RoomNumber];
auto& currentFloor = room.floor[(pos.z - room.z) / BLOCK(1) + (pos.x - room.x) / BLOCK(1) * room.zSize]; auto& currentFloor = room.Sectors[(pos.z - room.Position.z) / BLOCK(1) + (pos.x - room.Position.x) / BLOCK(1) * room.ZSize];
if (currentFloor.Stopper) if (currentFloor.Stopper)
{ {

View file

@ -266,7 +266,7 @@ void UpdateRats()
if (TestEnvironment(ENV_FLAG_WATER, room)) if (TestEnvironment(ENV_FLAG_WATER, room))
{ {
rat->Pose.Position.y = room->maxceiling + 50; rat->Pose.Position.y = room->TopHeight + 50;
rat->Velocity = 16; rat->Velocity = 16;
rat->VerticalVelocity = 0; rat->VerticalVelocity = 0;
@ -274,16 +274,16 @@ void UpdateRats()
{ {
if (!(GetRandomControl() & 0xF)) if (!(GetRandomControl() & 0xF))
SpawnRipple( SpawnRipple(
Vector3(rat->Pose.Position.x, room->maxceiling, rat->Pose.Position.z), Vector3(rat->Pose.Position.x, room->TopHeight, rat->Pose.Position.z),
rat->RoomNumber, rat->RoomNumber,
Random::GenerateFloat(48.0f, 52.0f), Random::GenerateFloat(48.0f, 52.0f),
(int)RippleFlags::SlowFade); (int)RippleFlags::SlowFade);
} }
else else
{ {
AddWaterSparks(rat->Pose.Position.x, room->maxceiling, rat->Pose.Position.z, 16); AddWaterSparks(rat->Pose.Position.x, room->TopHeight, rat->Pose.Position.z, 16);
SpawnRipple( SpawnRipple(
Vector3(rat->Pose.Position.x, room->maxceiling, rat->Pose.Position.z), Vector3(rat->Pose.Position.x, room->TopHeight, rat->Pose.Position.z),
rat->RoomNumber, rat->RoomNumber,
Random::GenerateFloat(48.0f, 52.0f), Random::GenerateFloat(48.0f, 52.0f),
(int)RippleFlags::SlowFade); (int)RippleFlags::SlowFade);

View file

@ -232,9 +232,9 @@ void UpdateSpiders()
spider->VerticalVelocity = 0; spider->VerticalVelocity = 0;
} }
if (spider->Pose.Position.y < g_Level.Rooms[spider->RoomNumber].maxceiling + 50) if (spider->Pose.Position.y < g_Level.Rooms[spider->RoomNumber].TopHeight + 50)
{ {
spider->Pose.Position.y = g_Level.Rooms[spider->RoomNumber].maxceiling + 50; spider->Pose.Position.y = g_Level.Rooms[spider->RoomNumber].TopHeight + 50;
spider->Pose.Orientation.y += -ANGLE(180.0f); spider->Pose.Orientation.y += -ANGLE(180.0f);
spider->VerticalVelocity = 1; spider->VerticalVelocity = 1;
} }

View file

@ -337,7 +337,7 @@ namespace TEN::Entities::Creatures::TR5
auto pos = GetJointPosition(item, 16); auto pos = GetJointPosition(item, 16);
auto* floor = GetSector(room, pos.x - room->x, pos.z - room->z); auto* floor = GetSector(room, pos.x - room->Position.x, pos.z - room->Position.z);
if (floor->Stopper) if (floor->Stopper)
{ {
for (int i = 0; i < room->mesh.size(); i++) for (int i = 0; i < room->mesh.size(); i++)

View file

@ -363,9 +363,9 @@ namespace TEN::Entities::Creatures::TR5
auto* room = &g_Level.Rooms[roomNumber]; auto* room = &g_Level.Rooms[roomNumber];
int x = room->x + (creature->Tosspad / 256 & 0xFF) * BLOCK(1) + 512; int x = room->Position.x + (creature->Tosspad / 256 & 0xFF) * BLOCK(1) + 512;
int y = room->minfloor + floorHeight; int y = room->BottomHeight + floorHeight;
int z = room->z + (creature->Tosspad & 0xFF) * BLOCK(1) + 512; int z = room->Position.z + (creature->Tosspad & 0xFF) * BLOCK(1) + 512;
TestTriggers(x, y, z, roomNumber, true); TestTriggers(x, y, z, roomNumber, true);

View file

@ -543,7 +543,7 @@ namespace TEN::Entities::Creatures::TR5
pos = GetJointPosition(item, 16); pos = GetJointPosition(item, 16);
auto* room = &g_Level.Rooms[item->RoomNumber]; auto* room = &g_Level.Rooms[item->RoomNumber];
FloorInfo* floor = GetSector(room, pos.x - room->x, pos.z - room->z); FloorInfo* floor = GetSector(room, pos.x - room->Position.x, pos.z - room->Position.z);
// If floor is stopped, then try to find static meshes and shatter them, activating heavy triggers below // If floor is stopped, then try to find static meshes and shatter them, activating heavy triggers below
if (floor->Stopper) if (floor->Stopper)
@ -823,9 +823,9 @@ namespace TEN::Entities::Creatures::TR5
short floorHeight = item->ItemFlags[2] & 0xFF00; short floorHeight = item->ItemFlags[2] & 0xFF00;
auto* room = &g_Level.Rooms[roomNumber]; auto* room = &g_Level.Rooms[roomNumber];
int x = room->x + (creature->Tosspad / 256 & 0xFF) * BLOCK(1) + 512; int x = room->Position.x + (creature->Tosspad / 256 & 0xFF) * BLOCK(1) + 512;
int y = room->minfloor + floorHeight; int y = room->BottomHeight + floorHeight;
int z = room->z + (creature->Tosspad & 0xFF) * BLOCK(1) + 512; int z = room->Position.z + (creature->Tosspad & 0xFF) * BLOCK(1) + 512;
TestTriggers(x, y, z, roomNumber, true); TestTriggers(x, y, z, roomNumber, true);
} }

View file

@ -82,7 +82,7 @@ namespace TEN::Entities::Generic
return; return;
} }
int distToPortal = *&g_Level.Rooms[item->RoomNumber].maxceiling - item->Pose.Position.y; int distToPortal = *&g_Level.Rooms[item->RoomNumber].TopHeight - item->Pose.Position.y;
if (distToPortal <= speed) if (distToPortal <= speed)
UpdateBridgeItem(*item); UpdateBridgeItem(*item);

View file

@ -14,10 +14,10 @@ void InitializeSmashObject(short itemNumber)
item->Flags = 0; item->Flags = 0;
item->MeshBits = 1; item->MeshBits = 1;
auto* room = &g_Level.Rooms[item->RoomNumber]; auto& room = g_Level.Rooms[item->RoomNumber];
// NOTE: Avoids crash when attempting to access Boxes[] array while box is equal to NO_VALUE. -- TokyoSU 2022.12.20 // NOTE: Avoids crash when attempting to access Boxes[] array while box is equal to NO_VALUE. -- TokyoSU 2022.12.20
FloorInfo* floor = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z); FloorInfo* floor = GetSector(&room, item->Pose.Position.x - room.Position.x, item->Pose.Position.z - room.Position.z);
if (floor->PathfindingBoxID == NO_VALUE) if (floor->PathfindingBoxID == NO_VALUE)
{ {
TENLog("Smash object with ID " + std::to_string(itemNumber) + " may be inside a wall." , LogLevel::Warning); TENLog("Smash object with ID " + std::to_string(itemNumber) + " may be inside a wall." , LogLevel::Warning);
@ -34,9 +34,9 @@ void SmashObject(short itemNumber)
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* room = &g_Level.Rooms[item->RoomNumber]; auto* room = &g_Level.Rooms[item->RoomNumber];
int sector = ((item->Pose.Position.z - room->z) / 1024) + room->zSize * ((item->Pose.Position.x - room->x) / 1024); int sector = ((item->Pose.Position.z - room->Position.z) / 1024) + room->ZSize * ((item->Pose.Position.x - room->Position.z) / 1024);
auto* box = &g_Level.PathfindingBoxes[room->floor[sector].PathfindingBoxID]; auto* box = &g_Level.PathfindingBoxes[room->Sectors[sector].PathfindingBoxID];
if (box->flags & 0x8000) if (box->flags & 0x8000)
box->flags &= ~BOX_BLOCKED; box->flags &= ~BOX_BLOCKED;

View file

@ -232,10 +232,10 @@ enum GAME_OBJECT_ID : short
ID_CLAW_MUTANT, ID_CLAW_MUTANT,
ID_WASP_MUTANT, ID_WASP_MUTANT,
ID_TWIN_AUTO_GUN, ID_TWIN_AUTO_GUN,
ID_SKATEBOARD = 296, ID_SKATEBOARD,
ID_SKATEBOARD_KID, ID_SKATEBOARD_KID,
ID_WINSTON, ID_WINSTON,
//299 ID_SEAL_MUTANT,
ID_SPRINGBOARD = 320, ID_SPRINGBOARD = 320,
ID_ROLLING_SPINDLE, ID_ROLLING_SPINDLE,

View file

@ -212,14 +212,14 @@ namespace TEN::Renderer
r->ItemsToDraw.reserve(MAX_ITEMS_DRAW); r->ItemsToDraw.reserve(MAX_ITEMS_DRAW);
r->EffectsToDraw.reserve(MAX_ITEMS_DRAW); r->EffectsToDraw.reserve(MAX_ITEMS_DRAW);
Vector3 boxMin = Vector3(room.x + BLOCK(1), room.maxceiling - CLICK(1), room.z + BLOCK(1)); Vector3 boxMin = Vector3(room.Position.x + BLOCK(1), room.TopHeight - CLICK(1), room.Position.z + BLOCK(1));
Vector3 boxMax = Vector3(room.x + (room.xSize - 1) * BLOCK(1), room.minfloor + CLICK(1), room.z + (room.zSize - 1) * BLOCK(1)); Vector3 boxMax = Vector3(room.Position.x + (room.XSize - 1) * BLOCK(1), room.BottomHeight + CLICK(1), room.Position.z + (room.ZSize - 1) * BLOCK(1));
Vector3 center = (boxMin + boxMax) / 2.0f; Vector3 center = (boxMin + boxMax) / 2.0f;
Vector3 extents = boxMax - center; Vector3 extents = boxMax - center;
r->BoundingBox = BoundingBox(center, extents); r->BoundingBox = BoundingBox(center, extents);
r->Neighbors.clear(); r->Neighbors.clear();
for (int j : room.neighbors) for (int j : room.NeighborRoomNumbers)
if (g_Level.Rooms[j].Active()) if (g_Level.Rooms[j].Active())
r->Neighbors.push_back(j); r->Neighbors.push_back(j);
@ -238,9 +238,9 @@ namespace TEN::Renderer
for (int k = 0; k < 4; k++) for (int k = 0; k < 4; k++)
{ {
door->AbsoluteVertices[k] = Vector4( door->AbsoluteVertices[k] = Vector4(
room.x + oldDoor->vertices[k].x, room.Position.x + oldDoor->vertices[k].x,
room.y + oldDoor->vertices[k].y, room.Position.y + oldDoor->vertices[k].y,
room.z + oldDoor->vertices[k].z, room.Position.z + oldDoor->vertices[k].z,
1.0f); 1.0f);
} }
} }
@ -312,9 +312,9 @@ namespace TEN::Renderer
Vertex* vertex = &_roomsVertices[lastVertex]; Vertex* vertex = &_roomsVertices[lastVertex];
int index = poly.indices[k]; int index = poly.indices[k];
vertex->Position.x = room.x + room.positions[index].x; vertex->Position.x = room.Position.x + room.positions[index].x;
vertex->Position.y = room.y + room.positions[index].y; vertex->Position.y = room.Position.y + room.positions[index].y;
vertex->Position.z = room.z + room.positions[index].z; vertex->Position.z = room.Position.z + room.positions[index].z;
bucket.Centre += vertex->Position; bucket.Centre += vertex->Position;

View file

@ -1725,7 +1725,7 @@ namespace TEN::Renderer
//RenderSimpleSceneToParaboloid(&_roomAmbientMapsCache[ambientMapCacheIndex].Back, LaraItem->Pose.Position.ToVector3(), -1); //RenderSimpleSceneToParaboloid(&_roomAmbientMapsCache[ambientMapCacheIndex].Back, LaraItem->Pose.Position.ToVector3(), -1);
// Bind and clear render target. // Bind and clear render target.
_context->ClearRenderTargetView(_renderTarget.RenderTargetView.Get(), _debugPage == RendererDebugPage::WireframeMode ? Colors::White : Colors::Black); _context->ClearRenderTargetView(_renderTarget.RenderTargetView.Get(), _debugPage == RendererDebugPage::WireframeMode ? Colors::DimGray : Colors::Black);
_context->ClearDepthStencilView(_renderTarget.DepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); _context->ClearDepthStencilView(_renderTarget.DepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
// Reset viewport and scissor // Reset viewport and scissor

View file

@ -67,8 +67,23 @@ namespace TEN::Renderer
inline int SF_Center(bool selected = false) { return (int)PrintStringFlags::Outline | (int)PrintStringFlags::Center | (selected ? (int)PrintStringFlags::Blink : 0); } inline int SF_Center(bool selected = false) { return (int)PrintStringFlags::Outline | (int)PrintStringFlags::Center | (selected ? (int)PrintStringFlags::Blink : 0); }
// Helper functions to get specific generic strings // Helper functions to get specific generic strings
inline const char* Str_Enabled(bool enabled = false) { return g_GameFlow->GetString(enabled ? STRING_ENABLED : STRING_DISABLED); } inline const std::string Str_Enabled(bool enabled = false) { return g_GameFlow->GetString(enabled ? STRING_ENABLED : STRING_DISABLED); }
inline const char* Str_LoadSave(bool save = false) { return g_GameFlow->GetString(save ? STRING_SAVE_GAME : STRING_LOAD_GAME); } inline const std::string Str_LoadSave(bool save = false) { return g_GameFlow->GetString(save ? STRING_SAVE_GAME : STRING_LOAD_GAME); }
inline const std::string Str_MenuOptionLoopingMode(MenuOptionLoopingMode loopingMode)
{
switch (loopingMode)
{
default:
case MenuOptionLoopingMode::AllMenus:
return g_GameFlow->GetString(STRING_MENU_OPT_LOOP_ALL_MENUS);
case MenuOptionLoopingMode::SaveLoadOnly:
return g_GameFlow->GetString(STRING_MENU_OPT_LOOP_SAVE_LOAD_ONLY);
case MenuOptionLoopingMode::Disabled:
return g_GameFlow->GetString(STRING_MENU_OPT_LOOP_DISABLED);
}
}
// These bars are only used in menus. // These bars are only used in menus.
TEN::Renderer::RendererHudBar* g_MusicVolumeBar = nullptr; TEN::Renderer::RendererHudBar* g_MusicVolumeBar = nullptr;
@ -261,9 +276,9 @@ namespace TEN::Renderer
AddString(MenuRightSideEntry, y, std::to_string(g_Gui.GetCurrentSettings().Configuration.MouseSensitivity).c_str(), PRINTSTRING_COLOR_WHITE, SF(titleOption == 9)); AddString(MenuRightSideEntry, y, std::to_string(g_Gui.GetCurrentSettings().Configuration.MouseSensitivity).c_str(), PRINTSTRING_COLOR_WHITE, SF(titleOption == 9));
GetNextLinePosition(&y); GetNextLinePosition(&y);
// Mouse smoothing // Menu option looping
AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_MOUSE_SMOOTHING), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 10)); AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_MENU_OPT_LOOP), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 10));
AddString(MenuRightSideEntry, y, std::to_string(g_Gui.GetCurrentSettings().Configuration.MouseSmoothing).c_str(), PRINTSTRING_COLOR_WHITE, SF(titleOption == 10)); AddString(MenuRightSideEntry, y, Str_MenuOptionLoopingMode(g_Gui.GetCurrentSettings().Configuration.MenuOptionLoopingMode), PRINTSTRING_COLOR_WHITE, SF(titleOption == 10));
GetNextBlockPosition(&y); GetNextBlockPosition(&y);
// Apply // Apply
@ -493,6 +508,14 @@ namespace TEN::Renderer
GetNextLinePosition(&y); GetNextLinePosition(&y);
selectedOption++; selectedOption++;
// Home Level
if (g_GameFlow->IsHomeLevelEnabled())
{
AddString(MenuCenterEntry, y, g_GameFlow->GetString(STRING_HOME_LEVEL), PRINTSTRING_COLOR_WHITE, SF_Center(titleOption == selectedOption));
GetNextLinePosition(&y);
selectedOption++;
}
// Load game // Load game
if (g_GameFlow->IsLoadSaveEnabled()) if (g_GameFlow->IsLoadSaveEnabled())
{ {
@ -523,13 +546,15 @@ namespace TEN::Renderer
AddString(MenuCenterEntry, 26, g_GameFlow->GetString(STRING_SELECT_LEVEL), PRINTSTRING_COLOR_ORANGE, SF_Center()); AddString(MenuCenterEntry, 26, g_GameFlow->GetString(STRING_SELECT_LEVEL), PRINTSTRING_COLOR_ORANGE, SF_Center());
GetNextBlockPosition(&y); GetNextBlockPosition(&y);
// Level listing (starts with 1 because 0 is always title) // Level 0 is always Title Level and level 1 might be Home Level.
for (int i = 1; i < g_GameFlow->GetNumLevels(); i++, selectedOption++) for (int i = (g_GameFlow->IsHomeLevelEnabled() ? 2 : 1); i < g_GameFlow->GetNumLevels(); i++, selectedOption++)
{ {
AddString(MenuCenterEntry, y, g_GameFlow->GetString(g_GameFlow->GetLevel(i)->NameStringKey.c_str()), AddString(
MenuCenterEntry, y, g_GameFlow->GetString(g_GameFlow->GetLevel(i)->NameStringKey.c_str()),
PRINTSTRING_COLOR_WHITE, SF_Center(titleOption == selectedOption)); PRINTSTRING_COLOR_WHITE, SF_Center(titleOption == selectedOption));
GetNextNarrowLinePosition(&y); GetNextNarrowLinePosition(&y);
} }
break; break;
case Menu::Options: case Menu::Options:
@ -1225,8 +1250,8 @@ namespace TEN::Renderer
PrintDebugMessage("RoomNumber: %d", LaraItem->RoomNumber); PrintDebugMessage("RoomNumber: %d", LaraItem->RoomNumber);
PrintDebugMessage("PathfindingBoxID: %d", LaraItem->BoxNumber); PrintDebugMessage("PathfindingBoxID: %d", LaraItem->BoxNumber);
PrintDebugMessage("WaterSurfaceDist: %d", Lara.Context.WaterSurfaceDist); PrintDebugMessage("WaterSurfaceDist: %d", Lara.Context.WaterSurfaceDist);
PrintDebugMessage("Room Position: %d, %d, %d, %d", room.x, room.z, room.x + BLOCK(room.xSize), room.z + BLOCK(room.zSize)); PrintDebugMessage("Room Position: %d, %d, %d, %d", room.Position.z, room.Position.z, room.Position.z + BLOCK(room.XSize), room.Position.z + BLOCK(room.ZSize));
PrintDebugMessage("Room.y, minFloor, maxCeiling: %d, %d, %d ", room.y, room.minfloor, room.maxceiling); PrintDebugMessage("Room.y, minFloor, maxCeiling: %d, %d, %d ", room.Position.y, room.BottomHeight, room.TopHeight);
PrintDebugMessage("Camera Position: %d, %d, %d", Camera.pos.x, Camera.pos.y, Camera.pos.z); PrintDebugMessage("Camera Position: %d, %d, %d", Camera.pos.x, Camera.pos.y, Camera.pos.z);
PrintDebugMessage("Camera LookAt: %d, %d, %d", Camera.target.x, Camera.target.y, Camera.target.z); PrintDebugMessage("Camera LookAt: %d, %d, %d", Camera.target.x, Camera.target.y, Camera.target.z);
PrintDebugMessage("Camera RoomNumber: %d", Camera.pos.RoomNumber); PrintDebugMessage("Camera RoomNumber: %d", Camera.pos.RoomNumber);

View file

@ -35,6 +35,7 @@ public:
virtual bool IsMassPickupEnabled() const = 0; virtual bool IsMassPickupEnabled() const = 0;
virtual bool IsPointFilterEnabled() const = 0; virtual bool IsPointFilterEnabled() const = 0;
virtual bool IsLaraInTitleEnabled() const = 0; virtual bool IsLaraInTitleEnabled() const = 0;
virtual bool IsHomeLevelEnabled() const = 0;
virtual bool IsLoadSaveEnabled() const = 0; virtual bool IsLoadSaveEnabled() const = 0;
virtual bool HasCrawlExtended() const = 0; virtual bool HasCrawlExtended() const = 0;
virtual bool HasCrouchRoll() const = 0; virtual bool HasCrouchRoll() const = 0;

View file

@ -3,7 +3,7 @@
// std::string ID macros // std::string ID macros
#define STRING_WINDOW_TITLE "window_title" #define STRING_WINDOW_TITLE "window_title"
#define STRING_PASSPORT "passport" #define STRING_PASSPORT "passport"
#define STRING_LARA_HOME "lara_home" #define STRING_HOME_LEVEL "home_level"
#define STRING_CONTROLS "controls" #define STRING_CONTROLS "controls"
#define STRING_DISPLAY "display" #define STRING_DISPLAY "display"
#define STRING_OTHER_SETTINGS "other_settings" #define STRING_OTHER_SETTINGS "other_settings"
@ -86,8 +86,11 @@
#define STRING_RUMBLE "rumble" #define STRING_RUMBLE "rumble"
#define STRING_THUMBSTICK_CAMERA "thumbstick_camera" #define STRING_THUMBSTICK_CAMERA "thumbstick_camera"
#define STRING_SUBTITLES "subtitles" #define STRING_SUBTITLES "subtitles"
#define STRING_MENU_OPT_LOOP "menu_option_looping"
#define STRING_MENU_OPT_LOOP_ALL_MENUS "menu_option_looping_all_menus"
#define STRING_MENU_OPT_LOOP_DISABLED "menu_option_looping_disabled"
#define STRING_MENU_OPT_LOOP_SAVE_LOAD_ONLY "menu_option_looping_save_load_only"
#define STRING_MOUSE_SENSITIVITY "mouse_sensitivity" #define STRING_MOUSE_SENSITIVITY "mouse_sensitivity"
#define STRING_MOUSE_SMOOTHING "mouse_smoothing"
#define STRING_ACTIONS_FORWARD "actions_forward" #define STRING_ACTIONS_FORWARD "actions_forward"
#define STRING_ACTIONS_BACKWARD "actions_backward" #define STRING_ACTIONS_BACKWARD "actions_backward"
#define STRING_ACTIONS_LEFT "actions_left" #define STRING_ACTIONS_LEFT "actions_left"

View file

@ -223,6 +223,7 @@ static constexpr char ScriptReserved_EnableFlyCheat[] = "EnableFlyCheat";
static constexpr char ScriptReserved_EnableMassPickup[] = "EnableMassPickup"; static constexpr char ScriptReserved_EnableMassPickup[] = "EnableMassPickup";
static constexpr char ScriptReserved_EnableLaraInTitle[] = "EnableLaraInTitle"; static constexpr char ScriptReserved_EnableLaraInTitle[] = "EnableLaraInTitle";
static constexpr char ScriptReserved_EnableLevelSelect[] = "EnableLevelSelect"; static constexpr char ScriptReserved_EnableLevelSelect[] = "EnableLevelSelect";
static constexpr char ScriptReserved_EnableHomeLevel[] = "EnableHomeLevel";
static constexpr char ScriptReserved_EnableLoadSave[] = "EnableLoadSave"; static constexpr char ScriptReserved_EnableLoadSave[] = "EnableLoadSave";
static constexpr char ScriptReserved_EnablePointFilter[] = "EnablePointFilter"; static constexpr char ScriptReserved_EnablePointFilter[] = "EnablePointFilter";

View file

@ -77,21 +77,23 @@ Must be true or false
*/ */
tableFlow.set_function(ScriptReserved_EnableLevelSelect, &FlowHandler::EnableLevelSelect, this); tableFlow.set_function(ScriptReserved_EnableLevelSelect, &FlowHandler::EnableLevelSelect, this);
/*** Enable or disable saving and loading of savegames. /// Enable or disable Home Level entry in the main menu.
@function EnableLoadSave // @function EnableHomeLevel()
@tparam bool enabled true or false. // @tparam bool enabled True or false.
*/ tableFlow.set_function(ScriptReserved_EnableHomeLevel, &FlowHandler::EnableHomeLevel, this);
/// Enable or disable saving and loading of savegames.
// @function EnableLoadSave()
// @tparam bool enabled True or false.
tableFlow.set_function(ScriptReserved_EnableLoadSave, &FlowHandler::EnableLoadSave, this); tableFlow.set_function(ScriptReserved_EnableLoadSave, &FlowHandler::EnableLoadSave, this);
/*** gameflow.lua or level scripts. /*** gameflow.lua or level scripts.
@section FlowluaOrScripts @section FlowluaOrScripts
*/ */
/*** Enable or disable DOZY mode (fly cheat). /// Enable or disable the fly cheat.
Must be true or false // @function EnableFlyCheat()
@function EnableFlyCheat // @tparam bool enabled True or false.
@tparam bool enabled true or false
*/
tableFlow.set_function(ScriptReserved_EnableFlyCheat, &FlowHandler::EnableFlyCheat, this); tableFlow.set_function(ScriptReserved_EnableFlyCheat, &FlowHandler::EnableFlyCheat, this);
/*** Enable or disable point texture filter. /*** Enable or disable point texture filter.
@ -537,9 +539,9 @@ bool FlowHandler::IsFlyCheatEnabled() const
return FlyCheat; return FlyCheat;
} }
void FlowHandler::EnableFlyCheat(bool flyCheat) void FlowHandler::EnableFlyCheat(bool enable)
{ {
FlyCheat = flyCheat; FlyCheat = enable;
} }
bool FlowHandler::IsPointFilterEnabled() const bool FlowHandler::IsPointFilterEnabled() const
@ -547,9 +549,9 @@ bool FlowHandler::IsPointFilterEnabled() const
return PointFilter; return PointFilter;
} }
void FlowHandler::EnablePointFilter(bool pointFilter) void FlowHandler::EnablePointFilter(bool enable)
{ {
PointFilter = pointFilter; PointFilter = enable;
} }
bool FlowHandler::IsMassPickupEnabled() const bool FlowHandler::IsMassPickupEnabled() const
@ -557,9 +559,9 @@ bool FlowHandler::IsMassPickupEnabled() const
return MassPickup; return MassPickup;
} }
void FlowHandler::EnableMassPickup(bool massPickup) void FlowHandler::EnableMassPickup(bool enable)
{ {
MassPickup = massPickup; MassPickup = enable;
} }
bool FlowHandler::IsLaraInTitleEnabled() const bool FlowHandler::IsLaraInTitleEnabled() const
@ -567,14 +569,24 @@ bool FlowHandler::IsLaraInTitleEnabled() const
return LaraInTitle; return LaraInTitle;
} }
void FlowHandler::EnableLaraInTitle(bool laraInTitle) void FlowHandler::EnableLaraInTitle(bool enable)
{ {
LaraInTitle = laraInTitle; LaraInTitle = enable;
} }
void FlowHandler::EnableLevelSelect(bool levelSelect) void FlowHandler::EnableLevelSelect(bool enable)
{ {
LevelSelect = levelSelect; LevelSelect = enable;
}
bool FlowHandler::IsHomeLevelEnabled() const
{
return HomeLevel;
}
void FlowHandler::EnableHomeLevel(bool enable)
{
HomeLevel = enable;
} }
bool FlowHandler::IsLoadSaveEnabled() const bool FlowHandler::IsLoadSaveEnabled() const
@ -582,9 +594,9 @@ bool FlowHandler::IsLoadSaveEnabled() const
return LoadSave; return LoadSave;
} }
void FlowHandler::EnableLoadSave(bool loadSave) void FlowHandler::EnableLoadSave(bool enable)
{ {
LoadSave = loadSave; LoadSave = enable;
} }
void FlowHandler::PrepareInventoryObjects() void FlowHandler::PrepareInventoryObjects()
@ -674,12 +686,20 @@ bool FlowHandler::DoFlow()
break; break;
case GameStatus::NewGame: case GameStatus::NewGame:
CurrentLevel = (SelectedLevelForNewGame != 0 ? SelectedLevelForNewGame : 1); // NOTE: 0 reserved for title level and 1 reserved for home level.
CurrentLevel = (SelectedLevelForNewGame != 0) ? SelectedLevelForNewGame : (IsHomeLevelEnabled() ? 2 : 1);
RequiredStartPos = 0; RequiredStartPos = 0;
SelectedLevelForNewGame = 0; SelectedLevelForNewGame = 0;
InitializeGame = true; InitializeGame = true;
break; break;
case GameStatus::HomeLevel:
CurrentLevel = 1;
RequiredStartPos = 0;
InitializeGame = true;
break;
case GameStatus::LoadGame: case GameStatus::LoadGame:
// Load header of savegame to get level to load. // Load header of savegame to get level to load.
SaveGame::LoadHeader(SelectedSaveGame, &header); SaveGame::LoadHeader(SelectedSaveGame, &header);

View file

@ -30,6 +30,7 @@ public:
int FogOutDistance = 0; int FogOutDistance = 0;
bool LevelSelect = true; bool LevelSelect = true;
bool HomeLevel = false;
bool LoadSave = true; bool LoadSave = true;
bool FlyCheat = true; bool FlyCheat = true;
bool PointFilter = false; bool PointFilter = false;
@ -74,17 +75,19 @@ public:
void SetTitleScreenImagePath(const std::string& path); void SetTitleScreenImagePath(const std::string& path);
void SetTotalSecretCount(int secretsNumber); void SetTotalSecretCount(int secretsNumber);
bool IsFlyCheatEnabled() const; bool IsFlyCheatEnabled() const;
void EnableFlyCheat(bool flyCheat); void EnableFlyCheat(bool enable);
bool IsPointFilterEnabled() const; bool IsPointFilterEnabled() const;
void EnablePointFilter(bool pointFilter); void EnablePointFilter(bool enable);
bool IsMassPickupEnabled() const; bool IsMassPickupEnabled() const;
void EnableMassPickup(bool massPickup); void EnableMassPickup(bool enable);
bool IsLaraInTitleEnabled() const; bool IsLaraInTitleEnabled() const;
void EnableLaraInTitle(bool laraInTitle); void EnableLaraInTitle(bool enable);
bool IsLevelSelectEnabled() const; bool IsLevelSelectEnabled() const;
void EnableLevelSelect(bool laraInTitle); void EnableLevelSelect(bool enable);
bool IsHomeLevelEnabled() const;
void EnableHomeLevel(bool enable);
bool IsLoadSaveEnabled() const; bool IsLoadSaveEnabled() const;
void EnableLoadSave(bool loadSave); void EnableLoadSave(bool enable);
bool HasCrawlExtended() const override { return Anims.HasCrawlExtended; } bool HasCrawlExtended() const override { return Anims.HasCrawlExtended; }
bool HasCrouchRoll() const override { return Anims.HasCrouchRoll; } bool HasCrouchRoll() const override { return Anims.HasCrouchRoll; }

View file

@ -239,7 +239,7 @@ The following constants are inside ObjID.
SKATEBOARD SKATEBOARD
SKATEBOARD_KID SKATEBOARD_KID
WINSTON WINSTON
ARMY_WINSTON SEAL_MUTANT
SPRINGBOARD SPRINGBOARD
ROLLING_SPINDLE ROLLING_SPINDLE
DISK_SHOOTER DISK_SHOOTER
@ -825,6 +825,7 @@ The following constants are inside ObjID.
MESHSWAP_ROMAN_GOD2 MESHSWAP_ROMAN_GOD2
MESHSWAP_MONKEY_MEDIPACK MESHSWAP_MONKEY_MEDIPACK
MESHSWAP_MONKEY_KEY MESHSWAP_MONKEY_KEY
MESHSWAP_WINSTON_ARMY_OUTFIT
ANIMATING1 ANIMATING1
ANIMATING2 ANIMATING2
ANIMATING3 ANIMATING3
@ -1415,6 +1416,7 @@ static const std::unordered_map<std::string, GAME_OBJECT_ID> kObjIDs {
{ "SKATEBOARD", ID_SKATEBOARD }, { "SKATEBOARD", ID_SKATEBOARD },
{ "SKATEBOARD_KID", ID_SKATEBOARD_KID }, { "SKATEBOARD_KID", ID_SKATEBOARD_KID },
{ "WINSTON", ID_WINSTON }, { "WINSTON", ID_WINSTON },
{ "SEAL_MUTANT", ID_SEAL_MUTANT },
{ "SPRINGBOARD", ID_SPRINGBOARD }, { "SPRINGBOARD", ID_SPRINGBOARD },
{ "ROLLING_SPINDLE", ID_ROLLING_SPINDLE }, { "ROLLING_SPINDLE", ID_ROLLING_SPINDLE },
{ "DISK_SHOOTER", ID_DISK_SHOOTER }, { "DISK_SHOOTER", ID_DISK_SHOOTER },

View file

@ -111,7 +111,7 @@ private:
auto room = std::get<std::reference_wrapper<ROOM_INFO>>(val).get(); auto room = std::get<std::reference_wrapper<ROOM_INFO>>(val).get();
if (std::any_of(room.tags.begin(), room.tags.end(), if (std::any_of(room.Tags.begin(), room.Tags.end(),
[&tag](const std::string& value) { return value == tag; })) [&tag](const std::string& value) { return value == tag; }))
{ {
rooms.push_back(GetByName<Room, ScriptReserved_Room>(key)); rooms.push_back(GetByName<Room, ScriptReserved_Room>(key));

View file

@ -104,7 +104,7 @@ void Room::SetReverbType(ReverbType reverb)
std::string Room::GetName() const std::string Room::GetName() const
{ {
return m_room.name; return m_room.Name;
} }
void Room::SetName(const std::string& name) void Room::SetName(const std::string& name)
@ -115,8 +115,8 @@ void Room::SetName(const std::string& name)
// Remove old name if it already exists. // Remove old name if it already exists.
if (s_callbackSetName(name, m_room)) if (s_callbackSetName(name, m_room))
{ {
s_callbackRemoveName(m_room.name); s_callbackRemoveName(m_room.Name);
m_room.name = name; m_room.Name = name;
} }
else else
{ {
@ -144,10 +144,10 @@ void Room::SetFlag(RoomEnvFlags flag, bool value)
bool Room::IsTagPresent(const std::string& tag) const bool Room::IsTagPresent(const std::string& tag) const
{ {
if (m_room.tags.empty()) if (m_room.Tags.empty())
return false; return false;
return std::any_of( return std::any_of(
m_room.tags.begin(), m_room.tags.end(), m_room.Tags.begin(), m_room.Tags.end(),
[&tag](const std::string& value) { return (value == tag); }); [&tag](const std::string& value) { return (value == tag); });
} }

View file

@ -463,10 +463,9 @@ namespace TEN::Input
(((rawAxes.x - -DISPLAY_SPACE_RES.x) * 2) / (DISPLAY_SPACE_RES.x - -DISPLAY_SPACE_RES.x)) - 1.0f, (((rawAxes.x - -DISPLAY_SPACE_RES.x) * 2) / (DISPLAY_SPACE_RES.x - -DISPLAY_SPACE_RES.x)) - 1.0f,
(((rawAxes.y - -DISPLAY_SPACE_RES.y) * 2) / (DISPLAY_SPACE_RES.y - -DISPLAY_SPACE_RES.y)) - 1.0f); (((rawAxes.y - -DISPLAY_SPACE_RES.y) * 2) / (DISPLAY_SPACE_RES.y - -DISPLAY_SPACE_RES.y)) - 1.0f);
// Apply sensitivity and smoothing. // Apply sensitivity.
float sensitivity = (g_Configuration.MouseSensitivity * 0.1f) + 0.4f; float sensitivity = (g_Configuration.MouseSensitivity * 0.1f) + 0.4f;
float smoothing = 1.0f - (g_Configuration.MouseSmoothing * 0.1f); normAxes *= sensitivity;
normAxes *= sensitivity * smoothing;
// Set mouse axis values. // Set mouse axis values.
AxisMap[(int)InputAxis::Mouse] = normAxes; AxisMap[(int)InputAxis::Mouse] = normAxes;

View file

@ -257,7 +257,7 @@ bool SaveConfiguration()
// Set Input keys. // Set Input keys.
if (SetDWORDRegKey(inputKey, REGKEY_MOUSE_SENSITIVITY, g_Configuration.MouseSensitivity) != ERROR_SUCCESS || if (SetDWORDRegKey(inputKey, REGKEY_MOUSE_SENSITIVITY, g_Configuration.MouseSensitivity) != ERROR_SUCCESS ||
SetDWORDRegKey(inputKey, REGKEY_MOUSE_SMOOTHING, g_Configuration.MouseSmoothing) != ERROR_SUCCESS) SetDWORDRegKey(inputKey, REGKEY_ENABLE_MENU_OPTION_LOOPING, (int)g_Configuration.MenuOptionLoopingMode) != ERROR_SUCCESS)
{ {
RegCloseKey(rootKey); RegCloseKey(rootKey);
RegCloseKey(graphicsKey); RegCloseKey(graphicsKey);
@ -330,7 +330,7 @@ void InitDefaultConfiguration()
g_Configuration.EnableThumbstickCamera = false; g_Configuration.EnableThumbstickCamera = false;
g_Configuration.MouseSensitivity = GameConfiguration::DEFAULT_MOUSE_SENSITIVITY; g_Configuration.MouseSensitivity = GameConfiguration::DEFAULT_MOUSE_SENSITIVITY;
g_Configuration.MouseSmoothing = GameConfiguration::DEFAULT_MOUSE_SMOOTHING; g_Configuration.MenuOptionLoopingMode = MenuOptionLoopingMode::SaveLoadOnly;
g_Configuration.SupportedScreenResolutions = GetAllSupportedScreenResolutions(); g_Configuration.SupportedScreenResolutions = GetAllSupportedScreenResolutions();
g_Configuration.AdapterName = g_Renderer.GetDefaultAdapterName(); g_Configuration.AdapterName = g_Renderer.GetDefaultAdapterName();
@ -444,14 +444,14 @@ bool LoadConfiguration()
} }
DWORD mouseSensitivity = GameConfiguration::DEFAULT_MOUSE_SENSITIVITY; DWORD mouseSensitivity = GameConfiguration::DEFAULT_MOUSE_SENSITIVITY;
DWORD mouseSmoothing = GameConfiguration::DEFAULT_MOUSE_SMOOTHING; DWORD menuOptionLoopingMode = (DWORD)MenuOptionLoopingMode::SaveLoadOnly;
// Load Input keys. // Load Input keys.
HKEY inputKey = NULL; HKEY inputKey = NULL;
if (RegOpenKeyExA(rootKey, REGKEY_INPUT, 0, KEY_READ, &inputKey) == ERROR_SUCCESS) if (RegOpenKeyExA(rootKey, REGKEY_INPUT, 0, KEY_READ, &inputKey) == ERROR_SUCCESS)
{ {
if (GetDWORDRegKey(inputKey, REGKEY_MOUSE_SENSITIVITY, &mouseSensitivity, GameConfiguration::DEFAULT_MOUSE_SENSITIVITY) != ERROR_SUCCESS || if (GetDWORDRegKey(inputKey, REGKEY_MOUSE_SENSITIVITY, &mouseSensitivity, GameConfiguration::DEFAULT_MOUSE_SENSITIVITY) != ERROR_SUCCESS ||
GetDWORDRegKey(inputKey, REGKEY_MOUSE_SMOOTHING, &mouseSmoothing, GameConfiguration::DEFAULT_MOUSE_SMOOTHING) != ERROR_SUCCESS) GetDWORDRegKey(inputKey, REGKEY_ENABLE_MENU_OPTION_LOOPING, &menuOptionLoopingMode, (DWORD)MenuOptionLoopingMode::SaveLoadOnly) != ERROR_SUCCESS)
{ {
RegCloseKey(rootKey); RegCloseKey(rootKey);
RegCloseKey(graphicsKey); RegCloseKey(graphicsKey);
@ -519,7 +519,7 @@ bool LoadConfiguration()
g_Configuration.EnableThumbstickCamera = enableThumbstickCamera; g_Configuration.EnableThumbstickCamera = enableThumbstickCamera;
g_Configuration.MouseSensitivity = mouseSensitivity; g_Configuration.MouseSensitivity = mouseSensitivity;
g_Configuration.MouseSmoothing = mouseSmoothing; g_Configuration.MenuOptionLoopingMode = (MenuOptionLoopingMode)menuOptionLoopingMode;
// Set legacy variables. // Set legacy variables.
SetVolumeTracks(musicVolume); SetVolumeTracks(musicVolume);

View file

@ -7,6 +7,7 @@ using namespace TEN::Input;
using namespace TEN::Math; using namespace TEN::Math;
// Directories // Directories
constexpr auto REGKEY_ROOT = "Software\\TombEngine\\1.1.0"; constexpr auto REGKEY_ROOT = "Software\\TombEngine\\1.1.0";
constexpr auto REGKEY_GRAPHICS = "Graphics"; constexpr auto REGKEY_GRAPHICS = "Graphics";
constexpr auto REGKEY_SOUND = "Sound"; constexpr auto REGKEY_SOUND = "Sound";
@ -14,6 +15,7 @@ constexpr auto REGKEY_GAMEPLAY = "Gameplay";
constexpr auto REGKEY_INPUT = "Input"; constexpr auto REGKEY_INPUT = "Input";
// Graphics keys // Graphics keys
constexpr auto REGKEY_SCREEN_WIDTH = "ScreenWidth"; constexpr auto REGKEY_SCREEN_WIDTH = "ScreenWidth";
constexpr auto REGKEY_SCREEN_HEIGHT = "ScreenHeight"; constexpr auto REGKEY_SCREEN_HEIGHT = "ScreenHeight";
constexpr auto REGKEY_ENABLE_WINDOWED_MODE = "EnableWindowedMode"; constexpr auto REGKEY_ENABLE_WINDOWED_MODE = "EnableWindowedMode";
@ -25,6 +27,7 @@ constexpr auto REGKEY_ANTIALIASING_MODE = "AntialiasingMode";
constexpr auto REGKEY_AMBIENT_OCCLUSION = "AmbientOcclusion"; constexpr auto REGKEY_AMBIENT_OCCLUSION = "AmbientOcclusion";
// Sound keys // Sound keys
constexpr auto REGKEY_SOUND_DEVICE = "SoundDevice"; constexpr auto REGKEY_SOUND_DEVICE = "SoundDevice";
constexpr auto REGKEY_ENABLE_SOUND = "EnableSound"; constexpr auto REGKEY_ENABLE_SOUND = "EnableSound";
constexpr auto REGKEY_ENABLE_REVERB = "EnableReverb"; constexpr auto REGKEY_ENABLE_REVERB = "EnableReverb";
@ -32,6 +35,7 @@ constexpr auto REGKEY_MUSIC_VOLUME = "MusicVolume";
constexpr auto REGKEY_SFX_VOLUME = "SfxVolume"; constexpr auto REGKEY_SFX_VOLUME = "SfxVolume";
// Gameplay keys // Gameplay keys
constexpr auto REGKEY_ENABLE_SUBTITLES = "EnableSubtitles"; constexpr auto REGKEY_ENABLE_SUBTITLES = "EnableSubtitles";
constexpr auto REGKEY_ENABLE_AUTO_MONKEY_JUMP = "EnableAutoMonkeySwingJump"; constexpr auto REGKEY_ENABLE_AUTO_MONKEY_JUMP = "EnableAutoMonkeySwingJump";
constexpr auto REGKEY_ENABLE_AUTO_TARGETING = "EnableAutoTargeting"; constexpr auto REGKEY_ENABLE_AUTO_TARGETING = "EnableAutoTargeting";
@ -40,17 +44,25 @@ constexpr auto REGKEY_ENABLE_RUMBLE = "EnableRumble";
constexpr auto REGKEY_ENABLE_THUMBSTICK_CAMERA = "EnableThumbstickCamera"; constexpr auto REGKEY_ENABLE_THUMBSTICK_CAMERA = "EnableThumbstickCamera";
// Input keys // Input keys
constexpr auto REGKEY_MOUSE_SENSITIVITY = "MouseSensitivity"; constexpr auto REGKEY_MOUSE_SENSITIVITY = "MouseSensitivity";
constexpr auto REGKEY_MOUSE_SMOOTHING = "MouseSmoothing"; constexpr auto REGKEY_ENABLE_MENU_OPTION_LOOPING = "EnableMenuOptionLooping";
enum class MenuOptionLoopingMode
{
AllMenus,
SaveLoadOnly,
Disabled
};
struct GameConfiguration struct GameConfiguration
{ {
static constexpr auto DEFAULT_SHADOW_MAP_SIZE = 1024; static constexpr auto DEFAULT_SHADOW_MAP_SIZE = 1024;
static constexpr auto DEFAULT_SHADOW_BLOBS_MAX = 16; static constexpr auto DEFAULT_SHADOW_BLOBS_MAX = 16;
static constexpr auto DEFAULT_MOUSE_SENSITIVITY = 6; static constexpr auto DEFAULT_MOUSE_SENSITIVITY = 6;
static constexpr auto DEFAULT_MOUSE_SMOOTHING = 1;
// Graphics // Graphics
int ScreenWidth = 0; int ScreenWidth = 0;
int ScreenHeight = 0; int ScreenHeight = 0;
bool EnableWindowedMode = false; bool EnableWindowedMode = false;
@ -62,6 +74,7 @@ struct GameConfiguration
AntialiasingMode AntialiasingMode = AntialiasingMode::None; AntialiasingMode AntialiasingMode = AntialiasingMode::None;
// Sound // Sound
int SoundDevice = 0; int SoundDevice = 0;
bool EnableSound = false; bool EnableSound = false;
bool EnableReverb = false; bool EnableReverb = false;
@ -69,6 +82,7 @@ struct GameConfiguration
int SfxVolume = 0; int SfxVolume = 0;
// Gameplay // Gameplay
bool EnableSubtitles = false; bool EnableSubtitles = false;
bool EnableAutoMonkeySwingJump = false; bool EnableAutoMonkeySwingJump = false;
bool EnableAutoTargeting = false; bool EnableAutoTargeting = false;
@ -77,8 +91,9 @@ struct GameConfiguration
bool EnableThumbstickCamera = false; bool EnableThumbstickCamera = false;
// Input // Input
int MouseSensitivity = DEFAULT_MOUSE_SENSITIVITY; int MouseSensitivity = DEFAULT_MOUSE_SENSITIVITY;
int MouseSmoothing = DEFAULT_MOUSE_SMOOTHING; MenuOptionLoopingMode MenuOptionLoopingMode = MenuOptionLoopingMode::SaveLoadOnly;
std::vector<int> Bindings = {}; std::vector<int> Bindings = {};
std::vector<Vector2i> SupportedScreenResolutions = {}; std::vector<Vector2i> SupportedScreenResolutions = {};

View file

@ -687,17 +687,17 @@ void ReadRooms()
{ {
auto& room = g_Level.Rooms.emplace_back(); auto& room = g_Level.Rooms.emplace_back();
room.name = ReadString(); room.Name = ReadString();
int tagCount = ReadInt32(); int tagCount = ReadInt32();
for (int j = 0; j < tagCount; j++) for (int j = 0; j < tagCount; j++)
room.tags.push_back(ReadString()); room.Tags.push_back(ReadString());
room.x = ReadInt32(); room.Position.x = ReadInt32();
room.y = 0; room.Position.y = 0;
room.z = ReadInt32(); room.Position.z = ReadInt32();
room.minfloor = ReadInt32(); room.BottomHeight = ReadInt32();
room.maxceiling = ReadInt32(); room.TopHeight = ReadInt32();
int vertexCount = ReadInt32(); int vertexCount = ReadInt32();
@ -769,14 +769,14 @@ void ReadRooms()
for (int j = 0; j < portalCount; j++) for (int j = 0; j < portalCount; j++)
LoadPortal(room); LoadPortal(room);
room.zSize = ReadInt32(); room.ZSize = ReadInt32();
room.xSize = ReadInt32(); room.XSize = ReadInt32();
auto roomPos = Vector2i(room.x, room.z); auto roomPos = Vector2i(room.Position.x, room.Position.z);
room.floor.reserve(room.zSize * room.xSize); room.Sectors.reserve(room.XSize * room.ZSize);
for (int x = 0; x < room.xSize; x++) for (int x = 0; x < room.XSize; x++)
{ {
for (int z = 0; z < room.zSize; z++) for (int z = 0; z < room.ZSize; z++)
{ {
auto sector = FloorInfo{}; auto sector = FloorInfo{};
@ -820,17 +820,17 @@ void ReadRooms()
sector.Flags.MarkTriggererActive = 0; // TODO: Needs to be written to and read from savegames. sector.Flags.MarkTriggererActive = 0; // TODO: Needs to be written to and read from savegames.
sector.Flags.MarkBeetle = ReadBool(); sector.Flags.MarkBeetle = ReadBool();
room.floor.push_back(sector); room.Sectors.push_back(sector);
} }
} }
room.ambient = ReadVector3(); room.ambient = ReadVector3();
int numLights = ReadInt32(); int lightCount = ReadInt32();
room.lights.reserve(numLights); room.lights.reserve(lightCount);
for (int j = 0; j < numLights; j++) for (int j = 0; j < lightCount; j++)
{ {
ROOM_LIGHT light; auto light = ROOM_LIGHT{};
light.x = ReadInt32(); light.x = ReadInt32();
light.y = ReadInt32(); light.y = ReadInt32();
@ -852,9 +852,9 @@ void ReadRooms()
room.lights.push_back(light); room.lights.push_back(light);
} }
int numStatics = ReadInt32(); int staticCount = ReadInt32();
room.mesh.reserve(numStatics); room.mesh.reserve(staticCount);
for (int j = 0; j < numStatics; j++) for (int j = 0; j < staticCount; j++)
{ {
auto& mesh = room.mesh.emplace_back(); auto& mesh = room.mesh.emplace_back();
@ -875,21 +875,17 @@ void ReadRooms()
g_GameScriptEntities->AddName(mesh.Name, mesh); g_GameScriptEntities->AddName(mesh.Name, mesh);
} }
int numTriggerVolumes = ReadInt32(); int triggerVolumeCount = ReadInt32();
room.TriggerVolumes.reserve(triggerVolumeCount);
// Reserve in advance so the vector doesn't resize itself and leave anything for (int j = 0; j < triggerVolumeCount; j++)
// in the script name-to-reference map obsolete.
room.triggerVolumes.reserve(numTriggerVolumes);
for (int j = 0; j < numTriggerVolumes; j++)
{ {
auto& volume = room.triggerVolumes.emplace_back(); auto& volume = room.TriggerVolumes.emplace_back();
volume.Type = (VolumeType)ReadInt32(); volume.Type = (VolumeType)ReadInt32();
// NOTE: Braces are necessary to ensure correct value init order. auto pos = ReadVector3();
auto pos = Vector3{ ReadFloat(), ReadFloat(), ReadFloat() }; auto orient = ReadVector4();
auto orient = Quaternion{ ReadFloat(), ReadFloat(), ReadFloat(), ReadFloat() }; auto scale = ReadVector3();
auto scale = Vector3{ ReadFloat(), ReadFloat(), ReadFloat() };
volume.Enabled = ReadBool(); volume.Enabled = ReadBool();
volume.DetectInAdjacentRooms = ReadBool(); volume.DetectInAdjacentRooms = ReadBool();
@ -913,9 +909,9 @@ void ReadRooms()
room.itemNumber = NO_VALUE; room.itemNumber = NO_VALUE;
room.fxNumber = NO_VALUE; room.fxNumber = NO_VALUE;
room.index = i; room.RoomNumber = i;
g_GameScriptEntities->AddName(room.name, room); g_GameScriptEntities->AddName(room.Name, room);
} }
} }
@ -1543,17 +1539,17 @@ void BuildOutsideRoomsTable()
{ {
auto* room = &g_Level.Rooms[i]; auto* room = &g_Level.Rooms[i];
int rx = (room->x / BLOCK(1)); int rx = (room->Position.x / BLOCK(1));
int rz = (room->z / BLOCK(1)); int rz = (room->Position.z / BLOCK(1));
for (int x = 0; x < OUTSIDE_SIZE; x++) for (int x = 0; x < OUTSIDE_SIZE; x++)
{ {
if (x < (rx + 1) || x > (rx + room->xSize - 2)) if (x < (rx + 1) || x > (rx + room->XSize - 2))
continue; continue;
for (int z = 0; z < OUTSIDE_SIZE; z++) for (int z = 0; z < OUTSIDE_SIZE; z++)
{ {
if (z < (rz + 1) || z > (rz + room->zSize - 2)) if (z < (rz + 1) || z > (rz + room->ZSize - 2))
continue; continue;
OutsideRoomTable[x][z].push_back(i); OutsideRoomTable[x][z].push_back(i);

View file

@ -483,18 +483,18 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)"</Command>
<ClInclude Include="Objects\Generic\generic_objects.h" /> <ClInclude Include="Objects\Generic\generic_objects.h" />
<ClInclude Include="Objects\Generic\puzzles_keys.h" /> <ClInclude Include="Objects\Generic\puzzles_keys.h" />
<ClInclude Include="Objects\Sink.h" /> <ClInclude Include="Objects\Sink.h" />
<ClInclude Include="Objects\TR1\Entity\Centaur.h" />
<ClInclude Include="Objects\TR1\Entity\Cowboy.h" /> <ClInclude Include="Objects\TR1\Entity\Cowboy.h" />
<ClInclude Include="Objects\TR1\Entity\Kold.h" /> <ClInclude Include="Objects\TR1\Entity\Kold.h" />
<ClInclude Include="Objects\TR1\Entity\tr1_ape.h" /> <ClInclude Include="Objects\TR1\Entity\tr1_ape.h" />
<ClInclude Include="Objects\TR1\Entity\tr1_bear.h" /> <ClInclude Include="Objects\TR1\Entity\tr1_bear.h" />
<ClInclude Include="Objects\TR1\Entity\tr1_big_rat.h" /> <ClInclude Include="Objects\TR1\Entity\tr1_big_rat.h" />
<ClInclude Include="Objects\TR1\Entity\tr1_centaur.h" />
<ClInclude Include="Objects\TR1\Entity\tr1_doppelganger.h" /> <ClInclude Include="Objects\TR1\Entity\tr1_doppelganger.h" />
<ClInclude Include="Objects\TR1\Entity\tr1_giant_mutant.h" /> <ClInclude Include="Objects\TR1\Entity\tr1_giant_mutant.h" />
<ClInclude Include="Objects\TR1\Entity\tr1_natla.h" /> <ClInclude Include="Objects\TR1\Entity\tr1_natla.h" />
<ClInclude Include="Objects\TR1\Entity\SkateboardKid.h" /> <ClInclude Include="Objects\TR1\Entity\SkateboardKid.h" />
<ClInclude Include="Objects\TR1\Entity\tr1_winged_mutant.h" />
<ClInclude Include="Objects\TR1\Entity\tr1_wolf.h" /> <ClInclude Include="Objects\TR1\Entity\tr1_wolf.h" />
<ClInclude Include="Objects\TR1\Entity\WingedMutant.h" />
<ClInclude Include="Objects\TR1\Trap\DamoclesSword.h" /> <ClInclude Include="Objects\TR1\Trap\DamoclesSword.h" />
<ClInclude Include="Objects\TR1\Trap\SlammingDoors.h" /> <ClInclude Include="Objects\TR1\Trap\SlammingDoors.h" />
<ClInclude Include="Objects\TR1\tr1_objects.h" /> <ClInclude Include="Objects\TR1\tr1_objects.h" />
@ -530,7 +530,9 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)"</Command>
<ClInclude Include="Objects\TR3\Entity\Compsognathus.h" /> <ClInclude Include="Objects\TR3\Entity\Compsognathus.h" />
<ClInclude Include="Objects\TR3\Entity\FishSwarm.h" /> <ClInclude Include="Objects\TR3\Entity\FishSwarm.h" />
<ClInclude Include="Objects\TR3\Entity\Lizard.h" /> <ClInclude Include="Objects\TR3\Entity\Lizard.h" />
<ClInclude Include="Objects\TR3\Entity\SealMutant.h" />
<ClInclude Include="Objects\TR3\Entity\PunaBoss.h" /> <ClInclude Include="Objects\TR3\Entity\PunaBoss.h" />
<ClInclude Include="Objects\TR3\Entity\Raptor.h" />
<ClInclude Include="Objects\TR3\Entity\Shiva.h" /> <ClInclude Include="Objects\TR3\Entity\Shiva.h" />
<ClInclude Include="Objects\TR3\Entity\SophiaLeigh.h" /> <ClInclude Include="Objects\TR3\Entity\SophiaLeigh.h" />
<ClInclude Include="Objects\TR3\Entity\tr3_claw_mutant.h" /> <ClInclude Include="Objects\TR3\Entity\tr3_claw_mutant.h" />
@ -542,7 +544,6 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)"</Command>
<ClInclude Include="Objects\TR3\Entity\tr3_monkey.h" /> <ClInclude Include="Objects\TR3\Entity\tr3_monkey.h" />
<ClInclude Include="Objects\TR3\Entity\tr3_mp_gun.h" /> <ClInclude Include="Objects\TR3\Entity\tr3_mp_gun.h" />
<ClInclude Include="Objects\TR3\Entity\tr3_mp_stick.h" /> <ClInclude Include="Objects\TR3\Entity\tr3_mp_stick.h" />
<ClInclude Include="Objects\TR3\Entity\tr3_raptor.h" />
<ClInclude Include="Objects\TR3\Entity\tr3_scuba_diver.h" /> <ClInclude Include="Objects\TR3\Entity\tr3_scuba_diver.h" />
<ClInclude Include="Objects\TR3\Entity\tr3_tiger.h" /> <ClInclude Include="Objects\TR3\Entity\tr3_tiger.h" />
<ClInclude Include="Objects\TR3\Entity\tr3_tony.h" /> <ClInclude Include="Objects\TR3\Entity\tr3_tony.h" />
@ -991,18 +992,18 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)"</Command>
<ClCompile Include="Objects\Generic\Traps\CrumblingPlatform.cpp" /> <ClCompile Include="Objects\Generic\Traps\CrumblingPlatform.cpp" />
<ClCompile Include="Objects\Generic\Traps\dart_emitter.cpp" /> <ClCompile Include="Objects\Generic\Traps\dart_emitter.cpp" />
<ClCompile Include="Objects\Generic\Traps\falling_block.cpp" /> <ClCompile Include="Objects\Generic\Traps\falling_block.cpp" />
<ClCompile Include="Objects\TR1\Entity\Centaur.cpp" />
<ClCompile Include="Objects\TR1\Entity\Cowboy.cpp" /> <ClCompile Include="Objects\TR1\Entity\Cowboy.cpp" />
<ClCompile Include="Objects\TR1\Entity\Kold.cpp" /> <ClCompile Include="Objects\TR1\Entity\Kold.cpp" />
<ClCompile Include="Objects\TR1\Entity\tr1_ape.cpp" /> <ClCompile Include="Objects\TR1\Entity\tr1_ape.cpp" />
<ClCompile Include="Objects\TR1\Entity\tr1_bear.cpp" /> <ClCompile Include="Objects\TR1\Entity\tr1_bear.cpp" />
<ClCompile Include="Objects\TR1\Entity\tr1_big_rat.cpp" /> <ClCompile Include="Objects\TR1\Entity\tr1_big_rat.cpp" />
<ClCompile Include="Objects\TR1\Entity\tr1_centaur.cpp" />
<ClCompile Include="Objects\TR1\Entity\tr1_doppelganger.cpp" /> <ClCompile Include="Objects\TR1\Entity\tr1_doppelganger.cpp" />
<ClCompile Include="Objects\TR1\Entity\tr1_giant_mutant.cpp" /> <ClCompile Include="Objects\TR1\Entity\tr1_giant_mutant.cpp" />
<ClCompile Include="Objects\TR1\Entity\tr1_natla.cpp" /> <ClCompile Include="Objects\TR1\Entity\tr1_natla.cpp" />
<ClCompile Include="Objects\TR1\Entity\SkateboardKid.cpp" /> <ClCompile Include="Objects\TR1\Entity\SkateboardKid.cpp" />
<ClCompile Include="Objects\TR1\Entity\tr1_winged_mutant.cpp" />
<ClCompile Include="Objects\TR1\Entity\tr1_wolf.cpp" /> <ClCompile Include="Objects\TR1\Entity\tr1_wolf.cpp" />
<ClCompile Include="Objects\TR1\Entity\WingedMutant.cpp" />
<ClCompile Include="Objects\TR1\tr1_objects.cpp" /> <ClCompile Include="Objects\TR1\tr1_objects.cpp" />
<ClCompile Include="Objects\TR1\Trap\DamoclesSword.cpp" /> <ClCompile Include="Objects\TR1\Trap\DamoclesSword.cpp" />
<ClCompile Include="Objects\TR1\Trap\SlammingDoors.cpp" /> <ClCompile Include="Objects\TR1\Trap\SlammingDoors.cpp" />
@ -1036,7 +1037,9 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)"</Command>
<ClCompile Include="Objects\TR3\Entity\Compsognathus.cpp" /> <ClCompile Include="Objects\TR3\Entity\Compsognathus.cpp" />
<ClCompile Include="Objects\TR3\Entity\FishSwarm.cpp" /> <ClCompile Include="Objects\TR3\Entity\FishSwarm.cpp" />
<ClCompile Include="Objects\TR3\Entity\Lizard.cpp" /> <ClCompile Include="Objects\TR3\Entity\Lizard.cpp" />
<ClCompile Include="Objects\TR3\Entity\SealMutant.cpp" />
<ClCompile Include="Objects\TR3\Entity\PunaBoss.cpp" /> <ClCompile Include="Objects\TR3\Entity\PunaBoss.cpp" />
<ClCompile Include="Objects\TR3\Entity\Raptor.cpp" />
<ClCompile Include="Objects\TR3\Entity\Shiva.cpp" /> <ClCompile Include="Objects\TR3\Entity\Shiva.cpp" />
<ClCompile Include="Objects\TR3\Entity\SophiaLeigh.cpp" /> <ClCompile Include="Objects\TR3\Entity\SophiaLeigh.cpp" />
<ClCompile Include="Objects\TR3\Entity\tr3_civvy.cpp" /> <ClCompile Include="Objects\TR3\Entity\tr3_civvy.cpp" />
@ -1046,7 +1049,6 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)"</Command>
<ClCompile Include="Objects\TR3\Entity\tr3_monkey.cpp" /> <ClCompile Include="Objects\TR3\Entity\tr3_monkey.cpp" />
<ClCompile Include="Objects\TR3\Entity\tr3_mp_gun.cpp" /> <ClCompile Include="Objects\TR3\Entity\tr3_mp_gun.cpp" />
<ClCompile Include="Objects\TR3\Entity\tr3_mp_stick.cpp" /> <ClCompile Include="Objects\TR3\Entity\tr3_mp_stick.cpp" />
<ClCompile Include="Objects\TR3\Entity\tr3_raptor.cpp" />
<ClCompile Include="Objects\TR3\Entity\tr3_scuba_diver.cpp" /> <ClCompile Include="Objects\TR3\Entity\tr3_scuba_diver.cpp" />
<ClCompile Include="Objects\TR3\Entity\tr3_tiger.cpp" /> <ClCompile Include="Objects\TR3\Entity\tr3_tiger.cpp" />
<ClCompile Include="Objects\TR3\Entity\tr3_tony.cpp" /> <ClCompile Include="Objects\TR3\Entity\tr3_tony.cpp" />