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)
- Tomo (general coding, bug fixing)
- Troye (general coding, refactoring)
- WolfCheese (general coding)
- Nickelony (general coding)
- JesseG, aka WolfCheese (general coding)
## Testers
- Adngel

View file

@ -25,6 +25,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed sentry gun joint rotation.
* Fixed teeth spikes not triggering the player impale animation.
* Fixed TR4 mine crash with OCB 1 when triggered.
* Fixed cases where Atlantean mutant's bombs cause the game to crash.
### Features/Amendments
* 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 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 ).
* 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.
- Underwater - sound plays when the camera is submerged.
* 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
* Added Inventory.GetUsedItem(), Inventory.SetUsedItem() and Inventory.ClearUsedItem() functions.
* Added Input.KeyClearAll()
* Added Flow.EnableHomeLevel()
* Removed anims.monkeyAutoJump. It is now a player menu configuration.
* 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>
</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="summary">Enable or disable saving and loading of savegames.</td>
</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>
<dt>
<a name = "EnableLoadSave"></a>

View file

@ -357,7 +357,7 @@ WASP_MUTANT
SKATEBOARD
SKATEBOARD_KID
WINSTON
ARMY_WINSTON
SEAL_MUTANT
SPRINGBOARD
ROLLING_SPINDLE
DISK_SHOOTER
@ -943,6 +943,7 @@ MESHSWAP_ROMAN_GOD1
MESHSWAP_ROMAN_GOD2
MESHSWAP_MONKEY_MEDIPACK
MESHSWAP_MONKEY_KEY
MESHSWAP_WINSTON_ARMY_OUTFIT
ANIMATING1
ANIMATING2
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
lara_home = { "Lara's Home" },
home_level = { "Home Level" },
test_level = { "Test Level" },
title = { "Title" },
}

View file

@ -80,8 +80,11 @@ local strings =
low = { "Low" },
medium = { "Medium" },
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_smoothing = { "Mouse Smoothing" },
music_volume = { "Music Volume" },
new_game = { "New Game" },
none = { "None" },

View file

@ -166,7 +166,7 @@ void LookCamera(ItemInfo& item, const CollisionInfo& coll)
bool isInSwamp = TestEnvironment(ENV_FLAG_SWAMP, item.RoomNumber);
auto basePos = Vector3i(
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);
// Define landmarks.
@ -343,7 +343,7 @@ void MoveCamera(GameVector* ideal, int speed)
int y = Camera.pos.y;
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);
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);
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;
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);
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);
Camera.target.RoomNumber = pointColl.GetRoomNumber();
@ -1393,7 +1393,7 @@ bool CheckItemCollideCamera(ItemInfo* item)
static std::vector<int> FillCollideableItemList()
{
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++)
{
@ -1443,7 +1443,7 @@ bool CheckStaticCollideCamera(MESH_INFO* mesh)
std::vector<MESH_INFO*> FillCollideableStaticsList()
{
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)
{

View file

@ -57,7 +57,7 @@ namespace TEN::Collision::Point
int roomNumber = roomNumberBelow.value_or(bottomSector->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);
}
_bottomSector = bottomSector;
@ -78,7 +78,7 @@ namespace TEN::Collision::Point
int roomNumber = roomNumberAbove.value_or(topSector->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);
}
_topSector = topSector;

View file

@ -123,7 +123,7 @@ CollidedObjectData GetCollidedObjects(ItemInfo& collidingItem, bool onlyVisible,
// Run through neighboring rooms.
const auto& room = g_Level.Rooms[collidingItem.RoomNumber];
for (int roomNumber : room.neighbors)
for (int roomNumber : room.NeighborRoomNumbers)
{
auto& neighborRoom = g_Level.Rooms[roomNumber];
if (!neighborRoom.Active())
@ -307,7 +307,7 @@ void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll)
// 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())
continue;
@ -940,7 +940,7 @@ void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll)
{
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())
continue;
@ -1809,7 +1809,7 @@ void DoObjectCollision(ItemInfo* item, CollisionInfo* coll)
return;
const auto& room = g_Level.Rooms[item->RoomNumber];
for (int neighborRoomNumber : room.neighbors)
for (int neighborRoomNumber : room.NeighborRoomNumbers)
{
auto& neighborRoom = g_Level.Rooms[neighborRoomNumber];
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)
{
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))
{
@ -1094,7 +1094,7 @@ int GetWaterSurface(int x, int y, int z, short roomNumber)
if (!TestEnvironment(ENV_FLAG_WATER, room))
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;
@ -1107,7 +1107,7 @@ int GetWaterSurface(int x, int y, int z, short roomNumber)
if (TestEnvironment(ENV_FLAG_WATER, room))
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;
do
{
int xFloor = (x - room->x) / BLOCK(1);
int zFloor = (z - room->z) / BLOCK(1);
int xFloor = (x - room->Position.x) / BLOCK(1);
int zFloor = (z - room->Position.z) / BLOCK(1);
if (zFloor <= 0)
{
@ -1137,33 +1137,33 @@ int GetWaterDepth(int x, int y, int z, short roomNumber)
{
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)
{
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)
{
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;
if (adjoiningRoomNumber != NO_VALUE)
{
@ -1188,7 +1188,7 @@ int GetWaterDepth(int x, int y, int z, short roomNumber)
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;
@ -1207,7 +1207,7 @@ int GetWaterDepth(int x, int y, int z, short roomNumber)
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;
@ -1227,8 +1227,8 @@ int GetWaterHeight(int x, int y, int z, short roomNumber)
int adjoiningRoomNumber = NO_VALUE;
do
{
int xBlock = (x - room->x) / BLOCK(1);
int zBlock = (z - room->z) / BLOCK(1);
int xBlock = (x - room->Position.x) / BLOCK(1);
int zBlock = (z - room->Position.z) / BLOCK(1);
if (zBlock <= 0)
{
@ -1237,33 +1237,33 @@ int GetWaterHeight(int x, int y, int z, short roomNumber)
{
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)
{
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)
{
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;
if (adjoiningRoomNumber != NO_VALUE)
@ -1290,7 +1290,7 @@ int GetWaterHeight(int x, int y, int z, short roomNumber)
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);
@ -1307,7 +1307,7 @@ int GetWaterHeight(int x, int y, int z, short roomNumber)
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);

View file

@ -368,17 +368,17 @@ namespace TEN::Collision::Floordata
const auto& room = g_Level.Rooms[roomNumber];
// Calculate room grid coord.
auto roomGridCoord = Vector2i((x - room.x) / BLOCK(1), (z - room.z) / BLOCK(1));
if (x < room.x)
auto roomGridCoord = Vector2i((x - room.Position.x) / BLOCK(1), (z - room.Position.z) / BLOCK(1));
if (x < room.Position.x)
roomGridCoord.x -= 1;
if (z < room.z)
if (z < room.Position.z)
roomGridCoord.y -= 1;
// Clamp room grid coord to room bounds (if applicable).
if (clampToBounds)
{
roomGridCoord.x = std::clamp(roomGridCoord.x, 0, room.xSize - 1);
roomGridCoord.y = std::clamp(roomGridCoord.y, 0, room.zSize - 1);
roomGridCoord.x = std::clamp(roomGridCoord.x, 0, room.XSize - 1);
roomGridCoord.y = std::clamp(roomGridCoord.y, 0, room.ZSize - 1);
}
return roomGridCoord;
@ -397,8 +397,8 @@ namespace TEN::Collision::Floordata
const auto& room = g_Level.Rooms[roomNumber];
// Search area out of range; return empty vector.
if (xMax <= 0 || xMin >= (room.xSize - 1) ||
xMax <= 0 || xMin >= (room.xSize - 1))
if (xMax <= 0 || xMin >= (room.XSize - 1) ||
xMax <= 0 || xMin >= (room.XSize - 1))
{
return {};
}
@ -408,13 +408,13 @@ namespace TEN::Collision::Floordata
for (int x = xMin; x <= xMax; x++)
{
// Test if out of room X range.
if (x <= 0 || x >= (room.xSize - 1))
if (x <= 0 || x >= (room.XSize - 1))
continue;
for (int z = zMin; z <= zMax; z++)
{
// Test if out of room Z range.
if (z <= 0 || z >= (room.zSize - 1))
if (z <= 0 || z >= (room.ZSize - 1))
continue;
roomGridCoords.push_back(Vector2i(x, z));
@ -430,7 +430,7 @@ namespace TEN::Collision::Floordata
// Run through neighbor rooms.
auto& room = g_Level.Rooms[roomNumber];
for (int neighborRoomNumber : room.neighbors)
for (int neighborRoomNumber : room.NeighborRoomNumbers)
{
// Collect neighbor sectors.
auto roomGridCoords = GetNeighborRoomGridCoords(pos, neighborRoomNumber, searchDepth);
@ -446,8 +446,8 @@ namespace TEN::Collision::Floordata
{
auto& room = g_Level.Rooms[roomNumber];
int sectorID = (room.zSize * roomGridCoord.x) + roomGridCoord.y;
return room.floor[sectorID];
int sectorID = (room.ZSize * roomGridCoord.x) + roomGridCoord.y;
return room.Sectors[sectorID];
}
FloorInfo& GetFloor(int roomNumber, int x, int z)
@ -831,18 +831,18 @@ namespace TEN::Collision::Floordata
const auto& room = g_Level.Rooms[item.RoomNumber];
// 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 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 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 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 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.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.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.Position.z) / BLOCK(1));
// 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 pZ = (room.z + BLOCK(z)) + BLOCK(0.5f);
float pX = (room.Position.x + BLOCK(x)) + BLOCK(0.5f);
float pZ = (room.Position.z + BLOCK(z)) + BLOCK(0.5f);
float offX = pX - item.Pose.Position.x;
float offZ = pZ - item.Pose.Position.z;
@ -911,7 +911,7 @@ namespace TEN::Collision::Floordata
// Run through neighboring rooms.
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];
@ -919,8 +919,8 @@ namespace TEN::Collision::Floordata
auto roomGridCoords = GetNeighborRoomGridCoords(item.Pose.Position, neighborRoomNumber, SECTOR_SEARCH_DEPTH);
for (const auto& roomGridCoord : roomGridCoords)
{
pos.x = BLOCK(roomGridCoord.x) + neighborRoom.x;
pos.z = BLOCK(roomGridCoord.y) + neighborRoom.z;
pos.x = BLOCK(roomGridCoord.x) + neighborRoom.Position.x;
pos.z = BLOCK(roomGridCoord.y) + neighborRoom.Position.z;
pointColl = GetPointCollision(pos, neighborRoomNumber);
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& 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)
return false;
item.BoxNumber = boxSource;
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)
return false;
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();
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];
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)
return;
@ -1504,7 +1504,7 @@ int TargetReachable(ItemInfo* item, ItemInfo* enemy)
{
const auto& creature = *GetCreatureInfo(item);
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.
// 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* 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];
enemy->BoxNumber = TargetReachable(item, enemy);
@ -2129,14 +2129,14 @@ void AdjustStopperFlag(ItemInfo* item, int direction)
int z = item->Pose.Position.z;
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;
x = item->Pose.Position.x + BLOCK(1) * phd_sin(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()];
floor = GetSector(room, x - room->x, z - room->z);
floor = GetSector(room, x - room->Position.x, z - room->Position.z);
floor->Stopper = !floor->Stopper;
}
@ -2151,11 +2151,11 @@ void InitializeItemBoxData()
{
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));
if (index > room.floor.size())
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.Sectors.size())
continue;
auto* floor = &room.floor[index];
auto* floor = &room.Sectors[index];
if (floor->PathfindingBoxID == NO_VALUE)
continue;
@ -2184,19 +2184,7 @@ bool CanCreatureJump(ItemInfo& item, JumpDistance jumpDistType)
if (creature.Enemy == nullptr)
return false;
float stepDist = 0.0f;
switch (jumpDistType)
{
default:
case JumpDistance::Block1:
stepDist = BLOCK(0.51f);
break;
case JumpDistance::Block2:
stepDist = BLOCK(0.76f);
break;
}
float stepDist = BLOCK(0.92f);
int vPos = item.Pose.Position.y;
auto pointCollA = GetPointCollision(item, item.Pose.Orientation.y, stepDist);
auto pointCollB = GetPointCollision(item, item.Pose.Orientation.y, stepDist * 2);

View file

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

View file

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

View file

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

View file

@ -240,7 +240,7 @@ void CreateZone(ItemInfo* item)
auto* creature = GetCreatureInfo(item);
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)
{

View file

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

View file

@ -158,7 +158,7 @@ namespace TEN::Effects::Bubble
{
// Hit water surface; spawn ripple.
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,
((bubble.SizeMax.x + bubble.SizeMax.y) / 2) * 0.5f,
(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.
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;
continue;

View file

@ -256,30 +256,23 @@ void TriggerPilotFlame(int itemNumber, int nodeIndex)
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;
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;
return part;
}
Particle* SetupFireSpark()
static Particle* SetupFireSpark()
{
auto* spark = GetFreeParticle();
@ -296,19 +289,20 @@ Particle* SetupFireSpark()
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->y = (GetRandomControl() & 0x1F) + pos1.y - 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;
spark->life = spark->sLife = v / 6;
spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + spriteID;
spark->xVel = v * (pos2.x - pos1.x) / 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;
}
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];
for (int i = 0; i < 3; i++)
{
auto& spark = *SetupFireSpark();
AttachAndCreateSpark(&spark, &item, meshIndex, offset, vel);
auto& part = *SetupFireSpark();
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);
AttachAndCreateSpark(spark, item, meshIndex, offset, vel);
spark->flags = SP_POISON | SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF;
auto& part = SetupPoisonParticle(colorStart, colorEnd);
AttachAndCreateSpark(&part, &item, boneID, offset, vel, spriteID);
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()
@ -993,10 +987,10 @@ void UpdateGunShells()
!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);
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,
Random::GenerateFloat(8.0f, 12.0f),
(int)RippleFlags::SlowFade);

View file

@ -230,10 +230,10 @@ void TriggerGlobalStaticFlame();
void TriggerGlobalFireSmoke();
void TriggerGlobalFireFlame();
void TriggerPilotFlame(int itemNumber, int nodeIndex);
void ThrowFire(int itemNumber, int meshIndex, const Vector3i& offset, const Vector3i& vel);
void ThrowFire(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel);
void ThrowPoison(int itemNumber, int meshIndex, const Vector3i& offset, const Vector3i& vel, const Vector3& color);
void ThrowPoison(int itemNumber, const CreatureBiteInfo& bite, const Vector3i& vel, const Vector3& color);
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, int spriteID = 0);
void ThrowPoison(const ItemInfo& item, int boneID, const Vector3& offset, const Vector3& vel, const Color& colorStart, const Color& colorEnd, int spriteID = 0);
void ThrowPoison(const ItemInfo& item, const CreatureBiteInfo& bite, const Vector3& vel, const Color& colorStart, const Color& colorEnd, int spriteID = 0);
void UpdateFireProgress();
void ClearFires();
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
{
NewGame,
HomeLevel,
LoadGame,
Options,
ExitGame
ExitGame,
Count
};
static const int numTitleOptions = 3;
static const int numLoadGameOptions = SAVEGAME_MAX - 1;
static const int numOptionsOptions = 2;
constexpr auto TITLE_OPTION_COUNT = TitleOption::Count - 1;
constexpr auto LOAD_GAME_OPTION_COUNT = SAVEGAME_MAX - 1;
constexpr auto OPTION_OPTION_COUNT = 2;
static int selectedOptionBackup;
auto inventoryResult = InventoryResult::None;
@ -300,20 +303,31 @@ namespace TEN::Gui
switch (MenuToDisplay)
{
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;
case Menu::SelectLevel:
inventoryResult = InventoryResult::None;
OptionCount = g_GameFlow->GetNumLevels() - 2;
if (g_GameFlow->IsHomeLevelEnabled())
OptionCount--;
break;
case Menu::LoadGame:
OptionCount = numLoadGameOptions;
OptionCount = LOAD_GAME_OPTION_COUNT;
break;
case Menu::Options:
OptionCount = numOptionsOptions;
OptionCount = OPTION_OPTION_COUNT;
break;
case Menu::Display:
@ -354,21 +368,7 @@ namespace TEN::Gui
MenuToDisplay == Menu::SelectLevel ||
MenuToDisplay == Menu::Options)
{
if (GuiIsPulsed(In::Forward))
{
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);
}
SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus);
if (GuiIsDeselected() && MenuToDisplay != Menu::Title)
{
@ -383,9 +383,14 @@ namespace TEN::Gui
if (MenuToDisplay == Menu::Title)
{
// Skip load game entry if loading and saving is disabled.
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++;
switch (realSelectedOption)
@ -404,6 +409,10 @@ namespace TEN::Gui
break;
case TitleOption::HomeLevel:
inventoryResult = InventoryResult::HomeLevel;
break;
case TitleOption::LoadGame:
selectedOptionBackup = SelectedOption;
SelectedOption = 0;
@ -424,8 +433,13 @@ namespace TEN::Gui
}
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;
// Level 1 reserved for Home Level; increment option if enabled to offset it.
if (g_GameFlow->IsHomeLevelEnabled())
g_GameFlow->SelectedLevelForNewGame++;
MenuToDisplay = Menu::Title;
SelectedOption = 0;
inventoryResult = InventoryResult::NewGameSelectedLevel;
@ -577,25 +591,7 @@ namespace TEN::Gui
}
}
if (GuiIsPulsed(In::Forward))
{
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);
}
SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus);
if (GuiIsSelected())
{
@ -743,25 +739,7 @@ namespace TEN::Gui
}
else
{
if (GuiIsPulsed(In::Forward))
{
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);
}
SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus);
// HACK: Menu screen scroll.
if (GuiIsPulsed(In::Left) || GuiIsPulsed(In::Right))
@ -893,8 +871,9 @@ namespace TEN::Gui
TargetHighlighter,
ToggleRumble,
ThumbstickCameraControl,
MouseSensitivity,
MouseSmoothing,
MenuOptionLooping,
Apply,
Cancel,
@ -1000,14 +979,23 @@ namespace TEN::Gui
break;
case OtherSettingsOption::MouseSmoothing:
if (CurrentSettings.Configuration.MouseSmoothing > MOUSE_SMOOTHING_MIN)
{
CurrentSettings.Configuration.MouseSmoothing -= 1;
if (CurrentSettings.Configuration.MouseSmoothing < MOUSE_SMOOTHING_MIN)
CurrentSettings.Configuration.MouseSmoothing = MOUSE_SMOOTHING_MIN;
case OtherSettingsOption::MenuOptionLooping:
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;
@ -1063,14 +1051,23 @@ namespace TEN::Gui
break;
case OtherSettingsOption::MouseSmoothing:
if (CurrentSettings.Configuration.MouseSmoothing < MOUSE_SMOOTHING_MAX)
{
CurrentSettings.Configuration.MouseSmoothing += 1;
if (CurrentSettings.Configuration.MouseSmoothing > MOUSE_SMOOTHING_MAX)
CurrentSettings.Configuration.MouseSmoothing = MOUSE_SMOOTHING_MAX;
case OtherSettingsOption::MenuOptionLooping:
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;
@ -1083,33 +1080,7 @@ namespace TEN::Gui
}
}
if (GuiIsPulsed(In::Forward))
{
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);
}
SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus);
if (GuiIsSelected())
{
@ -1191,25 +1162,7 @@ namespace TEN::Gui
if (MenuToDisplay == Menu::Pause ||
MenuToDisplay == Menu::Options)
{
if (GuiIsPulsed(In::Forward))
{
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);
}
SelectedOption = GetLoopedSelectedOption(SelectedOption, OptionCount, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus);
}
if (GuiIsDeselected() || IsClicked(In::Pause))
@ -2515,51 +2468,10 @@ namespace TEN::Gui
!invRing.ObjectListMovement &&
!ammoRing.ObjectListMovement)
{
CurrentSelectedOption = GetLoopedSelectedOption(CurrentSelectedOption, n - 1, g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus);
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;
}
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))
{
@ -3459,27 +3371,49 @@ namespace TEN::Gui
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 (selectedOption <= 0)
{
if (IsClicked(In::Forward) && canLoop)
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
if (SelectedSaveSlot == 0)
SelectedSaveSlot += SAVEGAME_MAX - 1;
else
SelectedSaveSlot--;
return optionCount;
}
}
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())
{
@ -3501,25 +3435,9 @@ namespace TEN::Gui
bool GuiController::DoSave()
{
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))
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
if (SelectedSaveSlot == 0)
SelectedSaveSlot += SAVEGAME_MAX - 1;
else
SelectedSaveSlot--;
}
bool canLoop = g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::SaveLoadOnly ||
g_Configuration.MenuOptionLoopingMode == MenuOptionLoopingMode::AllMenus;
SelectedSaveSlot = GetLoopedSelectedOption(SelectedSaveSlot, SAVEGAME_MAX - 1, canLoop);
if (GuiIsSelected())
{

View file

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

View file

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

View file

@ -584,7 +584,7 @@ void InitializeItem(short itemNumber)
item->NextItem = room->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->BoxNumber = floor->PathfindingBoxID;

View file

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

View file

@ -18,5 +18,5 @@ enum LaraMeshMask
};
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);

View file

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

View file

@ -169,8 +169,8 @@ bool TargetVisible(ItemInfo* item, AI_INFO* ai, float maxAngleInDegrees)
auto target = GameVector(
enemy->Pose.Position.x,
enemy->Pose.Position.y + ((((bounds.Y1 * 2) + bounds.Y1) + bounds.Y2) / 4),
enemy->Pose.Position.z,
enemy->RoomNumber); // TODO: Check why this line didn't exist before. -- TokyoSU, 10/8/2022
enemy->Pose.Position.z);
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,
// must check both original room number and flippedRoom equality,
// as well as NO_VALUE if checking non-flipped rooms.
return (!FlipStats[flipNumber] && flippedRoom != index && flippedRoom != NO_VALUE) ||
( FlipStats[flipNumber] && flippedRoom == index);
return (!FlipStats[flipNumber] && flippedRoom != RoomNumber && flippedRoom != NO_VALUE) ||
( FlipStats[flipNumber] && flippedRoom == RoomNumber);
}
static void AddRoomFlipItems(const ROOM_INFO& room)
@ -105,11 +105,11 @@ void DoFlipMap(int group)
g_Renderer.FlipRooms(roomNumber, room.flippedRoom);
// Update active room sectors.
for (auto& sector : room.floor)
for (auto& sector : room.Sectors)
sector.RoomNumber = roomNumber;
// Update flipped room sectors.
for (auto& sector : flippedRoom.floor)
for (auto& sector : flippedRoom.Sectors)
sector.RoomNumber = room.flippedRoom;
}
}
@ -158,9 +158,9 @@ int IsRoomOutside(int x, int y, int z)
int roomNumber = OutsideRoomTable[xTable][zTable][i];
const auto& room = g_Level.Rooms[roomNumber];
if ((x > (room.x + BLOCK(1)) && x < (room.x + (room.xSize - 1) * BLOCK(1))) &&
(y > room.maxceiling && y < room.minfloor) &&
(z > (room.z + BLOCK(1)) && z < (room.z + (room.zSize - 1) * BLOCK(1))))
if ((x > (room.Position.x + BLOCK(1)) && x < (room.Position.x + (room.XSize - 1) * BLOCK(1))) &&
(y > room.TopHeight && y < room.BottomHeight) &&
(z > (room.Position.z + BLOCK(1)) && z < (room.Position.z + (room.ZSize - 1) * BLOCK(1))))
{
auto pointColl = GetPointCollision(Vector3i(x, y, z), roomNumber);
@ -188,14 +188,14 @@ namespace TEN::Collision::Room
// TODO: Can use floordata's GetRoomGridCoord()?
FloorInfo* GetSector(ROOM_INFO* room, int x, int z)
{
int sectorX = std::clamp(x / BLOCK(1), 0, room->xSize - 1);
int sectorZ = std::clamp(z / BLOCK(1), 0, room->zSize - 1);
int sectorX = std::clamp(x / BLOCK(1), 0, room->XSize - 1);
int sectorZ = std::clamp(z / BLOCK(1), 0, room->ZSize - 1);
int sectorID = sectorZ + (sectorX * room->zSize);
if (sectorID > room->floor.size())
int sectorID = sectorZ + (sectorX * room->ZSize);
if (sectorID > room->Sectors.size())
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];
if (pos.z >= (room.z + BLOCK(1)) && pos.z <= (room.z + BLOCK(room.zSize - 1)) &&
pos.y <= room.minfloor && pos.y > room.maxceiling &&
pos.x >= (room.x + BLOCK(1)) && pos.x <= (room.x + BLOCK(room.xSize - 1)))
if (pos.z >= (room.Position.z + BLOCK(1)) && pos.z <= (room.Position.z + BLOCK(room.ZSize - 1)) &&
pos.y <= room.BottomHeight && pos.y > room.TopHeight &&
pos.x >= (room.Position.x + BLOCK(1)) && pos.x <= (room.Position.x + BLOCK(room.XSize - 1)))
{
return true;
}
@ -234,7 +234,7 @@ int FindRoomNumber(const Vector3i& pos, int startRoomNumber)
if (startRoomNumber != NO_VALUE && startRoomNumber < g_Level.Rooms.size())
{
const auto& room = g_Level.Rooms[startRoomNumber];
for (int neighborRoomNumber : room.neighbors)
for (int neighborRoomNumber : room.NeighborRoomNumbers)
{
const auto& neighborRoom = g_Level.Rooms[neighborRoomNumber];
if (neighborRoomNumber != startRoomNumber && neighborRoom.Active() &&
@ -258,15 +258,15 @@ Vector3i GetRoomCenter(int roomNumber)
{
const auto& room = g_Level.Rooms[roomNumber];
int halfLength = BLOCK(room.xSize) / 2;
int halfDepth = BLOCK(room.zSize) / 2;
int halfHeight = (room.maxceiling - room.minfloor) / 2;
int halfLength = BLOCK(room.XSize) / 2;
int halfDepth = BLOCK(room.ZSize) / 2;
int halfHeight = (room.TopHeight - room.BottomHeight) / 2;
// Calculate and return center.
return Vector3i(
room.x + halfLength,
room.minfloor + halfHeight,
room.z + halfDepth);
room.Position.x + halfLength,
room.BottomHeight + halfHeight,
room.Position.z + halfDepth);
}
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++)
{
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.
@ -326,11 +326,11 @@ void InitializeNeighborRoomList()
if (room.flippedRoom == NO_VALUE)
continue;
if (!Contains(room.neighbors, room.flippedRoom))
room.neighbors.push_back(room.flippedRoom);
if (!Contains(room.NeighborRoomNumbers, room.flippedRoom))
room.NeighborRoomNumbers.push_back(room.flippedRoom);
auto& flippedRoom = g_Level.Rooms[room.flippedRoom];
if (!Contains(flippedRoom.neighbors, roomNumber))
flippedRoom.neighbors.push_back(roomNumber);
if (!Contains(flippedRoom.NeighborRoomNumbers, roomNumber))
flippedRoom.NeighborRoomNumbers.push_back(roomNumber);
}
}

View file

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

View file

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

View file

@ -1,4 +1,5 @@
#pragma once
#include "Math/Constants.h"
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 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)
{
return short(degrees * SHORTS_TO_1_DEGREE);
return (short)ROUND(degrees * SHORTS_TO_1_DEGREE);
}
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)

View file

@ -77,7 +77,7 @@ namespace TEN::Entities::Doors
xOffset = BLOCK(1);
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;
if (roomNumber == NO_VALUE)
@ -85,7 +85,7 @@ namespace TEN::Entities::Doors
else
{
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;
@ -94,7 +94,7 @@ namespace TEN::Entities::Doors
if (r->flippedRoom != -1)
{
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;
if (roomNumber == NO_VALUE)
@ -102,7 +102,7 @@ namespace TEN::Entities::Doors
else
{
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;
@ -124,7 +124,7 @@ namespace TEN::Entities::Doors
else
{
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;
if (roomNumber == NO_VALUE)
@ -132,7 +132,7 @@ namespace TEN::Entities::Doors
else
{
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;
@ -141,7 +141,7 @@ namespace TEN::Entities::Doors
if (r->flippedRoom != -1)
{
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;
if (roomNumber == NO_VALUE)
@ -149,7 +149,7 @@ namespace TEN::Entities::Doors
else
{
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;

View file

@ -315,7 +315,7 @@ namespace TEN::Entities::Generic
waterHeight = pointColl.GetWaterSurfaceHeight();
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);
}
@ -324,7 +324,7 @@ namespace TEN::Entities::Generic
waterHeight = pointColl.GetWaterSurfaceHeight();
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{};

View file

@ -187,8 +187,8 @@ namespace TEN::Entities::Generic
ForcedFixedCamera.x = trapDoorItem->Pose.Position.x - phd_sin(trapDoorItem->Pose.Orientation.y) * 2048;
ForcedFixedCamera.y = trapDoorItem->Pose.Position.y - 2048;
if (ForcedFixedCamera.y < g_Level.Rooms[trapDoorItem->RoomNumber].maxceiling)
ForcedFixedCamera.y = g_Level.Rooms[trapDoorItem->RoomNumber].maxceiling;
if (ForcedFixedCamera.y < g_Level.Rooms[trapDoorItem->RoomNumber].TopHeight)
ForcedFixedCamera.y = g_Level.Rooms[trapDoorItem->RoomNumber].TopHeight;
ForcedFixedCamera.z = trapDoorItem->Pose.Position.z - phd_cos(trapDoorItem->Pose.Orientation.y) * 2048;
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 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
{
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"
// Creatures
#include "Objects/TR1/Entity/Cowboy.h" // OK
#include "Objects/TR1/Entity/Kold.h" // OK
#include "Objects/TR1/Entity/tr1_ape.h" // OK
#include "Objects/TR1/Entity/tr1_bear.h" // OK
#include "Objects/TR1/Entity/tr1_doppelganger.h" // OK
#include "Objects/TR1/Entity/tr1_natla.h" // OK
#include "Objects/TR1/Entity/tr1_giant_mutant.h" // OK
#include "Objects/TR1/Entity/tr1_wolf.h" // OK
#include "Objects/TR1/Entity/tr1_big_rat.h" // OK
#include "Objects/TR1/Entity/tr1_centaur.h" // OK
#include "Objects/TR1/Entity/tr1_winged_mutant.h" // OK
#include "Objects/TR1/Entity/SkateboardKid.h" // OK
#include "Objects/TR1/Entity/Centaur.h"
#include "Objects/TR1/Entity/Cowboy.h"
#include "Objects/TR1/Entity/Kold.h"
#include "Objects/TR1/Entity/SkateboardKid.h"
#include "Objects/TR1/Entity/WingedMutant.h"
#include "Objects/TR1/Entity/tr1_ape.h"
#include "Objects/TR1/Entity/tr1_bear.h"
#include "Objects/TR1/Entity/tr1_doppelganger.h"
#include "Objects/TR1/Entity/tr1_natla.h"
#include "Objects/TR1/Entity/tr1_giant_mutant.h"
#include "Objects/TR1/Entity/tr1_wolf.h"
#include "Objects/TR1/Entity/tr1_big_rat.h"
#include "Objects/Utils/object_helper.h"
// Traps
@ -149,7 +149,7 @@ static void StartEntity(ObjectInfo* obj)
if (obj->loaded)
{
obj->Initialize = InitializeCreature;
obj->control = CentaurControl;
obj->control = ControlCentaur;
obj->collision = CreatureCollision;
obj->shadowType = ShadowMode::All;
obj->HitPoints = 120;
@ -157,7 +157,8 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = BLOCK(1 / 3.0f);
obj->intelligent = true;
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();
}
@ -165,7 +166,7 @@ static void StartEntity(ObjectInfo* obj)
if (obj->loaded)
{
obj->Initialize = InitializeWingedMutant;
obj->control = WingedMutantControl;
obj->control = ControlWingedMutant;
obj->collision = CreatureCollision;
obj->shadowType = ShadowMode::All;
obj->pivotLength = 150;
@ -274,7 +275,6 @@ static void StartProjectiles(ObjectInfo* obj)
{
InitProjectile(obj, ControlMissile, ID_PROJ_SHARD);
InitProjectile(obj, ControlMissile, ID_PROJ_BOMB);
InitProjectile(obj, ControlMissile, ID_PROJ_BOMB);
}
void InitializeTR1Objects()

View file

@ -72,14 +72,17 @@ namespace TEN::Entities::Creatures::TR3
(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++)
ThrowPoison(itemNumber, bite, Vector3i(0.0f, -100.0f, speed << 2), Vector3(0.0f, 1.0f, 0.0f));
ThrowPoison(itemNumber, bite, Vector3i(0.0f, -100.0f, speed << 1), Vector3(0.0f, 1.0f, 0.0f));
{
auto colorStart = Color(0.0f, Random::GenerateFloat(0.25f, 0.5f), 0.1f);
auto colorEnd = Color(0.0f, Random::GenerateFloat(0.1f, 0.2f), 0.1f);
ThrowPoison(item, bite, velVector, colorStart, colorEnd);
}
}
void LizardControl(short itemNumber)
@ -319,9 +322,13 @@ namespace TEN::Entities::Creatures::TR3
creature.Flags += 2;
if (creature.Flags < 24)
SpawnLizardGas(itemNumber, LizardGasBite, creature.Flags);
{
SpawnLizardGas(item, LizardGasBite, creature.Flags);
}
else
SpawnLizardGas(itemNumber, LizardGasBite, (GetRandomControl() & 15) + 8);
{
SpawnLizardGas(item, LizardGasBite, (GetRandomControl() & 15) + 8);
}
}
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_HIT_POINTS_MAX = 28;
const auto TwinAutoGunLeftBite = CreatureBiteInfo(110, -30, 530, 2);
const auto TwinAutoGunRightBite = CreatureBiteInfo(-110, -30, 530, 2);
const auto TwinAutoGunLeftBite = CreatureBiteInfo(Vector3(110.0f, -30.0f, 530.0f), 2);
const auto TwinAutoGunRightBite = CreatureBiteInfo(Vector3(-110.0f, -30.0f, 530.0f), 2);
enum TwinAutoGunAnim
{

View file

@ -28,6 +28,7 @@ namespace TEN::Entities::Creatures::TR3
constexpr auto FLAMETHROWER_WALK_TURN_RATE_MAX = ANGLE(5.0f);
const auto FlamethrowerBite = CreatureBiteInfo(Vector3(0, 340, 64), 7);
const auto FlamethrowerTargetIds = { ID_LARA, ID_SEAL_MUTANT };
// TODO
enum FlamethrowerState
@ -94,30 +95,7 @@ namespace TEN::Entities::Creatures::TR3
}
else
{
creature->Enemy = nullptr;
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;
}
}
TargetNearestEntity(item, creature, FlamethrowerTargetIds, false);
}
AI_INFO AI;
@ -311,10 +289,8 @@ namespace TEN::Entities::Creatures::TR3
else
{
ThrowFire(itemNumber, FlamethrowerBite, Vector3i(0, (Random::GenerateInt() & 63) + 12, 0));
if (realEnemy)
{
/*code*/
}
if (realEnemy && realEnemy->ObjectNumber == ID_SEAL_MUTANT)
realEnemy->ItemFlags[0]++;
}
SoundEffect(SFX_TR4_FLAME_EMITTER, &item->Pose);
@ -345,10 +321,8 @@ namespace TEN::Entities::Creatures::TR3
else
{
ThrowFire(itemNumber, FlamethrowerBite, Vector3i(0, (GetRandomControl() & 63) + 12, 0));
if (realEnemy)
{
/*code*/
}
if (realEnemy && realEnemy->ObjectNumber == ID_SEAL_MUTANT)
realEnemy->ItemFlags[0]++;
}
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)
{
for (auto i : g_Level.Rooms[kayakItem->RoomNumber].neighbors)
for (auto i : g_Level.Rooms[kayakItem->RoomNumber].NeighborRoomNumbers)
{
if (!g_Level.Rooms[i].Active())
continue;

View file

@ -281,7 +281,7 @@ namespace TEN::Entities::Vehicles
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())
continue;

View file

@ -8,28 +8,29 @@
#include "Specific/level.h"
// Creatures
#include "Objects/TR3/Entity/Compsognathus.h" // OK
#include "Objects/TR3/Entity/Lizard.h" // OK
#include "Objects/TR3/Entity/PunaBoss.h" // OK
#include "Objects/TR3/Entity/Shiva.h" // OK
#include "Objects/TR3/Entity/SophiaLeigh.h" // OK
#include "Objects/TR3/Entity/Compsognathus.h"
#include "Objects/TR3/Entity/Lizard.h"
#include "Objects/TR3/Entity/PunaBoss.h"
#include "Objects/TR3/Entity/SealMutant.h"
#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/WaspMutant.h" // OK
#include "Objects/TR3/Entity/Winston.h" // OK
#include "Objects/TR3/Entity/tr3_tony.h" // OK
#include "Objects/TR3/Entity/tr3_civvy.h" // OK
#include "Objects/TR3/Entity/tr3_claw_mutant.h" // OK
#include "Objects/TR3/Entity/tr3_cobra.h" // OK
#include "Objects/TR3/Entity/FishSwarm.h" // OK
#include "Objects/TR3/Entity/tr3_flamethrower.h" // OK
#include "Objects/TR3/Entity/tr3_monkey.h" // OK
#include "Objects/TR3/Entity/tr3_mp_gun.h" // OK
#include "Objects/TR3/Entity/tr3_mp_stick.h" // OK
#include "Objects/TR3/Entity/tr3_raptor.h" // OK
#include "Objects/TR3/Entity/tr3_scuba_diver.h" // OK
#include "Objects/TR3/Entity/tr3_tiger.h" // OK
#include "Objects/TR3/Entity/tr3_trex.h" // OK
#include "Objects/TR3/Entity/tr3_tribesman.h" // OK
#include "Objects/TR3/Entity/WaspMutant.h"
#include "Objects/TR3/Entity/Winston.h"
#include "Objects/TR3/Entity/tr3_tony.h"
#include "Objects/TR3/Entity/tr3_civvy.h"
#include "Objects/TR3/Entity/tr3_claw_mutant.h"
#include "Objects/TR3/Entity/tr3_cobra.h"
#include "Objects/TR3/Entity/FishSwarm.h"
#include "Objects/TR3/Entity/tr3_flamethrower.h"
#include "Objects/TR3/Entity/tr3_monkey.h"
#include "Objects/TR3/Entity/tr3_mp_gun.h"
#include "Objects/TR3/Entity/tr3_mp_stick.h"
#include "Objects/TR3/Entity/tr3_scuba_diver.h"
#include "Objects/TR3/Entity/tr3_tiger.h"
#include "Objects/TR3/Entity/tr3_trex.h"
#include "Objects/TR3/Entity/tr3_tribesman.h"
// Effects
#include "Objects/Effects/Boss.h"
@ -117,6 +118,7 @@ static void StartEntity(ObjectInfo* obj)
obj->radius = 341;
obj->pivotLength = 600;
obj->intelligent = true;
obj->LotType = LotType::HumanPlusJump;
obj->SetBoneRotationFlags(20, ROT_Y);
obj->SetBoneRotationFlags(21, ROT_Y);
obj->SetBoneRotationFlags(23, ROT_Y);
@ -412,6 +414,22 @@ static void StartEntity(ObjectInfo* obj)
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];
if (obj->loaded)
{

View file

@ -229,13 +229,13 @@ namespace TEN::Entities::TR4
{
const auto& room = g_Level.Rooms[LaraItem->RoomNumber];
x = room.x + room.xSize * BLOCK(1) / 2 - item.Pose.Position.x;
z = room.z + room.zSize * BLOCK(1) / 2 - item.Pose.Position.z;
x = room.Position.x + room.XSize * BLOCK(1) / 2 - item.Pose.Position.x;
z = room.Position.z + room.ZSize * BLOCK(1) / 2 - item.Pose.Position.z;
distance = SQUARE(x) + SQUARE(z);
dy = abs((distance / MAX_VISIBILITY_DISTANCE) - CLICK(1));
//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);

View file

@ -186,7 +186,7 @@ namespace TEN::Entities::TR4
auto pos = GetJointPosition(item, LM_LINARM);
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)
{

View file

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

View file

@ -232,9 +232,9 @@ void UpdateSpiders()
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->VerticalVelocity = 1;
}

View file

@ -337,7 +337,7 @@ namespace TEN::Entities::Creatures::TR5
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)
{
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];
int x = room->x + (creature->Tosspad / 256 & 0xFF) * BLOCK(1) + 512;
int y = room->minfloor + floorHeight;
int z = room->z + (creature->Tosspad & 0xFF) * BLOCK(1) + 512;
int x = room->Position.x + (creature->Tosspad / 256 & 0xFF) * BLOCK(1) + 512;
int y = room->BottomHeight + floorHeight;
int z = room->Position.z + (creature->Tosspad & 0xFF) * BLOCK(1) + 512;
TestTriggers(x, y, z, roomNumber, true);

View file

@ -543,7 +543,7 @@ namespace TEN::Entities::Creatures::TR5
pos = GetJointPosition(item, 16);
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->Stopper)
@ -823,9 +823,9 @@ namespace TEN::Entities::Creatures::TR5
short floorHeight = item->ItemFlags[2] & 0xFF00;
auto* room = &g_Level.Rooms[roomNumber];
int x = room->x + (creature->Tosspad / 256 & 0xFF) * BLOCK(1) + 512;
int y = room->minfloor + floorHeight;
int z = room->z + (creature->Tosspad & 0xFF) * BLOCK(1) + 512;
int x = room->Position.x + (creature->Tosspad / 256 & 0xFF) * BLOCK(1) + 512;
int y = room->BottomHeight + floorHeight;
int z = room->Position.z + (creature->Tosspad & 0xFF) * BLOCK(1) + 512;
TestTriggers(x, y, z, roomNumber, true);
}

View file

@ -82,7 +82,7 @@ namespace TEN::Entities::Generic
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)
UpdateBridgeItem(*item);

View file

@ -14,10 +14,10 @@ void InitializeSmashObject(short itemNumber)
item->Flags = 0;
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
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)
{
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* 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)
box->flags &= ~BOX_BLOCKED;

View file

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

View file

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

View file

@ -1725,7 +1725,7 @@ namespace TEN::Renderer
//RenderSimpleSceneToParaboloid(&_roomAmbientMapsCache[ambientMapCacheIndex].Back, LaraItem->Pose.Position.ToVector3(), -1);
// 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);
// 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); }
// 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 char* Str_LoadSave(bool save = false) { return g_GameFlow->GetString(save ? STRING_SAVE_GAME : STRING_LOAD_GAME); }
inline const std::string Str_Enabled(bool enabled = false) { return g_GameFlow->GetString(enabled ? STRING_ENABLED : STRING_DISABLED); }
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.
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));
GetNextLinePosition(&y);
// Mouse smoothing
AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_MOUSE_SMOOTHING), 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));
// Menu option looping
AddString(MenuLeftSideEntry, y, g_GameFlow->GetString(STRING_MENU_OPT_LOOP), PRINTSTRING_COLOR_ORANGE, SF(titleOption == 10));
AddString(MenuRightSideEntry, y, Str_MenuOptionLoopingMode(g_Gui.GetCurrentSettings().Configuration.MenuOptionLoopingMode), PRINTSTRING_COLOR_WHITE, SF(titleOption == 10));
GetNextBlockPosition(&y);
// Apply
@ -493,6 +508,14 @@ namespace TEN::Renderer
GetNextLinePosition(&y);
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
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());
GetNextBlockPosition(&y);
// Level listing (starts with 1 because 0 is always title)
for (int i = 1; i < g_GameFlow->GetNumLevels(); i++, selectedOption++)
// Level 0 is always Title Level and level 1 might be Home Level.
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));
GetNextNarrowLinePosition(&y);
}
break;
case Menu::Options:
@ -1225,8 +1250,8 @@ namespace TEN::Renderer
PrintDebugMessage("RoomNumber: %d", LaraItem->RoomNumber);
PrintDebugMessage("PathfindingBoxID: %d", LaraItem->BoxNumber);
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.y, minFloor, maxCeiling: %d, %d, %d ", room.y, room.minfloor, room.maxceiling);
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.Position.y, room.BottomHeight, room.TopHeight);
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 RoomNumber: %d", Camera.pos.RoomNumber);

View file

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

View file

@ -3,7 +3,7 @@
// std::string ID macros
#define STRING_WINDOW_TITLE "window_title"
#define STRING_PASSPORT "passport"
#define STRING_LARA_HOME "lara_home"
#define STRING_HOME_LEVEL "home_level"
#define STRING_CONTROLS "controls"
#define STRING_DISPLAY "display"
#define STRING_OTHER_SETTINGS "other_settings"
@ -86,8 +86,11 @@
#define STRING_RUMBLE "rumble"
#define STRING_THUMBSTICK_CAMERA "thumbstick_camera"
#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_SMOOTHING "mouse_smoothing"
#define STRING_ACTIONS_FORWARD "actions_forward"
#define STRING_ACTIONS_BACKWARD "actions_backward"
#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_EnableLaraInTitle[] = "EnableLaraInTitle";
static constexpr char ScriptReserved_EnableLevelSelect[] = "EnableLevelSelect";
static constexpr char ScriptReserved_EnableHomeLevel[] = "EnableHomeLevel";
static constexpr char ScriptReserved_EnableLoadSave[] = "EnableLoadSave";
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);
/*** Enable or disable saving and loading of savegames.
@function EnableLoadSave
@tparam bool enabled true or false.
*/
/// Enable or disable Home Level entry in the main menu.
// @function EnableHomeLevel()
// @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);
/*** gameflow.lua or level scripts.
@section FlowluaOrScripts
*/
/*** Enable or disable DOZY mode (fly cheat).
Must be true or false
@function EnableFlyCheat
@tparam bool enabled true or false
*/
/// Enable or disable the fly cheat.
// @function EnableFlyCheat()
// @tparam bool enabled True or false.
tableFlow.set_function(ScriptReserved_EnableFlyCheat, &FlowHandler::EnableFlyCheat, this);
/*** Enable or disable point texture filter.
@ -537,9 +539,9 @@ bool FlowHandler::IsFlyCheatEnabled() const
return FlyCheat;
}
void FlowHandler::EnableFlyCheat(bool flyCheat)
void FlowHandler::EnableFlyCheat(bool enable)
{
FlyCheat = flyCheat;
FlyCheat = enable;
}
bool FlowHandler::IsPointFilterEnabled() const
@ -547,9 +549,9 @@ bool FlowHandler::IsPointFilterEnabled() const
return PointFilter;
}
void FlowHandler::EnablePointFilter(bool pointFilter)
void FlowHandler::EnablePointFilter(bool enable)
{
PointFilter = pointFilter;
PointFilter = enable;
}
bool FlowHandler::IsMassPickupEnabled() const
@ -557,9 +559,9 @@ bool FlowHandler::IsMassPickupEnabled() const
return MassPickup;
}
void FlowHandler::EnableMassPickup(bool massPickup)
void FlowHandler::EnableMassPickup(bool enable)
{
MassPickup = massPickup;
MassPickup = enable;
}
bool FlowHandler::IsLaraInTitleEnabled() const
@ -567,14 +569,24 @@ bool FlowHandler::IsLaraInTitleEnabled() const
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
@ -582,9 +594,9 @@ bool FlowHandler::IsLoadSaveEnabled() const
return LoadSave;
}
void FlowHandler::EnableLoadSave(bool loadSave)
void FlowHandler::EnableLoadSave(bool enable)
{
LoadSave = loadSave;
LoadSave = enable;
}
void FlowHandler::PrepareInventoryObjects()
@ -674,12 +686,20 @@ bool FlowHandler::DoFlow()
break;
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;
SelectedLevelForNewGame = 0;
InitializeGame = true;
break;
case GameStatus::HomeLevel:
CurrentLevel = 1;
RequiredStartPos = 0;
InitializeGame = true;
break;
case GameStatus::LoadGame:
// Load header of savegame to get level to load.
SaveGame::LoadHeader(SelectedSaveGame, &header);

View file

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

View file

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

View file

@ -111,7 +111,7 @@ private:
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; }))
{
rooms.push_back(GetByName<Room, ScriptReserved_Room>(key));

View file

@ -104,7 +104,7 @@ void Room::SetReverbType(ReverbType reverb)
std::string Room::GetName() const
{
return m_room.name;
return m_room.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.
if (s_callbackSetName(name, m_room))
{
s_callbackRemoveName(m_room.name);
m_room.name = name;
s_callbackRemoveName(m_room.Name);
m_room.Name = name;
}
else
{
@ -144,10 +144,10 @@ void Room::SetFlag(RoomEnvFlags flag, bool value)
bool Room::IsTagPresent(const std::string& tag) const
{
if (m_room.tags.empty())
if (m_room.Tags.empty())
return false;
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); });
}

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.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 smoothing = 1.0f - (g_Configuration.MouseSmoothing * 0.1f);
normAxes *= sensitivity * smoothing;
normAxes *= sensitivity;
// Set mouse axis values.
AxisMap[(int)InputAxis::Mouse] = normAxes;

View file

@ -257,7 +257,7 @@ bool SaveConfiguration()
// Set Input keys.
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(graphicsKey);
@ -330,7 +330,7 @@ void InitDefaultConfiguration()
g_Configuration.EnableThumbstickCamera = false;
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.AdapterName = g_Renderer.GetDefaultAdapterName();
@ -444,14 +444,14 @@ bool LoadConfiguration()
}
DWORD mouseSensitivity = GameConfiguration::DEFAULT_MOUSE_SENSITIVITY;
DWORD mouseSmoothing = GameConfiguration::DEFAULT_MOUSE_SMOOTHING;
DWORD menuOptionLoopingMode = (DWORD)MenuOptionLoopingMode::SaveLoadOnly;
// Load Input keys.
HKEY inputKey = NULL;
if (RegOpenKeyExA(rootKey, REGKEY_INPUT, 0, KEY_READ, &inputKey) == 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(graphicsKey);
@ -519,7 +519,7 @@ bool LoadConfiguration()
g_Configuration.EnableThumbstickCamera = enableThumbstickCamera;
g_Configuration.MouseSensitivity = mouseSensitivity;
g_Configuration.MouseSmoothing = mouseSmoothing;
g_Configuration.MenuOptionLoopingMode = (MenuOptionLoopingMode)menuOptionLoopingMode;
// Set legacy variables.
SetVolumeTracks(musicVolume);

View file

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

View file

@ -687,17 +687,17 @@ void ReadRooms()
{
auto& room = g_Level.Rooms.emplace_back();
room.name = ReadString();
room.Name = ReadString();
int tagCount = ReadInt32();
for (int j = 0; j < tagCount; j++)
room.tags.push_back(ReadString());
room.Tags.push_back(ReadString());
room.x = ReadInt32();
room.y = 0;
room.z = ReadInt32();
room.minfloor = ReadInt32();
room.maxceiling = ReadInt32();
room.Position.x = ReadInt32();
room.Position.y = 0;
room.Position.z = ReadInt32();
room.BottomHeight = ReadInt32();
room.TopHeight = ReadInt32();
int vertexCount = ReadInt32();
@ -769,14 +769,14 @@ void ReadRooms()
for (int j = 0; j < portalCount; j++)
LoadPortal(room);
room.zSize = ReadInt32();
room.xSize = ReadInt32();
auto roomPos = Vector2i(room.x, room.z);
room.ZSize = ReadInt32();
room.XSize = ReadInt32();
auto roomPos = Vector2i(room.Position.x, room.Position.z);
room.floor.reserve(room.zSize * room.xSize);
for (int x = 0; x < room.xSize; x++)
room.Sectors.reserve(room.XSize * room.ZSize);
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{};
@ -820,17 +820,17 @@ void ReadRooms()
sector.Flags.MarkTriggererActive = 0; // TODO: Needs to be written to and read from savegames.
sector.Flags.MarkBeetle = ReadBool();
room.floor.push_back(sector);
room.Sectors.push_back(sector);
}
}
room.ambient = ReadVector3();
int numLights = ReadInt32();
room.lights.reserve(numLights);
for (int j = 0; j < numLights; j++)
int lightCount = ReadInt32();
room.lights.reserve(lightCount);
for (int j = 0; j < lightCount; j++)
{
ROOM_LIGHT light;
auto light = ROOM_LIGHT{};
light.x = ReadInt32();
light.y = ReadInt32();
@ -852,9 +852,9 @@ void ReadRooms()
room.lights.push_back(light);
}
int numStatics = ReadInt32();
room.mesh.reserve(numStatics);
for (int j = 0; j < numStatics; j++)
int staticCount = ReadInt32();
room.mesh.reserve(staticCount);
for (int j = 0; j < staticCount; j++)
{
auto& mesh = room.mesh.emplace_back();
@ -875,21 +875,17 @@ void ReadRooms()
g_GameScriptEntities->AddName(mesh.Name, mesh);
}
int numTriggerVolumes = ReadInt32();
// Reserve in advance so the vector doesn't resize itself and leave anything
// in the script name-to-reference map obsolete.
room.triggerVolumes.reserve(numTriggerVolumes);
for (int j = 0; j < numTriggerVolumes; j++)
int triggerVolumeCount = ReadInt32();
room.TriggerVolumes.reserve(triggerVolumeCount);
for (int j = 0; j < triggerVolumeCount; j++)
{
auto& volume = room.triggerVolumes.emplace_back();
auto& volume = room.TriggerVolumes.emplace_back();
volume.Type = (VolumeType)ReadInt32();
// NOTE: Braces are necessary to ensure correct value init order.
auto pos = Vector3{ ReadFloat(), ReadFloat(), ReadFloat() };
auto orient = Quaternion{ ReadFloat(), ReadFloat(), ReadFloat(), ReadFloat() };
auto scale = Vector3{ ReadFloat(), ReadFloat(), ReadFloat() };
auto pos = ReadVector3();
auto orient = ReadVector4();
auto scale = ReadVector3();
volume.Enabled = ReadBool();
volume.DetectInAdjacentRooms = ReadBool();
@ -913,9 +909,9 @@ void ReadRooms()
room.itemNumber = 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];
int rx = (room->x / BLOCK(1));
int rz = (room->z / BLOCK(1));
int rx = (room->Position.x / BLOCK(1));
int rz = (room->Position.z / BLOCK(1));
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;
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;
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\puzzles_keys.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\Kold.h" />
<ClInclude Include="Objects\TR1\Entity\tr1_ape.h" />
<ClInclude Include="Objects\TR1\Entity\tr1_bear.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_giant_mutant.h" />
<ClInclude Include="Objects\TR1\Entity\tr1_natla.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\WingedMutant.h" />
<ClInclude Include="Objects\TR1\Trap\DamoclesSword.h" />
<ClInclude Include="Objects\TR1\Trap\SlammingDoors.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\FishSwarm.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\Raptor.h" />
<ClInclude Include="Objects\TR3\Entity\Shiva.h" />
<ClInclude Include="Objects\TR3\Entity\SophiaLeigh.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_mp_gun.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_tiger.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\dart_emitter.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\Kold.cpp" />
<ClCompile Include="Objects\TR1\Entity\tr1_ape.cpp" />
<ClCompile Include="Objects\TR1\Entity\tr1_bear.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_giant_mutant.cpp" />
<ClCompile Include="Objects\TR1\Entity\tr1_natla.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\WingedMutant.cpp" />
<ClCompile Include="Objects\TR1\tr1_objects.cpp" />
<ClCompile Include="Objects\TR1\Trap\DamoclesSword.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\FishSwarm.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\Raptor.cpp" />
<ClCompile Include="Objects\TR3\Entity\Shiva.cpp" />
<ClCompile Include="Objects\TR3\Entity\SophiaLeigh.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_mp_gun.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_tiger.cpp" />
<ClCompile Include="Objects\TR3\Entity\tr3_tony.cpp" />