Merge branch 'master' into math

This commit is contained in:
Sezz 2022-09-13 11:44:51 +10:00
commit dbe0713deb
185 changed files with 2928 additions and 2546 deletions

View file

@ -21,7 +21,9 @@ Version 1.0.2
* Fix occasional leave event calls when moving closer to volumes. * Fix occasional leave event calls when moving closer to volumes.
* Fix incorrect viewport size in windowed mode. * Fix incorrect viewport size in windowed mode.
* Fix late landing animation dispatch in rare cases. * Fix late landing animation dispatch in rare cases.
* Fix Lara's subway train death so that she no longer slides slowly after the animation has finished. * Fix Lara's subway train death so that she no longer slides slowly after the animation has finished.
* Fix horseman's axe attack using his left foot as the damaging joint.
* Fix stargate blades needlessly pushing the player around while hardly doing any damage.
Lua API changes: Lua API changes:

View file

@ -477,7 +477,9 @@ BOUNDING_BOX* GetBoundsAccurate(ItemInfo* item)
InterpolatedBounds.Y2 = framePtr[0]->boundingBox.Y2 + (framePtr[1]->boundingBox.Y2 - framePtr[0]->boundingBox.Y2) * frac / rate; InterpolatedBounds.Y2 = framePtr[0]->boundingBox.Y2 + (framePtr[1]->boundingBox.Y2 - framePtr[0]->boundingBox.Y2) * frac / rate;
InterpolatedBounds.Z1 = framePtr[0]->boundingBox.Z1 + (framePtr[1]->boundingBox.Z1 - framePtr[0]->boundingBox.Z1) * frac / rate; InterpolatedBounds.Z1 = framePtr[0]->boundingBox.Z1 + (framePtr[1]->boundingBox.Z1 - framePtr[0]->boundingBox.Z1) * frac / rate;
InterpolatedBounds.Z2 = framePtr[0]->boundingBox.Z2 + (framePtr[1]->boundingBox.Z2 - framePtr[0]->boundingBox.Z2) * frac / rate; InterpolatedBounds.Z2 = framePtr[0]->boundingBox.Z2 + (framePtr[1]->boundingBox.Z2 - framePtr[0]->boundingBox.Z2) * frac / rate;
return &InterpolatedBounds; {
return &InterpolatedBounds;
}
} }
} }

View file

@ -271,10 +271,10 @@ bool TestWithGlobalCollisionBounds(ItemInfo* item, ItemInfo* laraItem, Collision
{ {
auto* framePtr = GetBestFrame(laraItem); auto* framePtr = GetBestFrame(laraItem);
if (item->Pose.Position.y + GlobalCollisionBounds.Y2 <= laraItem->Pose.Position.y + framePtr->boundingBox.Y1) if ((item->Pose.Position.y + GlobalCollisionBounds.Y2) <= (laraItem->Pose.Position.y + framePtr->boundingBox.Y1))
return false; return false;
if (item->Pose.Position.y + GlobalCollisionBounds.Y1 >= framePtr->boundingBox.Y2) if ((item->Pose.Position.y + GlobalCollisionBounds.Y1) >= framePtr->boundingBox.Y2)
return false; return false;
float sinY = phd_sin(item->Pose.Orientation.y); float sinY = phd_sin(item->Pose.Orientation.y);
@ -286,10 +286,10 @@ bool TestWithGlobalCollisionBounds(ItemInfo* item, ItemInfo* laraItem, Collision
int x = (dx * cosY) - (dz * sinY); int x = (dx * cosY) - (dz * sinY);
int z = (dz * cosY) + (dx * sinY); int z = (dz * cosY) + (dx * sinY);
if (x < GlobalCollisionBounds.X1 - coll->Setup.Radius || if (x < (GlobalCollisionBounds.X1 - coll->Setup.Radius) ||
x > GlobalCollisionBounds.X2 + coll->Setup.Radius || x > (GlobalCollisionBounds.X2 + coll->Setup.Radius) ||
z < GlobalCollisionBounds.Z1 - coll->Setup.Radius || z < (GlobalCollisionBounds.Z1 - coll->Setup.Radius) ||
z > GlobalCollisionBounds.Z2 + coll->Setup.Radius) z > (GlobalCollisionBounds.Z2 + coll->Setup.Radius))
{ {
return false; return false;
} }
@ -346,16 +346,14 @@ void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll)
itemNumber = item2->NextItem; itemNumber = item2->NextItem;
} }
for (int j = 0; j < g_Level.Rooms[i].mesh.size(); j++) for (auto& mesh : g_Level.Rooms[i].mesh)
{ {
auto* mesh = &g_Level.Rooms[i].mesh[j]; if (!(mesh.flags & StaticMeshFlags::SM_VISIBLE))
if (!(mesh->flags & StaticMeshFlags::SM_VISIBLE))
continue; continue;
if (Vector3i::Distance(item->Pose.Position, mesh->pos.Position) < COLLISION_CHECK_DISTANCE) if (Vector3i::Distance(item->Pose.Position, mesh.pos.Position) < COLLISION_CHECK_DISTANCE)
{ {
auto box = TO_DX_BBOX(mesh->pos, GetBoundsAccurate(mesh, false)); auto box = TO_DX_BBOX(mesh.pos, GetBoundsAccurate(&mesh, false));
float distance; float distance;
if (box.Intersects(origin, direction, distance) && distance < coll->Setup.Radius * 2) if (box.Intersects(origin, direction, distance) && distance < coll->Setup.Radius * 2)
@ -476,12 +474,12 @@ static bool ItemInRange(int x, int z, int radius)
return ((SQUARE(x) + SQUARE(z)) <= SQUARE(radius)); return ((SQUARE(x) + SQUARE(z)) <= SQUARE(radius));
} }
bool ItemNearLara(PoseData* pos, int radius) bool ItemNearLara(Vector3i* origin, int radius)
{ {
auto target = GameVector( auto target = GameVector(
pos->Position.x - LaraItem->Pose.Position.x, origin->x - LaraItem->Pose.Position.x,
pos->Position.y - LaraItem->Pose.Position.y, origin->y - LaraItem->Pose.Position.y,
pos->Position.z - LaraItem->Pose.Position.z origin->z - LaraItem->Pose.Position.z
); );
if (!ItemCollide(target.y, ITEM_RADIUS_YMAX)) if (!ItemCollide(target.y, ITEM_RADIUS_YMAX))
@ -500,9 +498,9 @@ bool ItemNearLara(PoseData* pos, int radius)
return false; return false;
} }
bool ItemNearTarget(PoseData* origin, ItemInfo* target, int radius) bool ItemNearTarget(Vector3i* origin, ItemInfo* targetEntity, int radius)
{ {
auto pos = origin->Position - target->Pose.Position; auto pos = *origin - targetEntity->Pose.Position;
if (!ItemCollide(pos.y, ITEM_RADIUS_YMAX)) if (!ItemCollide(pos.y, ITEM_RADIUS_YMAX))
return false; return false;
@ -513,31 +511,31 @@ bool ItemNearTarget(PoseData* origin, ItemInfo* target, int radius)
if (!ItemInRange(pos.x, pos.z, radius)) if (!ItemInRange(pos.x, pos.z, radius))
return false; return false;
auto* bounds = GetBoundsAccurate(target); auto* bounds = GetBoundsAccurate(targetEntity);
if (pos.y >= bounds->Y1 && pos.y <= bounds->Y2) if (pos.y >= bounds->Y1 && pos.y <= bounds->Y2)
return true; return true;
return false; return false;
} }
bool Move3DPosTo3DPos(PoseData* origin, PoseData* target, int velocity, short angleAdd) bool Move3DPosTo3DPos(PoseData* fromPose, PoseData* toPose, int velocity, short angleAdd)
{ {
auto direction = target->Position - origin->Position; auto direction = toPose->Position - fromPose->Position;
int distance = sqrt(SQUARE(direction.x) + SQUARE(direction.y) + SQUARE(direction.z)); float distance = Vector3i::Distance(fromPose->Position, toPose->Position);
if (velocity < distance) if (velocity < distance)
origin->Position += direction * velocity / distance; fromPose->Position += direction * (velocity / distance);
else else
origin->Position = target->Position; fromPose->Position = toPose->Position;
if (!Lara.Control.IsMoving) if (!Lara.Control.IsMoving)
{ {
bool shouldAnimate = (distance - velocity) > (velocity * ANIMATED_ALIGNMENT_FRAME_COUNT_THRESHOLD); bool shouldAnimate = ((distance - velocity) > (velocity * ANIMATED_ALIGNMENT_FRAME_COUNT_THRESHOLD));
if (shouldAnimate && Lara.Control.WaterStatus != WaterStatus::Underwater) if (shouldAnimate && Lara.Control.WaterStatus != WaterStatus::Underwater)
{ {
int angle = mGetAngle(target->Position.x, target->Position.z, origin->Position.x, origin->Position.z); int angle = mGetAngle(toPose->Position.x, toPose->Position.z, fromPose->Position.x, fromPose->Position.z);
int direction = (GetQuadrant(angle) - GetQuadrant(target->Orientation.y)) & 3; int direction = (GetQuadrant(angle) - GetQuadrant(toPose->Orientation.y)) & 3;
switch (direction) switch (direction)
{ {
@ -568,31 +566,31 @@ bool Move3DPosTo3DPos(PoseData* origin, PoseData* target, int velocity, short an
Lara.Control.Count.PositionAdjust = 0; Lara.Control.Count.PositionAdjust = 0;
} }
short deltaAngle = target->Orientation.x - origin->Orientation.x; short deltaAngle = toPose->Orientation.x - fromPose->Orientation.x;
if (deltaAngle > angleAdd) if (deltaAngle > angleAdd)
origin->Orientation.x += angleAdd; fromPose->Orientation.x += angleAdd;
else if (deltaAngle < -angleAdd) else if (deltaAngle < -angleAdd)
origin->Orientation.x -= angleAdd; fromPose->Orientation.x -= angleAdd;
else else
origin->Orientation.x = target->Orientation.x; fromPose->Orientation.x = toPose->Orientation.x;
deltaAngle = target->Orientation.y - origin->Orientation.y; deltaAngle = toPose->Orientation.y - fromPose->Orientation.y;
if (deltaAngle > angleAdd) if (deltaAngle > angleAdd)
origin->Orientation.y += angleAdd; fromPose->Orientation.y += angleAdd;
else if (deltaAngle < -angleAdd) else if (deltaAngle < -angleAdd)
origin->Orientation.y -= angleAdd; fromPose->Orientation.y -= angleAdd;
else else
origin->Orientation.y = target->Orientation.y; fromPose->Orientation.y = toPose->Orientation.y;
deltaAngle = target->Orientation.z - origin->Orientation.z; deltaAngle = toPose->Orientation.z - fromPose->Orientation.z;
if (deltaAngle > angleAdd) if (deltaAngle > angleAdd)
origin->Orientation.z += angleAdd; fromPose->Orientation.z += angleAdd;
else if (deltaAngle < -angleAdd) else if (deltaAngle < -angleAdd)
origin->Orientation.z -= angleAdd; fromPose->Orientation.z -= angleAdd;
else else
origin->Orientation.z = target->Orientation.z; fromPose->Orientation.z = toPose->Orientation.z;
return (origin->Position == target->Position && origin->Orientation == target->Orientation); return (fromPose->Position == toPose->Position && fromPose->Orientation == toPose->Orientation);
} }
bool TestBoundsCollide(ItemInfo* item, ItemInfo* laraItem, int radius) bool TestBoundsCollide(ItemInfo* item, ItemInfo* laraItem, int radius)
@ -611,13 +609,13 @@ bool TestBoundsCollide(ItemInfo* item, ItemInfo* laraItem, int radius)
int x = laraItem->Pose.Position.x - item->Pose.Position.x; int x = laraItem->Pose.Position.x - item->Pose.Position.x;
int z = laraItem->Pose.Position.z - item->Pose.Position.z; int z = laraItem->Pose.Position.z - item->Pose.Position.z;
int dx = (cosY * x) - (sinY * z); int dx = (x * cosY) - (z * sinY);
int dz = (cosY * z) + (sinY * x); int dz = (z * cosY) + (x * sinY);
if (dx >= bounds->X1 - radius && if (dx >= (bounds->X1 - radius) &&
dx <= radius + bounds->X2 && dx <= (radius + bounds->X2) &&
dz >= bounds->Z1 - radius && dz >= (bounds->Z1 - radius) &&
dz <= radius + bounds->Z2) dz <= (radius + bounds->Z2))
{ {
return true; return true;
} }
@ -673,11 +671,7 @@ bool ItemPushItem(ItemInfo* item, ItemInfo* item2, CollisionInfo* coll, bool spa
int rx = (dx * cosY) - (dz * sinY); int rx = (dx * cosY) - (dz * sinY);
int rz = (dz * cosY) + (dx * sinY); int rz = (dz * cosY) + (dx * sinY);
BOUNDING_BOX* bounds; auto* bounds = (bigPush & 2) ? &GlobalCollisionBounds : (BOUNDING_BOX*)GetBestFrame(item);
if (bigPush & 2)
bounds = &GlobalCollisionBounds;
else
bounds = (BOUNDING_BOX*)GetBestFrame(item);
int minX = bounds->X1; int minX = bounds->X1;
int maxX = bounds->X2; int maxX = bounds->X2;
@ -714,8 +708,8 @@ bool ItemPushItem(ItemInfo* item, ItemInfo* item2, CollisionInfo* coll, bool spa
else else
rz -= bottom; rz -= bottom;
item2->Pose.Position.x = item->Pose.Position.x + cosY * rx + sinY * rz; item2->Pose.Position.x = item->Pose.Position.x + (rx * cosY) + (rz * sinY);
item2->Pose.Position.z = item->Pose.Position.z + cosY * rz - sinY * rx; item2->Pose.Position.z = item->Pose.Position.z + (rz * cosY) - (rx * sinY);
auto* lara = item2->IsLara() ? GetLaraInfo(item2) : nullptr; auto* lara = item2->IsLara() ? GetLaraInfo(item2) : nullptr;
@ -740,12 +734,12 @@ bool ItemPushItem(ItemInfo* item, ItemInfo* item2, CollisionInfo* coll, bool spa
coll->Setup.LowerCeilingBound = 0; coll->Setup.LowerCeilingBound = 0;
coll->Setup.UpperCeilingBound = MAX_HEIGHT; coll->Setup.UpperCeilingBound = MAX_HEIGHT;
auto facing = coll->Setup.ForwardAngle; auto headingAngle = coll->Setup.ForwardAngle;
coll->Setup.ForwardAngle = phd_atan(item2->Pose.Position.z - coll->Setup.OldPosition.z, item2->Pose.Position.x - coll->Setup.OldPosition.x); coll->Setup.ForwardAngle = phd_atan(item2->Pose.Position.z - coll->Setup.OldPosition.z, item2->Pose.Position.x - coll->Setup.OldPosition.x);
GetCollisionInfo(coll, item2); GetCollisionInfo(coll, item2);
coll->Setup.ForwardAngle = facing; coll->Setup.ForwardAngle = headingAngle;
if (coll->CollisionType == CT_NONE) if (coll->CollisionType == CT_NONE)
{ {
@ -763,8 +757,8 @@ bool ItemPushItem(ItemInfo* item, ItemInfo* item2, CollisionInfo* coll, bool spa
// If Lara is in the process of aligning to an object, cancel it. // If Lara is in the process of aligning to an object, cancel it.
if (lara != nullptr && lara->Control.Count.PositionAdjust > (LARA_POSITION_ADJUST_MAX_TIME / 6)) if (lara != nullptr && lara->Control.Count.PositionAdjust > (LARA_POSITION_ADJUST_MAX_TIME / 6))
{ {
Lara.Control.IsMoving = false; lara->Control.IsMoving = false;
Lara.Control.HandStatus = HandStatus::Free; lara->Control.HandStatus = HandStatus::Free;
} }
return true; return true;
@ -853,7 +847,7 @@ void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll)
{ {
auto* mesh = &g_Level.Rooms[i].mesh[j]; auto* mesh = &g_Level.Rooms[i].mesh[j];
// Only process meshes which are visible and solid // Only process meshes which are visible and solid.
if ((mesh->flags & StaticMeshFlags::SM_VISIBLE) && (mesh->flags & StaticMeshFlags::SM_SOLID)) if ((mesh->flags & StaticMeshFlags::SM_VISIBLE) && (mesh->flags & StaticMeshFlags::SM_SOLID))
{ {
if (Vector3i::Distance(item->Pose.Position, mesh->pos.Position) < COLLISION_CHECK_DISTANCE) if (Vector3i::Distance(item->Pose.Position, mesh->pos.Position) < COLLISION_CHECK_DISTANCE)
@ -867,39 +861,39 @@ void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll)
} }
} }
bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PoseData pos, CollisionInfo* coll) bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PoseData pose, CollisionInfo* coll)
{ {
bool result = false; bool result = false;
// Get DX static bounds in global coords // Get DX static bounds in global coordinates.
auto staticBounds = TO_DX_BBOX(pos, box); auto staticBounds = TO_DX_BBOX(pose, box);
// Get local TR bounds and DX item bounds in global coords // Get local TR bounds and DX item bounds in global coordinates.
auto itemBBox = GetBoundsAccurate(item); auto itemBBox = GetBoundsAccurate(item);
auto itemBounds = TO_DX_BBOX(item->Pose, itemBBox); auto itemBounds = TO_DX_BBOX(item->Pose, itemBBox);
// Extend bounds a bit for visual testing // Extend bounds a bit for visual testing.
itemBounds.Extents = itemBounds.Extents + Vector3(WALL_SIZE); itemBounds.Extents = itemBounds.Extents + Vector3(SECTOR(1));
// Filter out any further checks if static isn't nearby // Filter out any further checks if static isn't nearby.
if (!staticBounds.Intersects(itemBounds)) if (!staticBounds.Intersects(itemBounds))
return false; return false;
// Bring back extents // Bring back extents.
itemBounds.Extents = itemBounds.Extents - Vector3(WALL_SIZE); itemBounds.Extents = itemBounds.Extents - Vector3(SECTOR(1));
// Draw static bounds // Draw static bounds.
g_Renderer.AddDebugBox(staticBounds, Vector4(1, 0.3f, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS); g_Renderer.AddDebugBox(staticBounds, Vector4(1, 0.3f, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
// Calculate horizontal item coll bounds according to radius // Calculate horizontal item collision bounds according to radius.
BOUNDING_BOX collBox; BOUNDING_BOX collBox;
collBox.X1 = -coll->Setup.Radius; collBox.X1 = -coll->Setup.Radius;
collBox.X2 = coll->Setup.Radius; collBox.X2 = coll->Setup.Radius;
collBox.Z1 = -coll->Setup.Radius; collBox.Z1 = -coll->Setup.Radius;
collBox.Z2 = coll->Setup.Radius; collBox.Z2 = coll->Setup.Radius;
// Calculate vertical item coll bounds according to either height (land mode) or precise bounds (water mode). // Calculate vertical item collision bounds according to either height (land mode) or precise bounds (water mode).
// Water mode needs special processing because height calc in original engines is inconsistent in such cases. // Water mode needs special processing because height calculation in original engines is inconsistent in such cases.
if (TestEnvironment(ENV_FLAG_WATER, item)) if (TestEnvironment(ENV_FLAG_WATER, item))
{ {
collBox.Y1 = itemBBox->Y1; collBox.Y1 = itemBBox->Y1;
@ -911,18 +905,18 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PoseData pos, Collisi
collBox.Y2 = 0; collBox.Y2 = 0;
} }
// Get and test DX item coll bounds // Get and test DX item collision bounds.
auto collBounds = TO_DX_BBOX(PoseData(item->Pose.Position), &collBox); auto collBounds = TO_DX_BBOX(PoseData(item->Pose.Position), &collBox);
bool intersects = staticBounds.Intersects(collBounds); bool intersects = staticBounds.Intersects(collBounds);
// Check if previous item horizontal position intersects bounds // Check if previous item horizontal position intersects bounds.
auto oldCollBounds = TO_DX_BBOX(PoseData(coll->Setup.OldPosition.x, item->Pose.Position.y, coll->Setup.OldPosition.z), &collBox); auto oldCollBounds = TO_DX_BBOX(PoseData(coll->Setup.OldPosition.x, item->Pose.Position.y, coll->Setup.OldPosition.z), &collBox);
bool oldHorIntersects = staticBounds.Intersects(oldCollBounds); bool oldHorIntersects = staticBounds.Intersects(oldCollBounds);
// Draw item coll bounds // Draw item coll bounds.
g_Renderer.AddDebugBox(collBounds, intersects ? Vector4(1, 0, 0, 1) : Vector4(0, 1, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS); g_Renderer.AddDebugBox(collBounds, intersects ? Vector4(1, 0, 0, 1) : Vector4(0, 1, 0, 1), RENDERER_DEBUG_PAGE::LOGIC_STATS);
// Decompose static bounds into top/bottom plane vertices // Decompose static bounds into top/bottom plane vertices.
Vector3 corners[8]; Vector3 corners[8];
staticBounds.GetCorners(corners); staticBounds.GetCorners(corners);
Vector3 planeVertices[4][3] = Vector3 planeVertices[4][3] =
@ -933,7 +927,7 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PoseData pos, Collisi
{ corners[3], corners[6], corners[2] } { corners[3], corners[6], corners[2] }
}; };
// Determine collision box vertical dimensions // Determine collision box vertical dimensions.
auto height = collBox.Height(); auto height = collBox.Height();
auto center = item->Pose.Position.y - height / 2; auto center = item->Pose.Position.y - height / 2;
@ -945,24 +939,23 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PoseData pos, Collisi
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
// Calculate ray direction // Calculate ray direction.
auto mxR = Matrix::CreateFromYawPitchRoll(TO_RAD(item->Pose.Orientation.y), TO_RAD(item->Pose.Orientation.x + (ANGLE(90 * i))), 0.0f); auto mxR = Matrix::CreateFromYawPitchRoll(TO_RAD(item->Pose.Orientation.y), TO_RAD(item->Pose.Orientation.x + (ANGLE(90 * i))), 0.0f);
auto mxT = Matrix::CreateTranslation(Vector3::UnitY); auto mxT = Matrix::CreateTranslation(Vector3::UnitY);
auto direction = (mxT * mxR).Translation(); auto direction = (mxT * mxR).Translation();
// Make a ray and do ray tests against all decomposed planes // Make a ray and do ray tests against all decomposed planes.
auto ray = Ray(collBounds.Center, direction); auto ray = Ray(collBounds.Center, direction);
// Determine if top/bottom planes are closest ones or not // Determine if top/bottom planes are closest ones or not.
for (int p = 0; p < 4; p++) for (int p = 0; p < 4; p++)
{ {
// No plane intersection, quickly discard // No plane intersection, quickly discard.
float d = 0.0f; float d = 0.0f;
if (!ray.Intersects(planeVertices[p][0], planeVertices[p][1], planeVertices[p][2], d)) if (!ray.Intersects(planeVertices[p][0], planeVertices[p][1], planeVertices[p][2], d))
continue; continue;
// Process plane intersection only if distance is smaller // Process plane intersection only if distance is smaller than already found minimum.
// than already found minimum
if (d < minDistance) if (d < minDistance)
{ {
closestRay = ray; closestRay = ray;
@ -972,24 +965,25 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PoseData pos, Collisi
} }
} }
if (closestPlane != -1) // Top/bottom plane found // Top/bottom plane found.
if (closestPlane != -1)
{ {
auto bottom = closestPlane >= 2; auto bottom = closestPlane >= 2;
auto yPoint = abs((closestRay.direction * minDistance).y); auto yPoint = abs((closestRay.direction * minDistance).y);
auto distanceToVerticalPlane = height / 2 - yPoint; auto distanceToVerticalPlane = height / 2 - yPoint;
// Correct position according to top/bottom bounds, if collided and plane is nearby // Correct position according to top/bottom bounds, if collided and plane is nearby.
if (intersects && oldHorIntersects && minDistance < height) if (intersects && oldHorIntersects && minDistance < height)
{ {
if (bottom) if (bottom)
{ {
// HACK: additionally subtract 2 from bottom plane, or else false positives may occur. // HACK: Additionally subtract 2 from bottom plane, otherwise false positives may occur.
item->Pose.Position.y += distanceToVerticalPlane + 2; item->Pose.Position.y += distanceToVerticalPlane + 2;
coll->CollisionType = CT_TOP; coll->CollisionType = CT_TOP;
} }
else else
{ {
// Set collision type only if dry room (in water rooms it causes stucking) // Set collision type only if dry room (in water rooms the player can get stuck).
item->Pose.Position.y -= distanceToVerticalPlane; item->Pose.Position.y -= distanceToVerticalPlane;
coll->CollisionType = (g_Level.Rooms[item->RoomNumber].flags & 1) ? coll->CollisionType : CT_CLAMP; coll->CollisionType = (g_Level.Rooms[item->RoomNumber].flags & 1) ? coll->CollisionType : CT_CLAMP;
} }
@ -1005,29 +999,29 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PoseData pos, Collisi
if (!intersects) if (!intersects)
return false; return false;
// Check if bounds still collide after top/bottom position correction // Check if bounds still collide after top/bottom position correction.
if (!staticBounds.Intersects(TO_DX_BBOX(PoseData(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z), &collBox))) if (!staticBounds.Intersects(TO_DX_BBOX(PoseData(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z), &collBox)))
return result; return result;
// Determine identity rotation/distance // Determine identity orientation/distance.
auto distance = Vector3(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z) - Vector3(pos.Position.x, pos.Position.y, pos.Position.z); auto distance = (item->Pose.Position - pose.Position).ToVector3();
auto sinY = phd_sin(pos.Orientation.y); auto sinY = phd_sin(pose.Orientation.y);
auto cosY = phd_cos(pos.Orientation.y); auto cosY = phd_cos(pose.Orientation.y);
// Rotate item to collision bounds identity // Rotate item to collision bounds identity.
auto x = round(distance.x * cosY - distance.z * sinY) + pos.Position.x; auto x = round((distance.x * cosY) - (distance.z * sinY)) + pose.Position.x;
auto y = item->Pose.Position.y; auto y = item->Pose.Position.y;
auto z = round(distance.x * sinY + distance.z * cosY) + pos.Position.z; auto z = round((distance.x * sinY) + (distance.z * cosY)) + pose.Position.z;
// Determine identity static collision bounds // Determine identity static collision bounds.
auto XMin = pos.Position.x + box->X1; auto XMin = pose.Position.x + box->X1;
auto XMax = pos.Position.x + box->X2; auto XMax = pose.Position.x + box->X2;
auto YMin = pos.Position.y + box->Y1; auto YMin = pose.Position.y + box->Y1;
auto YMax = pos.Position.y + box->Y2; auto YMax = pose.Position.y + box->Y2;
auto ZMin = pos.Position.z + box->Z1; auto ZMin = pose.Position.z + box->Z1;
auto ZMax = pos.Position.z + box->Z2; auto ZMax = pose.Position.z + box->Z2;
// Determine item collision bounds // Determine item collision bounds.
auto inXMin = x + collBox.X1; auto inXMin = x + collBox.X1;
auto inXMax = x + collBox.X2; auto inXMax = x + collBox.X2;
auto inYMin = y + collBox.Y1; auto inYMin = y + collBox.Y1;
@ -1035,16 +1029,15 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PoseData pos, Collisi
auto inZMin = z + collBox.Z1; auto inZMin = z + collBox.Z1;
auto inZMax = z + collBox.Z2; auto inZMax = z + collBox.Z2;
// Don't calculate shifts if not in bounds // Don't calculate shifts if not in bounds.
if (inXMax <= XMin || inXMin >= XMax || if (inXMax <= XMin || inXMin >= XMax ||
inYMax <= YMin || inYMin >= YMax || inYMax <= YMin || inYMin >= YMax ||
inZMax <= ZMin || inZMin >= ZMax) inZMax <= ZMin || inZMin >= ZMax)
return result; return result;
// Calculate shifts // Calculate shifts.
Vector3i rawShift = {};
auto rawShift = Vector3i::Zero;
auto shiftLeft = inXMax - XMin; auto shiftLeft = inXMax - XMin;
auto shiftRight = XMax - inXMin; auto shiftRight = XMax - inXMin;
@ -1061,13 +1054,13 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PoseData pos, Collisi
else else
rawShift.z = shiftRight; rawShift.z = shiftRight;
// Rotate previous collision position to identity // Rotate previous collision position to identity.
distance = Vector3(coll->Setup.OldPosition.x, coll->Setup.OldPosition.y, coll->Setup.OldPosition.z) - Vector3(pos.Position.x, pos.Position.y, pos.Position.z); distance = (coll->Setup.OldPosition - pose.Position).ToVector3();
auto ox = round(distance.x * cosY - distance.z * sinY) + pos.Position.x; auto ox = round((distance.x * cosY) - (distance.z * sinY)) + pose.Position.x;
auto oz = round(distance.x * sinY + distance.z * cosY) + pos.Position.z; auto oz = round((distance.x * sinY) + (distance.z * cosY)) + pose.Position.z;
// Calculate collisison type based on identity rotation // Calculate collisison type based on identity orientation.
switch (GetQuadrant(coll->Setup.ForwardAngle - pos.Orientation.y)) switch (GetQuadrant(coll->Setup.ForwardAngle - pose.Orientation.y))
{ {
case NORTH: case NORTH:
if (rawShift.x > coll->Setup.Radius || rawShift.x < -coll->Setup.Radius) if (rawShift.x > coll->Setup.Radius || rawShift.x < -coll->Setup.Radius)
@ -1158,19 +1151,19 @@ bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PoseData pos, Collisi
break; break;
} }
// Determine final shifts rotation/distance // Determine final shifts orientation/distance.
distance = Vector3(x + coll->Shift.x, y, z + coll->Shift.z) - Vector3(pos.Position.x, pos.Position.y, pos.Position.z); distance = Vector3(x + coll->Shift.x, y, z + coll->Shift.z) - pose.Position.ToVector3();
sinY = phd_sin(-pos.Orientation.y); sinY = phd_sin(-pose.Orientation.y);
cosY = phd_cos(-pos.Orientation.y); cosY = phd_cos(-pose.Orientation.y);
// Calculate final shifts rotation/distance // Calculate final shifts orientation/distance.
coll->Shift.x = (round(distance.x * cosY - distance.z * sinY) + pos.Position.x) - item->Pose.Position.x; coll->Shift.x = (round((distance.x * cosY) - (distance.z * sinY)) + pose.Position.x) - item->Pose.Position.x;
coll->Shift.z = (round(distance.x * sinY + distance.z * cosY) + pos.Position.z) - item->Pose.Position.z; coll->Shift.z = (round((distance.x * sinY) + (distance.z * cosY)) + pose.Position.z) - item->Pose.Position.z;
if (coll->Shift.x == 0 && coll->Shift.z == 0) if (coll->Shift.x == 0 && coll->Shift.z == 0)
coll->CollisionType = CT_NONE; // Paranoid coll->CollisionType = CT_NONE; // Paranoid.
// Set splat state flag if item is Lara and bounds are taller than Lara's headroom // Set splat state flag if item is Lara and bounds are taller than Lara's headroom.
if (item == LaraItem && coll->CollisionType == CT_FRONT) if (item == LaraItem && coll->CollisionType == CT_FRONT)
coll->HitTallObject = (YMin <= inYMin + LARA_HEADROOM); coll->HitTallObject = (YMin <= inYMin + LARA_HEADROOM);
@ -1182,39 +1175,39 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto prevCollResult = GetCollision(x, y, z, item->RoomNumber); auto prevPointProbe = GetCollision(x, y, z, item->RoomNumber);
auto collResult = GetCollision(item); auto pointProbe = GetCollision(item);
auto* bounds = GetBoundsAccurate(item); auto* bounds = GetBoundsAccurate(item);
int radius = bounds->Height(); int radius = bounds->Height();
item->Pose.Position.y += radius; item->Pose.Position.y += radius;
if (item->Pose.Position.y >= collResult.Position.Floor) if (item->Pose.Position.y >= pointProbe.Position.Floor)
{ {
int bs = 0; int bs = 0;
if (collResult.Position.FloorSlope && prevCollResult.Position.Floor < collResult.Position.Floor) if (pointProbe.Position.FloorSlope && prevPointProbe.Position.Floor < pointProbe.Position.Floor)
{ {
int yAngle = (long)((unsigned short)item->Pose.Orientation.y); int yAngle = (long)((unsigned short)item->Pose.Orientation.y);
if (collResult.FloorTilt.x < 0) if (pointProbe.FloorTilt.x < 0)
{ {
if (yAngle >= ANGLE(180.0f)) if (yAngle >= ANGLE(180.0f))
bs = 1; bs = 1;
} }
else if (collResult.FloorTilt.x > 0) else if (pointProbe.FloorTilt.x > 0)
{ {
if (yAngle <= ANGLE(180.0f)) if (yAngle <= ANGLE(180.0f))
bs = 1; bs = 1;
} }
if (collResult.FloorTilt.y < 0) if (pointProbe.FloorTilt.y < 0)
{ {
if (yAngle >= ANGLE(90.0f) && yAngle <= ANGLE(270.0f)) if (yAngle >= ANGLE(90.0f) && yAngle <= ANGLE(270.0f))
bs = 1; bs = 1;
} }
else if (collResult.FloorTilt.y > 0) else if (pointProbe.FloorTilt.y > 0)
{ {
if (yAngle <= ANGLE(90.0f) || yAngle >= ANGLE(270.0f)) if (yAngle <= ANGLE(90.0f) || yAngle >= ANGLE(270.0f))
bs = 1; bs = 1;
@ -1223,7 +1216,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
// If last position of item was also below this floor height, we've hit a wall, else we've hit a floor. // If last position of item was also below this floor height, we've hit a wall, else we've hit a floor.
if (y > (collResult.Position.Floor + 32) && bs == 0 && if (y > (pointProbe.Position.Floor + 32) && bs == 0 &&
(((x / SECTOR(1)) != (item->Pose.Position.x / SECTOR(1))) || (((x / SECTOR(1)) != (item->Pose.Position.x / SECTOR(1))) ||
((z / SECTOR(1)) != (item->Pose.Position.z / SECTOR(1))))) ((z / SECTOR(1)) != (item->Pose.Position.z / SECTOR(1)))))
{ {
@ -1231,8 +1224,8 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
long xs; long xs;
if ((x & (~(WALL_SIZE - 1))) != (item->Pose.Position.x & (~(WALL_SIZE - 1))) && // X crossed boundary? if ((x & (~WALL_MASK)) != (item->Pose.Position.x & (~WALL_MASK)) && // X crossed boundary?
(z & (~(WALL_SIZE - 1))) != (item->Pose.Position.z & (~(WALL_SIZE - 1)))) // Z crossed boundary as well? (z & (~WALL_MASK)) != (item->Pose.Position.z & (~WALL_MASK))) // Z crossed boundary as well?
{ {
if (abs(x - item->Pose.Position.x) < abs(z - item->Pose.Position.z)) if (abs(x - item->Pose.Position.x) < abs(z - item->Pose.Position.z))
xs = 1; // X has travelled the shortest, so (maybe) hit first. (Seems to work ok). xs = 1; // X has travelled the shortest, so (maybe) hit first. (Seems to work ok).
@ -1242,7 +1235,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
else else
xs = 1; xs = 1;
if ((x & (~(WALL_SIZE - 1))) != (item->Pose.Position.x & (~(WALL_SIZE - 1))) && xs) // X crossed boundary? if ((x & (~WALL_MASK)) != (item->Pose.Position.x & (~WALL_MASK)) && xs) // X crossed boundary?
{ {
// Hit angle = ANGLE(270.0f). // Hit angle = ANGLE(270.0f).
if (xv <= 0) if (xv <= 0)
@ -1263,14 +1256,14 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
item->Pose.Position.z = z; item->Pose.Position.z = z;
} }
// Hit a steep slope? // Hit a steep slope?
else if (collResult.Position.FloorSlope) else if (pointProbe.Position.FloorSlope)
{ {
// Need to know which direction the slope is. // Need to know which direction the slope is.
item->Animation.Velocity.z -= (item->Animation.Velocity.z / 4); item->Animation.Velocity.z -= (item->Animation.Velocity.z / 4);
// Hit angle = ANGLE(90.0f) // Hit angle = ANGLE(90.0f)
if (collResult.FloorTilt.x < 0 && ((abs(collResult.FloorTilt.x)) - (abs(collResult.FloorTilt.y)) >= 2)) if (pointProbe.FloorTilt.x < 0 && ((abs(pointProbe.FloorTilt.x)) - (abs(pointProbe.FloorTilt.y)) >= 2))
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(180.0f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(180.0f))
{ {
@ -1282,7 +1275,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z -= collResult.FloorTilt.x * 2; item->Animation.Velocity.z -= pointProbe.FloorTilt.x * 2;
if ((unsigned short)item->Pose.Orientation.y > ANGLE(90.0f) && (unsigned short)item->Pose.Orientation.y < ANGLE(270.0f)) if ((unsigned short)item->Pose.Orientation.y > ANGLE(90.0f) && (unsigned short)item->Pose.Orientation.y < ANGLE(270.0f))
{ {
item->Pose.Orientation.y -= ANGLE(22.5f); item->Pose.Orientation.y -= ANGLE(22.5f);
@ -1304,7 +1297,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Hit angle = ANGLE(270.0f) // Hit angle = ANGLE(270.0f)
else if (collResult.FloorTilt.x > 0 && ((abs(collResult.FloorTilt.x)) - (abs(collResult.FloorTilt.y)) >= 2)) else if (pointProbe.FloorTilt.x > 0 && ((abs(pointProbe.FloorTilt.x)) - (abs(pointProbe.FloorTilt.y)) >= 2))
{ {
if (((unsigned short)item->Pose.Orientation.y) < ANGLE(180.0f)) if (((unsigned short)item->Pose.Orientation.y) < ANGLE(180.0f))
{ {
@ -1316,7 +1309,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z += collResult.FloorTilt.x * 2; item->Animation.Velocity.z += pointProbe.FloorTilt.x * 2;
if ((unsigned short)item->Pose.Orientation.y > ANGLE(270.0f) || (unsigned short)item->Pose.Orientation.y < ANGLE(90.0f)) if ((unsigned short)item->Pose.Orientation.y > ANGLE(270.0f) || (unsigned short)item->Pose.Orientation.y < ANGLE(90.0f))
{ {
item->Pose.Orientation.y -= ANGLE(22.5f); item->Pose.Orientation.y -= ANGLE(22.5f);
@ -1338,7 +1331,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Hit angle = 0 // Hit angle = 0
else if (collResult.FloorTilt.y < 0 && ((abs(collResult.FloorTilt.y)) - (abs(collResult.FloorTilt.x)) >= 2)) else if (pointProbe.FloorTilt.y < 0 && ((abs(pointProbe.FloorTilt.y)) - (abs(pointProbe.FloorTilt.x)) >= 2))
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(90.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(270.0f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(90.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(270.0f))
{ {
@ -1350,7 +1343,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z -= collResult.FloorTilt.y * 2; item->Animation.Velocity.z -= pointProbe.FloorTilt.y * 2;
if ((unsigned short)item->Pose.Orientation.y < ANGLE(180.0f)) if ((unsigned short)item->Pose.Orientation.y < ANGLE(180.0f))
{ {
@ -1373,7 +1366,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Hit angle = ANGLE(180.0f) // Hit angle = ANGLE(180.0f)
else if (collResult.FloorTilt.y > 0 && ((abs(collResult.FloorTilt.y)) - (abs(collResult.FloorTilt.x)) >= 2)) else if (pointProbe.FloorTilt.y > 0 && ((abs(pointProbe.FloorTilt.y)) - (abs(pointProbe.FloorTilt.x)) >= 2))
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(270.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(90.0f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(270.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(90.0f))
{ {
@ -1385,7 +1378,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z += collResult.FloorTilt.y * 2; item->Animation.Velocity.z += pointProbe.FloorTilt.y * 2;
if ((unsigned short)item->Pose.Orientation.y > ANGLE(180.0f)) if ((unsigned short)item->Pose.Orientation.y > ANGLE(180.0f))
{ {
@ -1407,7 +1400,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
item->Animation.Velocity.y = 0; item->Animation.Velocity.y = 0;
} }
} }
else if (collResult.FloorTilt.x < 0 && collResult.FloorTilt.y < 0) // Hit angle = 0x2000 else if (pointProbe.FloorTilt.x < 0 && pointProbe.FloorTilt.y < 0) // Hit angle = 0x2000
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(135.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(315.0f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(135.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(315.0f))
{ {
@ -1419,7 +1412,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z += -collResult.FloorTilt.x + -collResult.FloorTilt.y; item->Animation.Velocity.z += -pointProbe.FloorTilt.x + -pointProbe.FloorTilt.y;
if ((unsigned short)item->Pose.Orientation.y > ANGLE(45.0f) && (unsigned short)item->Pose.Orientation.y < ANGLE(225.0f)) if ((unsigned short)item->Pose.Orientation.y > ANGLE(45.0f) && (unsigned short)item->Pose.Orientation.y < ANGLE(225.0f))
{ {
item->Pose.Orientation.y -= ANGLE(22.5f); item->Pose.Orientation.y -= ANGLE(22.5f);
@ -1441,7 +1434,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Hit angle = ANGLE(135.0f) // Hit angle = ANGLE(135.0f)
else if (collResult.FloorTilt.x < 0 && collResult.FloorTilt.y > 0) else if (pointProbe.FloorTilt.x < 0 && pointProbe.FloorTilt.y > 0)
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(225.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(45.0f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(225.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(45.0f))
{ {
@ -1453,7 +1446,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z += (-collResult.FloorTilt.x) + collResult.FloorTilt.y; item->Animation.Velocity.z += (-pointProbe.FloorTilt.x) + pointProbe.FloorTilt.y;
if ((unsigned short)item->Pose.Orientation.y < ANGLE(315.0f) && (unsigned short)item->Pose.Orientation.y > ANGLE(135.0f)) if ((unsigned short)item->Pose.Orientation.y < ANGLE(315.0f) && (unsigned short)item->Pose.Orientation.y > ANGLE(135.0f))
{ {
item->Pose.Orientation.y -= ANGLE(22.5f); item->Pose.Orientation.y -= ANGLE(22.5f);
@ -1475,7 +1468,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Hit angle = ANGLE(225.5f) // Hit angle = ANGLE(225.5f)
else if (collResult.FloorTilt.x > 0 && collResult.FloorTilt.y > 0) else if (pointProbe.FloorTilt.x > 0 && pointProbe.FloorTilt.y > 0)
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(315.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(135.0f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(315.0f) || ((unsigned short)item->Pose.Orientation.y) < ANGLE(135.0f))
{ {
@ -1487,7 +1480,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z += collResult.FloorTilt.x + collResult.FloorTilt.y; item->Animation.Velocity.z += pointProbe.FloorTilt.x + pointProbe.FloorTilt.y;
if ((unsigned short)item->Pose.Orientation.y < ANGLE(45.0f) || (unsigned short)item->Pose.Orientation.y > ANGLE(225.5f)) if ((unsigned short)item->Pose.Orientation.y < ANGLE(45.0f) || (unsigned short)item->Pose.Orientation.y > ANGLE(225.5f))
{ {
item->Pose.Orientation.y -= ANGLE(22.5f); item->Pose.Orientation.y -= ANGLE(22.5f);
@ -1509,7 +1502,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Hit angle = ANGLE(315.0f) // Hit angle = ANGLE(315.0f)
else if (collResult.FloorTilt.x > 0 && collResult.FloorTilt.y < 0) else if (pointProbe.FloorTilt.x > 0 && pointProbe.FloorTilt.y < 0)
{ {
if (((unsigned short)item->Pose.Orientation.y) > ANGLE(45.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(225.5f)) if (((unsigned short)item->Pose.Orientation.y) > ANGLE(45.0f) && ((unsigned short)item->Pose.Orientation.y) < ANGLE(225.5f))
{ {
@ -1521,7 +1514,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (item->Animation.Velocity.z < 32) if (item->Animation.Velocity.z < 32)
{ {
item->Animation.Velocity.z += collResult.FloorTilt.x + (-collResult.FloorTilt.y); item->Animation.Velocity.z += pointProbe.FloorTilt.x + (-pointProbe.FloorTilt.y);
if ((unsigned short)item->Pose.Orientation.y < ANGLE(135.0f) || (unsigned short)item->Pose.Orientation.y > ANGLE(315.0f)) if ((unsigned short)item->Pose.Orientation.y < ANGLE(135.0f) || (unsigned short)item->Pose.Orientation.y > ANGLE(315.0f))
{ {
item->Pose.Orientation.y -= ANGLE(22.5f); item->Pose.Orientation.y -= ANGLE(22.5f);
@ -1543,7 +1536,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
// Put item back in its last position. // Move item back to its previous position.
item->Pose.Position.x = x; item->Pose.Position.x = x;
item->Pose.Position.y = y; item->Pose.Position.y = y;
item->Pose.Position.z = z; item->Pose.Position.z = z;
@ -1582,7 +1575,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
item->Pose.Position.y = collResult.Position.Floor; item->Pose.Position.y = pointProbe.Position.Floor;
} }
} }
// Check for on top of object. // Check for on top of object.
@ -1590,8 +1583,8 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
{ {
if (yv >= 0) if (yv >= 0)
{ {
prevCollResult = GetCollision(item->Pose.Position.x, y, item->Pose.Position.z, item->RoomNumber); prevPointProbe = GetCollision(item->Pose.Position.x, y, item->Pose.Position.z, item->RoomNumber);
collResult = GetCollision(item); pointProbe = GetCollision(item);
// Bounce off floor. // Bounce off floor.
@ -1599,7 +1592,7 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
// was always set to 0 by GetHeight() function which was called before the check. // was always set to 0 by GetHeight() function which was called before the check.
// Possibly a mistake or unfinished feature by Core? -- Lwmte, 27.08.21 // Possibly a mistake or unfinished feature by Core? -- Lwmte, 27.08.21
if (item->Pose.Position.y >= prevCollResult.Position.Floor) if (item->Pose.Position.y >= prevPointProbe.Position.Floor)
{ {
// Hit the floor; bounce and slow down. // Hit the floor; bounce and slow down.
if (item->Animation.Velocity.y > 0) if (item->Animation.Velocity.y > 0)
@ -1633,17 +1626,17 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
item->Pose.Position.y = prevCollResult.Position.Floor; item->Pose.Position.y = prevPointProbe.Position.Floor;
} }
} }
// else // else
{ {
// Bounce off ceiling. // Bounce off ceiling.
collResult = GetCollision(item); pointProbe = GetCollision(item);
if (item->Pose.Position.y < collResult.Position.Ceiling) if (item->Pose.Position.y < pointProbe.Position.Ceiling)
{ {
if (y < collResult.Position.Ceiling && if (y < pointProbe.Position.Ceiling &&
(((x / SECTOR(1)) != (item->Pose.Position.x / SECTOR(1))) || (((x / SECTOR(1)) != (item->Pose.Position.x / SECTOR(1))) ||
((z / SECTOR(1)) != (item->Pose.Position.z / SECTOR(1))))) ((z / SECTOR(1)) != (item->Pose.Position.z / SECTOR(1)))))
{ {
@ -1666,13 +1659,13 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
else else
item->Animation.Velocity.z /= 2; item->Animation.Velocity.z /= 2;
// Put item back in its last position. // Move item back to its previous position.
item->Pose.Position.x = x; item->Pose.Position.x = x;
item->Pose.Position.y = y; item->Pose.Position.y = y;
item->Pose.Position.z = z; item->Pose.Position.z = z;
} }
else else
item->Pose.Position.y = collResult.Position.Ceiling; item->Pose.Position.y = pointProbe.Position.Ceiling;
if (item->Animation.Velocity.y < 0) if (item->Animation.Velocity.y < 0)
item->Animation.Velocity.y = -item->Animation.Velocity.y; item->Animation.Velocity.y = -item->Animation.Velocity.y;
@ -1680,14 +1673,14 @@ void DoProjectileDynamics(short itemNumber, int x, int y, int z, int xv, int yv,
} }
} }
collResult = GetCollision(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber); pointProbe = GetCollision(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber);
if (collResult.RoomNumber != item->RoomNumber) if (pointProbe.RoomNumber != item->RoomNumber)
{ {
if (item->ObjectNumber == ID_GRENADE && TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, collResult.RoomNumber)) if (item->ObjectNumber == ID_GRENADE && TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, pointProbe.RoomNumber))
Splash(item); Splash(item);
ItemNewRoom(itemNumber, collResult.RoomNumber); ItemNewRoom(itemNumber, pointProbe.RoomNumber);
} }
item->Pose.Position.y -= radius; item->Pose.Position.y -= radius;
@ -1776,8 +1769,8 @@ void DoObjectCollision(ItemInfo* laraItem, CollisionInfo* coll)
else else
{ {
DoDamage(item, INT_MAX); DoDamage(item, INT_MAX);
DoLotsOfBlood(
DoLotsOfBlood(item->Pose.Position.x, item->Pose.Position.x,
laraItem->Pose.Position.y - CLICK(1), laraItem->Pose.Position.y - CLICK(1),
item->Pose.Position.z, item->Pose.Position.z,
laraItem->Animation.Velocity.z, laraItem->Animation.Velocity.z,
@ -1882,7 +1875,7 @@ void CreatureCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll
int rx = (frame->boundingBox.X1 + frame->boundingBox.X2) / 2; int rx = (frame->boundingBox.X1 + frame->boundingBox.X2) / 2;
int rz = (frame->boundingBox.X2 + frame->boundingBox.Z2) / 2; int rz = (frame->boundingBox.X2 + frame->boundingBox.Z2) / 2;
if (frame->boundingBox.Height() > STEP_SIZE) if (frame->boundingBox.Height() > CLICK(1))
{ {
auto* lara = GetLaraInfo(laraItem); auto* lara = GetLaraInfo(laraItem);
@ -1890,7 +1883,7 @@ void CreatureCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll
lara->HitDirection = (short)angle; lara->HitDirection = (short)angle;
// TODO: check if a second Lara.hitFrame++; is required there ! // TODO: Check if a second Lara.hitFrame++ is required. -- TokyoSU
lara->HitFrame++; lara->HitFrame++;
if (lara->HitFrame > 30) if (lara->HitFrame > 30)
lara->HitFrame = 30; lara->HitFrame = 30;

View file

@ -1,16 +1,15 @@
#pragma once #pragma once
#include "Math/Math.h" #include "Math/Math.h"
#include "Math/Math.h"
struct ItemInfo;
struct CollisionInfo;
class FloorInfo; class FloorInfo;
struct CollisionInfo;
struct ItemInfo;
struct MESH_INFO; struct MESH_INFO;
constexpr auto MAX_COLLIDED_OBJECTS = 1024; constexpr auto MAX_COLLIDED_OBJECTS = 1024;
constexpr auto ITEM_RADIUS_YMAX = SECTOR(3); constexpr auto ITEM_RADIUS_YMAX = SECTOR(3);
constexpr auto VEHICLE_COLLISION_TERMINAL_VELOCITY = 30; constexpr auto VEHICLE_COLLISION_TERMINAL_VELOCITY = 30.0f;
extern BOUNDING_BOX GlobalCollisionBounds; extern BOUNDING_BOX GlobalCollisionBounds;
extern ItemInfo* CollidedItems[MAX_COLLIDED_OBJECTS]; extern ItemInfo* CollidedItems[MAX_COLLIDED_OBJECTS];
@ -36,17 +35,17 @@ bool TestLaraPosition(OBJECT_COLLISION_BOUNDS* bounds, ItemInfo* item, ItemInfo*
bool AlignLaraPosition(Vector3i* offset, ItemInfo* item, ItemInfo* laraItem); bool AlignLaraPosition(Vector3i* offset, ItemInfo* item, ItemInfo* laraItem);
bool MoveLaraPosition(Vector3i* pos, ItemInfo* item, ItemInfo* laraItem); bool MoveLaraPosition(Vector3i* pos, ItemInfo* item, ItemInfo* laraItem);
bool ItemNearLara(PoseData* pos, int radius); bool ItemNearLara(Vector3i* origin, int radius);
bool ItemNearTarget(PoseData* origin, ItemInfo* target, int radius); bool ItemNearTarget(Vector3i* origin, ItemInfo* targetEntity, int radius);
bool Move3DPosTo3DPos(PoseData* origin, PoseData* target, int velocity, short angleAdd); bool Move3DPosTo3DPos(PoseData* fromPose, PoseData* toPose, int velocity, short angleAdd);
bool TestBoundsCollide(ItemInfo* item, ItemInfo* laraItem, int radius); bool TestBoundsCollide(ItemInfo* item, ItemInfo* laraItem, int radius);
bool TestBoundsCollideStatic(ItemInfo* item, MESH_INFO* mesh, int radius); bool TestBoundsCollideStatic(ItemInfo* item, MESH_INFO* mesh, int radius);
bool ItemPushItem(ItemInfo* item, ItemInfo* laraItem, CollisionInfo* coll, bool spasmEnabled, char bigPush); bool ItemPushItem(ItemInfo* item, ItemInfo* laraItem, CollisionInfo* coll, bool spasmEnabled, char bigPush);
bool ItemPushStatic(ItemInfo* laraItem, MESH_INFO* mesh, CollisionInfo* coll); bool ItemPushStatic(ItemInfo* laraItem, MESH_INFO* mesh, CollisionInfo* coll);
bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PoseData pos, CollisionInfo* coll); bool CollideSolidBounds(ItemInfo* item, BOUNDING_BOX* box, PoseData pose, CollisionInfo* coll);
void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll); void CollideSolidStatics(ItemInfo* item, CollisionInfo* coll);
void AIPickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); void AIPickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);

View file

@ -77,7 +77,7 @@ int TestCollision(ItemInfo* item, ItemInfo* laraItem)
int dz = z1 - z2; int dz = z1 - z2;
int r = r1 + r2; int r = r1 + r2;
if ((pow(dx, 2) + pow(dy, 2) + pow(dz, 2)) < pow(r, 2)) if ((SQUARE(dx) + SQUARE(dy) + SQUARE(dz)) < SQUARE(r))
{ {
item->SetBits(JointBitType::Touch, i); item->SetBits(JointBitType::Touch, i);
laraItem->SetBits(JointBitType::Touch, j); laraItem->SetBits(JointBitType::Touch, j);

View file

@ -92,7 +92,7 @@ void DrawNearbyPathfinding(int boxIndex)
void DropEntityPickups(ItemInfo* item) void DropEntityPickups(ItemInfo* item)
{ {
ItemInfo* pickup = NULL; ItemInfo* pickup = nullptr;
for (short pickupNumber = item->CarriedItem; pickupNumber != NO_ITEM; pickupNumber = pickup->CarriedItem) for (short pickupNumber = item->CarriedItem; pickupNumber != NO_ITEM; pickupNumber = pickup->CarriedItem)
{ {
@ -157,7 +157,7 @@ void CreatureYRot2(PoseData* fromPose, short angle, short angleAdd)
bool SameZone(CreatureInfo* creature, ItemInfo* target) bool SameZone(CreatureInfo* creature, ItemInfo* target)
{ {
int* zone = g_Level.Zones[creature->LOT.Zone][FlipStatus].data(); int* zone = g_Level.Zones[(int)creature->LOT.Zone][FlipStatus].data();
auto* item = &g_Level.Items[creature->ItemNumber]; auto* item = &g_Level.Items[creature->ItemNumber];
auto* room = &g_Level.Rooms[item->RoomNumber]; auto* room = &g_Level.Rooms[item->RoomNumber];
@ -447,12 +447,13 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
short top; short top;
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
if (!item->Data)
if (!item->IsCreature())
return false; return false;
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
auto* LOT = &creature->LOT; auto* LOT = &creature->LOT;
int* zone = g_Level.Zones[LOT->Zone][FlipStatus].data(); int* zone = g_Level.Zones[(int)LOT->Zone][FlipStatus].data();
int boxHeight; int boxHeight;
if (item->BoxNumber != NO_BOX) if (item->BoxNumber != NO_BOX)
@ -460,7 +461,7 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
else else
boxHeight = item->Floor; boxHeight = item->Floor;
auto old = item->Pose.Position; auto prevPos = item->Pose.Position;
AnimateItem(item); AnimateItem(item);
@ -475,7 +476,7 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
int y = item->Pose.Position.y + bounds->Y1; int y = item->Pose.Position.y + bounds->Y1;
short roomNumber = item->RoomNumber; short roomNumber = item->RoomNumber;
GetFloor(old.x, y, old.z, &roomNumber); GetFloor(prevPos.x, y, prevPos.z, &roomNumber);
FloorInfo* floor = GetFloor(item->Pose.Position.x, y, item->Pose.Position.z, &roomNumber); FloorInfo* floor = GetFloor(item->Pose.Position.x, y, item->Pose.Position.z, &roomNumber);
// TODO: Check why some blocks have box = -1 assigned to them -- Lwmte, 10.11.21 // TODO: Check why some blocks have box = -1 assigned to them -- Lwmte, 10.11.21
@ -504,18 +505,18 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
{ {
xPos = item->Pose.Position.x / SECTOR(1); xPos = item->Pose.Position.x / SECTOR(1);
zPos = item->Pose.Position.z / SECTOR(1); zPos = item->Pose.Position.z / SECTOR(1);
shiftX = old.x / SECTOR(1); shiftX = prevPos.x / SECTOR(1);
shiftZ = old.z / SECTOR(1); shiftZ = prevPos.z / SECTOR(1);
if (xPos < shiftX) if (xPos < shiftX)
item->Pose.Position.x = old.x & (~(SECTOR(1) - 1)); item->Pose.Position.x = prevPos.x & (~WALL_MASK);
else if (xPos > shiftX) else if (xPos > shiftX)
item->Pose.Position.x = old.x | (SECTOR(1) - 1); item->Pose.Position.x = prevPos.x | WALL_MASK;
if (zPos < shiftZ) if (zPos < shiftZ)
item->Pose.Position.z = old.z & (~(SECTOR(1) - 1)); item->Pose.Position.z = prevPos.z & (~WALL_MASK);
else if (zPos > shiftZ) else if (zPos > shiftZ)
item->Pose.Position.z = old.z | (SECTOR(1) - 1); item->Pose.Position.z = prevPos.z | (WALL_MASK);
floor = GetFloor(item->Pose.Position.x, y, item->Pose.Position.z, &roomNumber); floor = GetFloor(item->Pose.Position.x, y, item->Pose.Position.z, &roomNumber);
height = g_Level.Boxes[floor->Box].height; height = g_Level.Boxes[floor->Box].height;
@ -536,8 +537,8 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
int x = item->Pose.Position.x; int x = item->Pose.Position.x;
int z = item->Pose.Position.z; int z = item->Pose.Position.z;
xPos = x & (SECTOR(1) - 1); xPos = x & WALL_MASK;
zPos = z & (SECTOR(1) - 1); zPos = z & WALL_MASK;
short radius = Objects[item->ObjectNumber].radius; short radius = Objects[item->ObjectNumber].radius;
shiftX = 0; shiftX = 0;
shiftZ = 0; shiftZ = 0;
@ -667,8 +668,8 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
{ {
if (item->Pose.Position.y + top < ceiling) if (item->Pose.Position.y + top < ceiling)
{ {
item->Pose.Position.x = old.x; item->Pose.Position.x = prevPos.x;
item->Pose.Position.z = old.z; item->Pose.Position.z = prevPos.z;
dy = LOT->Fly; dy = LOT->Fly;
} }
else else
@ -687,13 +688,13 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
} }
else if (item->Pose.Position.y <= height) else if (item->Pose.Position.y <= height)
{ {
dy = 0;
item->Pose.Position.y = height; item->Pose.Position.y = height;
dy = 0;
} }
else else
{ {
item->Pose.Position.x = old.x; item->Pose.Position.x = prevPos.x;
item->Pose.Position.z = old.z; item->Pose.Position.z = prevPos.z;
dy = -LOT->Fly; dy = -LOT->Fly;
} }
@ -730,11 +731,7 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
if (item->Pose.Position.y > item->Floor) if (item->Pose.Position.y > item->Floor)
{ {
if (item->Pose.Position.y > (item->Floor + CLICK(1))) if (item->Pose.Position.y > (item->Floor + CLICK(1)))
{ item->Pose.Position = prevPos;
item->Pose.Position.x = old.x;
item->Pose.Position.y = old.y;
item->Pose.Position.z = old.z;
}
else else
item->Pose.Position.y = item->Floor; item->Pose.Position.y = item->Floor;
} }
@ -751,11 +748,7 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
top = bounds->Y1; // TODO: check if Y1 or Y2 top = bounds->Y1; // TODO: check if Y1 or Y2
if (item->Pose.Position.y + top < ceiling) if (item->Pose.Position.y + top < ceiling)
{ item->Pose.Position = prevPos;
item->Pose.Position.x = old.x;
item->Pose.Position.z = old.z;
item->Pose.Position.y = old.y;
}
floor = GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &roomNumber); floor = GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &roomNumber);
item->Floor = GetFloorHeight(floor, item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z); item->Floor = GetFloorHeight(floor, item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z);
@ -771,7 +764,6 @@ int CreatureAnimation(short itemNumber, short angle, short tilt)
} }
CreatureSwitchRoom(itemNumber); CreatureSwitchRoom(itemNumber);
return true; return true;
} }
@ -890,7 +882,7 @@ int ValidBox(ItemInfo* item, short zoneNumber, short boxNumber)
return false; return false;
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
int* zone = g_Level.Zones[creature->LOT.Zone][FlipStatus].data(); int* zone = g_Level.Zones[(int)creature->LOT.Zone][FlipStatus].data();
if (creature->LOT.Fly == NO_FLYING && zone[boxNumber] != zoneNumber) if (creature->LOT.Fly == NO_FLYING && zone[boxNumber] != zoneNumber)
return false; return false;
@ -976,7 +968,7 @@ int UpdateLOT(LOTInfo* LOT, int depth)
int SearchLOT(LOTInfo* LOT, int depth) int SearchLOT(LOTInfo* LOT, int depth)
{ {
int* zone = g_Level.Zones[LOT->Zone][FlipStatus].data(); int* zone = g_Level.Zones[(int)LOT->Zone][FlipStatus].data();
int searchZone = zone[LOT->Head]; int searchZone = zone[LOT->Head];
if (depth <= 0) if (depth <= 0)
@ -1090,7 +1082,7 @@ int CreatureActive(short itemNumber)
if (item->Flags & IFLAG_KILLED) if (item->Flags & IFLAG_KILLED)
return false; // Object is already dead return false; // Object is already dead
if (item->Status == ITEM_INVISIBLE || !item->Data.is<CreatureInfo>()) if (item->Status == ITEM_INVISIBLE || !item->IsCreature())
{ {
if (!EnableEntityAI(itemNumber, 0)) if (!EnableEntityAI(itemNumber, 0))
return false; // AI couldn't be activated return false; // AI couldn't be activated
@ -1168,7 +1160,7 @@ int CreatureVault(short itemNumber, short angle, int vault, int shift)
vault = 0; vault = 0;
else if (item->Floor > y + CHECK_CLICK(7)) else if (item->Floor > y + CHECK_CLICK(7))
vault = -4; vault = -4;
// FIXME: edit assets adding climb down animations for Von Croy and baddys? // FIXME: edit assets adding climb down animations for Von Croy and baddies?
else if (item->Floor > y + CHECK_CLICK(5) && else if (item->Floor > y + CHECK_CLICK(5) &&
item->ObjectNumber != ID_VON_CROY && item->ObjectNumber != ID_VON_CROY &&
item->ObjectNumber != ID_BADDY1 && item->ObjectNumber != ID_BADDY1 &&
@ -1373,7 +1365,7 @@ void FindAITargetObject(CreatureInfo* creature, short objectNumber)
if (g_Level.AIObjects.size() > 0) if (g_Level.AIObjects.size() > 0)
{ {
AI_OBJECT* foundObject = NULL; AI_OBJECT* foundObject = nullptr;
for (int i = 0; i < g_Level.AIObjects.size(); i++) for (int i = 0; i < g_Level.AIObjects.size(); i++)
{ {
@ -1381,7 +1373,7 @@ void FindAITargetObject(CreatureInfo* creature, short objectNumber)
if (aiObject->objectNumber == objectNumber && aiObject->triggerFlags == item->ItemFlags[3] && aiObject->roomNumber != NO_ROOM) if (aiObject->objectNumber == objectNumber && aiObject->triggerFlags == item->ItemFlags[3] && aiObject->roomNumber != NO_ROOM)
{ {
int* zone = g_Level.Zones[creature->LOT.Zone][FlipStatus].data(); int* zone = g_Level.Zones[(int)creature->LOT.Zone][FlipStatus].data();
auto* room = &g_Level.Rooms[item->RoomNumber]; auto* room = &g_Level.Rooms[item->RoomNumber];
item->BoxNumber = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z)->Box; item->BoxNumber = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z)->Box;
@ -1400,7 +1392,7 @@ void FindAITargetObject(CreatureInfo* creature, short objectNumber)
} }
} }
if (foundObject != NULL) if (foundObject != nullptr)
{ {
auto* aiItem = creature->AITarget; auto* aiItem = creature->AITarget;
@ -1441,7 +1433,7 @@ void CreatureAIInfo(ItemInfo* item, AI_INFO* AI)
creature->Enemy = LaraItem; creature->Enemy = LaraItem;
} }
int* zone = g_Level.Zones[creature->LOT.Zone][FlipStatus].data(); int* zone = g_Level.Zones[(int)creature->LOT.Zone][FlipStatus].data();
auto* room = &g_Level.Rooms[item->RoomNumber]; auto* room = &g_Level.Rooms[item->RoomNumber];
item->BoxNumber = NO_BOX; item->BoxNumber = NO_BOX;
@ -1463,8 +1455,8 @@ void CreatureAIInfo(ItemInfo* item, AI_INFO* AI)
// This prevents enemies from running to Lara and attacking nothing when she is hanging or shimmying. -- Lwmte, 27.06.22 // This prevents enemies from running to Lara and attacking nothing when she is hanging or shimmying. -- Lwmte, 27.06.22
bool reachable = false; bool reachable = false;
if (object->zoneType == ZoneType::ZONE_FLYER || if (object->ZoneType == ZoneType::Flyer ||
(object->zoneType == ZoneType::ZONE_WATER && TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, item->RoomNumber))) (object->ZoneType == ZoneType::Water && TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, item->RoomNumber)))
{ {
reachable = true; // If NPC is flying or swimming in water, always reach Lara reachable = true; // If NPC is flying or swimming in water, always reach Lara
} }

View file

@ -112,15 +112,15 @@ void DisableEntityAI(short itemNumber)
item->Data = nullptr; item->Data = nullptr;
} }
void InitialiseSlot(short itemNum, short slot, bool makeTarget) void InitialiseSlot(short itemNumber, short slot, bool makeTarget)
{ {
auto* item = &g_Level.Items[itemNum]; auto* item = &g_Level.Items[itemNumber];
auto* obj = &Objects[item->ObjectNumber]; auto* object = &Objects[item->ObjectNumber];
item->Data = CreatureInfo(); item->Data = CreatureInfo();
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
InitialiseLOTarray(itemNum);
creature->ItemNumber = itemNum; InitialiseLOTarray(itemNumber);
creature->ItemNumber = itemNumber;
creature->Mood = MoodType::Bored; creature->Mood = MoodType::Bored;
creature->JointRotation[0] = 0; creature->JointRotation[0] = 0;
creature->JointRotation[1] = 0; creature->JointRotation[1] = 0;
@ -136,12 +136,12 @@ void InitialiseSlot(short itemNum, short slot, bool makeTarget)
creature->MonkeySwingAhead = false; creature->MonkeySwingAhead = false;
creature->LOT.CanJump = false; creature->LOT.CanJump = false;
creature->LOT.CanMonkey = false; creature->LOT.CanMonkey = false;
creature->LOT.IsAmphibious = false; // only the crocodile can go water and land. (default: true) creature->LOT.IsAmphibious = false; // True for crocodile by default as the only the crocodile that can move in water and on land.
creature->LOT.IsJumping = false; creature->LOT.IsJumping = false;
creature->LOT.IsMonkeying = false; creature->LOT.IsMonkeying = false;
creature->MaxTurn = ANGLE(1); creature->MaxTurn = ANGLE(1);
creature->Flags = 0; creature->Flags = 0;
creature->Enemy = NULL; creature->Enemy = nullptr;
creature->LOT.Fly = NO_FLYING; creature->LOT.Fly = NO_FLYING;
creature->LOT.BlockMask = BLOCKED; creature->LOT.BlockMask = BLOCKED;
@ -155,117 +155,116 @@ void InitialiseSlot(short itemNum, short slot, bool makeTarget)
else else
creature->AITarget = nullptr; creature->AITarget = nullptr;
switch (obj->zoneType) switch (object->ZoneType)
{ {
default: default:
case ZONE_NULL: case ZoneType::None:
creature->LOT.Step = CLICK(1); creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1); creature->LOT.Drop = -CLICK(1);
obj->zoneType = ZONE_BASIC; // only entity that use CreatureActive() will reach InitialiseSlot() ! object->ZoneType = ZoneType::Basic; // Only entities that use CreatureActive() will reach InitialiseSlot().
break; break;
case ZONE_SKELLY: // Can jump.
// Can jump case ZoneType::Skeleton:
creature->LOT.Step = CLICK(1); creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1); creature->LOT.Drop = -CLICK(1);
creature->LOT.CanJump = true; creature->LOT.CanJump = true;
creature->LOT.Zone = ZONE_SKELLY; creature->LOT.Zone = ZoneType::Skeleton;
break; break;
case ZONE_BASIC: case ZoneType::Basic:
creature->LOT.Step = CLICK(1); creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1); creature->LOT.Drop = -CLICK(1);
creature->LOT.Zone = ZONE_BASIC; creature->LOT.Zone = ZoneType::Basic;
break; break;
case ZONE_FLYER: // Can fly.
// Can fly case ZoneType::Flyer:
creature->LOT.Step = SECTOR(20); creature->LOT.Step = SECTOR(20);
creature->LOT.Drop = -SECTOR(20); creature->LOT.Drop = -SECTOR(20);
creature->LOT.Fly = DEFAULT_FLY_UPDOWN_SPEED; creature->LOT.Fly = DEFAULT_FLY_UPDOWN_SPEED;
creature->LOT.Zone = ZONE_FLYER; creature->LOT.Zone = ZoneType::Flyer;
break; break;
case ZONE_WATER: // Can swim.
// Can swim case ZoneType::Water:
creature->LOT.Step = SECTOR(20); creature->LOT.Step = SECTOR(20);
creature->LOT.Drop = -SECTOR(20); creature->LOT.Drop = -SECTOR(20);
creature->LOT.Zone = ZONE_WATER; creature->LOT.Zone = ZoneType::Water;
if (item->ObjectNumber == ID_CROCODILE) if (item->ObjectNumber == ID_CROCODILE)
{ {
creature->LOT.Fly = DEFAULT_SWIM_UPDOWN_SPEED / 2; // is more slow than the other underwater entity creature->LOT.Fly = DEFAULT_SWIM_UPDOWN_SPEED / 2; // Slower than the other underwater creatures.
creature->LOT.IsAmphibious = true; // crocodile can walk and swim. creature->LOT.IsAmphibious = true; // Can walk and swim.
} }
else if (item->ObjectNumber == ID_BIG_RAT) else if (item->ObjectNumber == ID_BIG_RAT)
{ {
creature->LOT.Fly = NO_FLYING; // dont want the bigrat to be able to go in water (just the surface !) creature->LOT.Fly = NO_FLYING; // Can't swim underwater, only on the surface.
creature->LOT.IsAmphibious = true; // bigrat can walk and swim. creature->LOT.IsAmphibious = true; // Can walk and swim.
} }
else else
{
creature->LOT.Fly = DEFAULT_SWIM_UPDOWN_SPEED; creature->LOT.Fly = DEFAULT_SWIM_UPDOWN_SPEED;
}
break; break;
case ZONE_HUMAN_CLASSIC: // Can climb.
// Can climb case ZoneType::HumanClassic:
creature->LOT.Step = SECTOR(1); creature->LOT.Step = SECTOR(1);
creature->LOT.Drop = -SECTOR(1); creature->LOT.Drop = -SECTOR(1);
creature->LOT.Zone = ZONE_HUMAN_CLASSIC; creature->LOT.Zone = ZoneType::HumanClassic;
break; break;
case ZONE_HUMAN_JUMP: // Can climb and jump.
// Can climb and jump case ZoneType::HumanJump:
creature->LOT.Step = SECTOR(1); creature->LOT.Step = SECTOR(1);
creature->LOT.Drop = -SECTOR(1); creature->LOT.Drop = -SECTOR(1);
creature->LOT.CanJump = true; creature->LOT.CanJump = true;
creature->LOT.Zone = ZONE_HUMAN_CLASSIC; creature->LOT.Zone = ZoneType::HumanClassic;
break; break;
case ZONE_HUMAN_JUMP_AND_MONKEY: // Can climb, jump, monkeyswing.
// Can climb, jump, monkey case ZoneType::HumanJumpAndMonkey:
creature->LOT.Step = SECTOR(1); creature->LOT.Step = SECTOR(1);
creature->LOT.Drop = -SECTOR(1); creature->LOT.Drop = -SECTOR(1);
creature->LOT.CanJump = true; creature->LOT.CanJump = true;
creature->LOT.CanMonkey = true; creature->LOT.CanMonkey = true;
creature->LOT.Zone = ZONE_HUMAN_CLASSIC; creature->LOT.Zone = ZoneType::HumanClassic;
break; break;
case ZONE_HUMAN_LONGJUMP_AND_MONKEY: // Can climb, jump, monkey swing, long jump.
// Can climb, jump, monkey, long jump case ZoneType::HumanLongJumpAndMonkey:
creature->LOT.Step = SECTOR(1) + CLICK(3); creature->LOT.Step = SECTOR(1) + CLICK(3);
creature->LOT.Drop = -(SECTOR(1) + CLICK(3)); creature->LOT.Drop = -(SECTOR(1) + CLICK(3));
creature->LOT.CanJump = true; creature->LOT.CanJump = true;
creature->LOT.CanMonkey = true; creature->LOT.CanMonkey = true;
creature->LOT.Zone = ZONE_VON_CROY; creature->LOT.Zone = ZoneType::VonCroy;
break; break;
case ZONE_SPIDER: case ZoneType::Spider:
creature->LOT.Step = SECTOR(1) - CLICK(2); creature->LOT.Step = SECTOR(1) - CLICK(2);
creature->LOT.Drop = -(SECTOR(1) - CLICK(2)); creature->LOT.Drop = -(SECTOR(1) - CLICK(2));
creature->LOT.Zone = ZONE_HUMAN_CLASSIC; creature->LOT.Zone = ZoneType::HumanClassic;
break; break;
case ZONE_BLOCKABLE: case ZoneType::Blockable:
creature->LOT.BlockMask = BLOCKABLE; creature->LOT.BlockMask = BLOCKABLE;
creature->LOT.Zone = ZONE_BASIC; creature->LOT.Zone = ZoneType::Basic;
break; break;
case ZONE_APE: case ZoneType::Ape:
creature->LOT.Step = CLICK(2); creature->LOT.Step = CLICK(2);
creature->LOT.Drop = -SECTOR(1); creature->LOT.Drop = -SECTOR(1);
break; break;
case ZONE_SOPHIALEE: case ZoneType::SophiaLee:
creature->LOT.Step = SECTOR(1); creature->LOT.Step = SECTOR(1);
creature->LOT.Drop = -CLICK(3); creature->LOT.Drop = -CLICK(3);
creature->LOT.Zone = ZONE_HUMAN_CLASSIC; creature->LOT.Zone = ZoneType::HumanClassic;
break; break;
} }
ClearLOT(&creature->LOT); ClearLOT(&creature->LOT);
if (itemNum != Lara.ItemNumber) if (itemNumber != Lara.ItemNumber)
CreateZone(item); CreateZone(item);
SlotsUsed++; SlotsUsed++;
@ -304,16 +303,16 @@ void ClearLOT(LOTInfo* LOT)
void CreateZone(ItemInfo* item) void CreateZone(ItemInfo* item)
{ {
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
auto* r = &g_Level.Rooms[item->RoomNumber]; auto* room = &g_Level.Rooms[item->RoomNumber];
item->BoxNumber = GetSector(r, item->Pose.Position.x - r->x, item->Pose.Position.z - r->z)->Box; item->BoxNumber = GetSector(room, item->Pose.Position.x - room->x, item->Pose.Position.z - room->z)->Box;
if (creature->LOT.Fly) if (creature->LOT.Fly)
{ {
BOX_NODE* node = creature->LOT.Node.data(); auto* node = creature->LOT.Node.data();
creature->LOT.ZoneCount = 0; creature->LOT.ZoneCount = 0;
for (int i = 0; i < g_Level.Boxes.size(); i++) for (size_t i = 0; i < g_Level.Boxes.size(); i++)
{ {
node->boxNumber = i; node->boxNumber = i;
node++; node++;
@ -322,8 +321,8 @@ void CreateZone(ItemInfo* item)
} }
else else
{ {
int* zone = g_Level.Zones[creature->LOT.Zone][0].data(); int* zone = g_Level.Zones[(int)creature->LOT.Zone][0].data();
int* flippedZone = g_Level.Zones[creature->LOT.Zone][1].data(); int* flippedZone = g_Level.Zones[(int)creature->LOT.Zone][1].data();
int zoneNumber = zone[item->BoxNumber]; int zoneNumber = zone[item->BoxNumber];
int flippedZoneNumber = flippedZone[item->BoxNumber]; int flippedZoneNumber = flippedZone[item->BoxNumber];
@ -331,7 +330,7 @@ void CreateZone(ItemInfo* item)
auto* node = creature->LOT.Node.data(); auto* node = creature->LOT.Node.data();
creature->LOT.ZoneCount = 0; creature->LOT.ZoneCount = 0;
for (int i = 0; i < g_Level.Boxes.size(); i++) for (size_t i = 0; i < g_Level.Boxes.size(); i++)
{ {
if (*zone == zoneNumber || *flippedZone == flippedZoneNumber) if (*zone == zoneNumber || *flippedZone == flippedZoneNumber)
{ {

View file

@ -56,6 +56,7 @@ int TriggerActive(ItemInfo* item)
flag = !flag; flag = !flag;
} }
} }
return flag; return flag;
} }

View file

@ -1,7 +1,8 @@
#pragma once #pragma once
#include <vector>
#include "Math/Math.h" #include "Math/Math.h"
using std::vector;
struct ItemInfo; struct ItemInfo;
struct BOX_NODE struct BOX_NODE
@ -12,34 +13,37 @@ struct BOX_NODE
int boxNumber; int boxNumber;
}; };
enum ZoneType : char enum class ZoneType
{ {
ZONE_NULL = -1, // default zone None = -1,
ZONE_SKELLY = 0, Skeleton,
ZONE_BASIC, Basic,
ZONE_FLYER, Flyer,
ZONE_HUMAN_CLASSIC, HumanClassic,
ZONE_VON_CROY, VonCroy,
ZONE_WATER, Water,
ZONE_MAX, Max,
/// custom zone (using zone above for LOT.zone):
ZONE_HUMAN_JUMP_AND_MONKEY, // Custom zones (above zones are used for LOT.zone):
ZONE_HUMAN_JUMP, HumanJumpAndMonkey,
ZONE_SPIDER, HumanJump,
ZONE_BLOCKABLE, // for trex, shiva, etc.. Spider,
ZONE_SOPHIALEE, // dont want sophia to go down again ! Blockable, // For large creatures such as trex and shiva.
ZONE_APE, // only 2 click climb SophiaLee, // Prevents Sophia from going to lower levels again.
ZONE_HUMAN_LONGJUMP_AND_MONKEY, Ape, // Only 0.5 block climb.
HumanLongJumpAndMonkey,
}; };
struct LOTInfo struct LOTInfo
{ {
bool Initialised; bool Initialised;
std::vector<BOX_NODE> Node; vector<BOX_NODE> Node;
int Head; int Head;
int Tail; int Tail;
ZoneType Zone = ZoneType::None;
Vector3i Target = Vector3i::Zero;
int SearchNumber; int SearchNumber;
int BlockMask; int BlockMask;
short Step; short Step;
@ -49,18 +53,16 @@ struct LOTInfo
int RequiredBox; int RequiredBox;
short Fly; short Fly;
bool CanJump; bool CanJump = false;
bool CanMonkey; bool CanMonkey = false;
bool IsJumping; bool IsJumping = false;
bool IsMonkeying; bool IsMonkeying = false;
bool IsAmphibious; bool IsAmphibious = false;
Vector3i Target;
ZoneType Zone;
}; };
enum class MoodType enum class MoodType
{ {
None,
Bored, Bored,
Attack, Attack,
Escape, Escape,
@ -70,45 +72,43 @@ enum class MoodType
enum class CreatureAIPriority enum class CreatureAIPriority
{ {
None, None,
High, Low,
Medium, Medium,
Low High
}; };
struct CreatureInfo struct CreatureInfo
{ {
short ItemNumber; short ItemNumber = -1;
short MaxTurn; LOTInfo LOT = {};
short JointRotation[4]; MoodType Mood = MoodType::None;
bool HeadLeft; ItemInfo* Enemy = nullptr;
bool HeadRight; ItemInfo* AITarget = nullptr;
short AITargetNumber = -1;
Vector3i Target = Vector3i::Zero;
bool Patrol; // Unused? short MaxTurn = 0;
bool Alerted; short JointRotation[4] = {};
bool Friendly; bool HeadLeft = false;
bool HurtByLara; bool HeadRight = false;
bool Poisoned;
bool JumpAhead;
bool MonkeySwingAhead;
bool ReachedGoal;
bool Patrol = false; // Unused?
bool Alerted = false;
bool Friendly = false;
bool HurtByLara = false;
bool Poisoned = false;
bool JumpAhead = false;
bool MonkeySwingAhead = false;
bool ReachedGoal = false;
short FiredWeapon;
short Tosspad; short Tosspad;
short LocationAI; short LocationAI;
short FiredWeapon; short Flags = 0;
LOTInfo LOT;
MoodType Mood;
ItemInfo* Enemy;
short AITargetNumber;
ItemInfo* AITarget;
short Pad; // Unused?
Vector3i Target;
#ifdef CREATURE_AI_PRIORITY_OPTIMIZATION #ifdef CREATURE_AI_PRIORITY_OPTIMIZATION
CreatureAIPriority Priority; CreatureAIPriority Priority = CreatureAIPriority::None;
size_t FramesSinceLOTUpdate; size_t FramesSinceLOTUpdate = 0;
#endif #endif
short Flags;
}; };

View file

@ -26,7 +26,7 @@ template<class... Ts> struct visitor : Ts... { using Ts::operator()...; };
template<class... Ts> visitor(Ts...)->visitor<Ts...>; // line not needed in C++20... template<class... Ts> visitor(Ts...)->visitor<Ts...>; // line not needed in C++20...
using namespace TEN::Entities::TR4; using namespace TEN::Entities::TR4;
using namespace TEN::Entities::TR5; using namespace TEN::Entities::Creatures::TR5;
using namespace TEN::Entities::Vehicles; using namespace TEN::Entities::Vehicles;
struct ItemInfo; struct ItemInfo;

View file

@ -57,7 +57,7 @@ void ControlMissile(short fxNumber)
fx->pos.Position.x += velocity * phd_sin(fx->pos.Orientation.y); fx->pos.Position.x += velocity * phd_sin(fx->pos.Orientation.y);
auto probe = GetCollision(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, fx->roomNumber); auto probe = GetCollision(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, fx->roomNumber);
auto hitLara = ItemNearLara(&fx->pos, 200); auto hitLara = ItemNearLara(&fx->pos.Position, 200);
// Check for hitting something. // Check for hitting something.
if (fx->pos.Position.y >= probe.Position.Floor || if (fx->pos.Position.y >= probe.Position.Floor ||

View file

@ -17,45 +17,45 @@ bool ShotLara(ItemInfo* item, AI_INFO* AI, BiteInfo gun, short extraRotation, in
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
auto* enemy = creature->Enemy; auto* enemy = creature->Enemy;
bool hit = false; bool hasHit = false;
bool targetable = false; bool isTargetable = false;
if (AI->distance <= pow(MAX_VISIBILITY_DISTANCE, 2) && Targetable(item, AI)) if (AI->distance <= SQUARE(MAX_VISIBILITY_DISTANCE) && Targetable(item, AI))
{ {
int distance = phd_sin(AI->enemyFacing) * enemy->Animation.Velocity.z * pow(MAX_VISIBILITY_DISTANCE, 2) / 300; int distance = phd_sin(AI->enemyFacing) * enemy->Animation.Velocity.z * pow(MAX_VISIBILITY_DISTANCE, 2) / 300;
distance = pow(distance, 2) + AI->distance; distance = SQUARE(distance) + AI->distance;
if (distance <= pow(MAX_VISIBILITY_DISTANCE, 2)) if (distance <= SQUARE(MAX_VISIBILITY_DISTANCE))
{ {
int random = (pow(MAX_VISIBILITY_DISTANCE, 2) - AI->distance) / (pow(MAX_VISIBILITY_DISTANCE, 2) / 0x5000) + 8192; int random = (SQUARE(MAX_VISIBILITY_DISTANCE) - AI->distance) / (SQUARE(MAX_VISIBILITY_DISTANCE) / 0x5000) + 8192;
hit = GetRandomControl() < random; hasHit = GetRandomControl() < random;
} }
else else
hit = false; hasHit = false;
targetable = true; isTargetable = true;
} }
else else
{ {
hit = false; hasHit = false;
targetable = false; isTargetable = false;
} }
if (damage) if (damage)
{ {
if (enemy->IsLara()) if (enemy->IsLara())
{ {
if (hit) if (hasHit)
{ {
DoDamage(enemy, damage); DoDamage(enemy, damage);
CreatureEffect(item, gun, &GunHit); CreatureEffect(item, gun, &GunHit);
} }
else if (targetable) else if (isTargetable)
CreatureEffect(item, gun, &GunMiss); CreatureEffect(item, gun, &GunMiss);
} }
else else
{ {
CreatureEffect(item, gun, &GunShot); CreatureEffect(item, gun, &GunShot);
if (hit) if (hasHit)
{ {
enemy->HitStatus = true; enemy->HitStatus = true;
enemy->HitPoints += damage / -10; enemy->HitPoints += damage / -10;
@ -72,7 +72,7 @@ bool ShotLara(ItemInfo* item, AI_INFO* AI, BiteInfo gun, short extraRotation, in
// TODO: smash objects // TODO: smash objects
return targetable; return isTargetable;
} }
short GunMiss(int x, int y, int z, short velocity, short yRot, short roomNumber) short GunMiss(int x, int y, int z, short velocity, short yRot, short roomNumber)
@ -141,32 +141,32 @@ bool TargetVisible(ItemInfo* item, AI_INFO* AI, float maxAngle)
return false; return false;
// Check just in case. // Check just in case.
auto* creatureInfo = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
if (creatureInfo == nullptr) if (creature == nullptr)
return false; return false;
auto* enemy = creatureInfo->Enemy; auto* enemy = creature->Enemy;
if (enemy == nullptr || enemy->HitPoints == 0) if (enemy == nullptr || enemy->HitPoints == 0)
return false; return false;
short angle = AI->angle - creatureInfo->JointRotation[2]; short angle = AI->angle - creature->JointRotation[2];
if (angle > -ANGLE(maxAngle) && angle < ANGLE(maxAngle)) if (angle > ANGLE(-maxAngle) && angle < ANGLE(maxAngle))
{ {
GameVector start;
GameVector target;
auto& bounds = GetBestFrame(enemy)->boundingBox; auto& bounds = GetBestFrame(enemy)->boundingBox;
start.x = item->Pose.Position.x; auto origin = GameVector(
start.y = item->Pose.Position.y - CLICK(3); item->Pose.Position.x,
start.z = item->Pose.Position.z; item->Pose.Position.y - CLICK(3),
start.roomNumber = item->RoomNumber; item->Pose.Position.z,
item->RoomNumber
target.x = enemy->Pose.Position.x; );
target.y = enemy->Pose.Position.y + ((((bounds.Y1 * 2) + bounds.Y1) + bounds.Y2) / 4); auto target = GameVector(
target.z = enemy->Pose.Position.z; enemy->Pose.Position.x,
target.roomNumber = enemy->RoomNumber; // TODO: Check why this line didn't exist before. -- TokyoSU, 10/8/2022 enemy->Pose.Position.y + ((((bounds.Y1 * 2) + bounds.Y1) + bounds.Y2) / 4),
enemy->Pose.Position.z,
return LOS(&start, &target); enemy->RoomNumber // TODO: Check why this line didn't exist before. -- TokyoSU, 10/8/2022
);
return LOS(&origin, &target);
} }
return false; return false;

View file

@ -1093,11 +1093,11 @@ bool SaveGame::Save(int slot)
putDataInVec(Save::VarUnion::funcName, funcNameOffset); putDataInVec(Save::VarUnion::funcName, funcNameOffset);
} }
else if (std::holds_alternative<Vector3Int>(s)) else if (std::holds_alternative<Vector3i>(s))
{ {
Save::vec3TableBuilder vtb{ fbb }; Save::vec3TableBuilder vtb{ fbb };
Vector3Int data = std::get<Vector3Int>(s); Vector3i data = std::get<Vector3i>(s);
Save::Vector3 saveVec = FromVector3(std::get<Vector3Int>(s)); Save::Vector3 saveVec = FromVector3(std::get<Vector3i>(s));
vtb.add_vec(&saveVec); vtb.add_vec(&saveVec);
auto vec3Offset = vtb.Finish(); auto vec3Offset = vtb.Finish();
@ -1934,7 +1934,7 @@ bool SaveGame::Load(int slot)
} }
else if (var->u_type() == Save::VarUnion::vec3) else if (var->u_type() == Save::VarUnion::vec3)
{ {
loadedVars.push_back(ToVector3Int(var->u_as_vec3()->vec())); loadedVars.push_back(ToVector3i(var->u_as_vec3()->vec()));
} }
else if (var->u_type() == Save::VarUnion::funcName) else if (var->u_type() == Save::VarUnion::funcName)
{ {

View file

@ -260,7 +260,7 @@ namespace TEN::Entities::Effects
return; return;
} }
if (ItemNearLara(&fx->pos, 200)) if (ItemNearLara(&fx->pos.Position, 200))
{ {
LaraItem->HitStatus = true; LaraItem->HitStatus = true;
if (fx->flag1 != 6) if (fx->flag1 != 6)

View file

@ -154,7 +154,7 @@ namespace TEN::Entities::Effects
SoundEffect(SFX_TR4_LOOP_FOR_SMALL_FIRES, &item->Pose); SoundEffect(SFX_TR4_LOOP_FOR_SMALL_FIRES, &item->Pose);
if (!Lara.Burn && if (!Lara.Burn &&
ItemNearLara(&item->Pose, 600) && ItemNearLara(&item->Pose.Position, 600) &&
(pow(LaraItem->Pose.Position.x - item->Pose.Position.x, 2) + (pow(LaraItem->Pose.Position.x - item->Pose.Position.x, 2) +
pow(LaraItem->Pose.Position.z - item->Pose.Position.z, 2) < pow(SECTOR(0.5f), 2)) && pow(LaraItem->Pose.Position.z - item->Pose.Position.z, 2) < pow(SECTOR(0.5f), 2)) &&
Lara.Control.WaterStatus != WaterStatus::FlyCheat) Lara.Control.WaterStatus != WaterStatus::FlyCheat)
@ -561,8 +561,7 @@ namespace TEN::Entities::Effects
TriggerDynamicLight(x, item->Pose.Position.y, z, 12, (GetRandomControl() & 0x3F) + 192, ((GetRandomControl() >> 4) & 0x1F) + 96, 0); TriggerDynamicLight(x, item->Pose.Position.y, z, 12, (GetRandomControl() & 0x3F) + 192, ((GetRandomControl() >> 4) & 0x1F) + 96, 0);
auto pos = PoseData(item->Pose.Position); auto pos = item->Pose.Position;
if (ItemNearLara(&pos, 600)) if (ItemNearLara(&pos, 600))
{ {
if ((!Lara.Burn) && Lara.Control.WaterStatus != WaterStatus::FlyCheat) if ((!Lara.Burn) && Lara.Control.WaterStatus != WaterStatus::FlyCheat)

View file

@ -186,7 +186,7 @@ namespace TEN::Entities::TR4
locust->pos.Position.y += locust->randomRotation * phd_sin(-locust->pos.Orientation.x); locust->pos.Position.y += locust->randomRotation * phd_sin(-locust->pos.Orientation.x);
locust->pos.Position.z += locust->randomRotation * phd_cos(locust->pos.Orientation.x) * phd_cos(locust->pos.Orientation.y); locust->pos.Position.z += locust->randomRotation * phd_cos(locust->pos.Orientation.x) * phd_cos(locust->pos.Orientation.y);
if (ItemNearTarget(&locust->pos, LaraItem, CLICK(1) / 2)) if (ItemNearTarget(&locust->pos.Position, LaraItem, CLICK(1) / 2))
{ {
TriggerBlood(locust->pos.Position.x, locust->pos.Position.y, locust->pos.Position.z, 2 * GetRandomControl(), 2); TriggerBlood(locust->pos.Position.x, locust->pos.Position.y, locust->pos.Position.z, 2 * GetRandomControl(), 2);
DoDamage(LaraItem, LOCUST_LARA_DAMAGE); DoDamage(LaraItem, LOCUST_LARA_DAMAGE);

View file

@ -15,19 +15,23 @@
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto APE_ATTACK_DAMAGE = 200; constexpr auto APE_ATTACK_DAMAGE = 200;
constexpr auto APE_ATTACK_RANGE = SQUARE(SECTOR(0.42f)); constexpr auto APE_ATTACK_RANGE = SQUARE(SECTOR(0.42f));
constexpr auto APE_PANIC_RANGE = SQUARE(SECTOR(2)); constexpr auto APE_PANIC_RANGE = SQUARE(SECTOR(2));
constexpr auto APE_JUMP_CHANCE = 0xA0; constexpr auto APE_IDLE_JUMP_CHANCE = 1.0f / 6;
constexpr auto APE_POUND_CHEST_CHANCE = APE_JUMP_CHANCE + 0xA0; constexpr auto APE_IDLE_POUND_CHEST_CHANCE = 1.0f / 3;
constexpr auto APE_POUND_GROUND_CHANCE = APE_POUND_CHEST_CHANCE + 0xA0; constexpr auto APE_IDLE_POUND_GROUND_CHANCE = 1.0f / 2;
constexpr auto APE_RUN_LEFT_CHANCE = APE_POUND_GROUND_CHANCE + 0xA0; constexpr auto APE_IDLE_RUN_LEFT_CHANCE = 1.0f / 2;
constexpr auto APE_RUN_JUMP_CHANCE = APE_IDLE_JUMP_CHANCE / 32;
constexpr auto APE_RUN_POUND_CHEST_CHANCE = APE_IDLE_POUND_CHEST_CHANCE / 32;
constexpr auto APE_RUN_POUND_GROUND_CHANCE = APE_IDLE_POUND_GROUND_CHANCE / 32;
constexpr auto APE_RUN_RUN_LEFT_CHANCE = APE_IDLE_RUN_LEFT_CHANCE / 32;
constexpr auto SHIFT = 75; constexpr auto APE_SHIFT = 75;
#define APE_RUN_TURN_RATE_MAX ANGLE(5.0f) #define APE_RUN_TURN_RATE_MAX ANGLE(5.0f)
#define APE_DISPLAY_ANGLE ANGLE(45.0f) #define APE_DISPLAY_ANGLE ANGLE(45.0f)
@ -118,12 +122,12 @@ namespace TEN::Entities::TR1
if (yy < yFloor) if (yy < yFloor)
{ {
item->Pose.Position.x = (yFloor * SECTOR(1)) - SHIFT; item->Pose.Position.x = (yFloor * SECTOR(1)) - APE_SHIFT;
item->Pose.Orientation.y = ANGLE(90.0f); item->Pose.Orientation.y = ANGLE(90.0f);
} }
else else
{ {
item->Pose.Position.x = (yy * SECTOR(1)) + SHIFT; item->Pose.Position.x = (yy * SECTOR(1)) + APE_SHIFT;
item->Pose.Orientation.y = -ANGLE(90.0f); item->Pose.Orientation.y = -ANGLE(90.0f);
} }
} }
@ -131,12 +135,12 @@ namespace TEN::Entities::TR1
{ {
if (xx < xFloor) if (xx < xFloor)
{ {
item->Pose.Position.z = (xFloor * SECTOR(1)) - SHIFT; item->Pose.Position.z = (xFloor * SECTOR(1)) - APE_SHIFT;
item->Pose.Orientation.y = 0; item->Pose.Orientation.y = 0;
} }
else else
{ {
item->Pose.Position.z = (xx * SECTOR(1)) + SHIFT; item->Pose.Position.z = (xx * SECTOR(1)) + APE_SHIFT;
item->Pose.Orientation.y = -ANGLE(180.0f); item->Pose.Orientation.y = -ANGLE(180.0f);
} }
} }
@ -145,7 +149,7 @@ namespace TEN::Entities::TR1
// diagonal // diagonal
} }
if (CreatureVault(itemNumber, angle, 2, SHIFT) == 2) if (CreatureVault(itemNumber, angle, 2, APE_SHIFT) == 2)
{ {
item->Pose.Position.y = y; item->Pose.Position.y = y;
SetAnimation(item, APE_ANIM_VAULT); SetAnimation(item, APE_ANIM_VAULT);
@ -184,8 +188,6 @@ namespace TEN::Entities::TR1
if (item->HitStatus || AI.distance < APE_PANIC_RANGE) if (item->HitStatus || AI.distance < APE_PANIC_RANGE)
creatureInfo->Flags |= APE_FLAG_ATTACK; creatureInfo->Flags |= APE_FLAG_ATTACK;
short random;
switch (item->Animation.ActiveState) switch (item->Animation.ActiveState)
{ {
case APE_STATE_IDLE: case APE_STATE_IDLE:
@ -207,14 +209,13 @@ namespace TEN::Entities::TR1
else if (!(creatureInfo->Flags & APE_FLAG_ATTACK) && else if (!(creatureInfo->Flags & APE_FLAG_ATTACK) &&
AI.zoneNumber == AI.enemyZone && AI.ahead) AI.zoneNumber == AI.enemyZone && AI.ahead)
{ {
random = (short)(GetRandomControl() / 32); if (TestProbability(APE_IDLE_JUMP_CHANCE))
if (random < APE_JUMP_CHANCE)
item->Animation.TargetState = APE_STATE_JUMP; item->Animation.TargetState = APE_STATE_JUMP;
else if (random < APE_POUND_CHEST_CHANCE) else if (TestProbability(APE_IDLE_POUND_CHEST_CHANCE))
item->Animation.TargetState = APE_STATE_POUND_CHEST; item->Animation.TargetState = APE_STATE_POUND_CHEST;
else if (random < APE_POUND_GROUND_CHANCE) else if (TestProbability(APE_IDLE_POUND_GROUND_CHANCE))
item->Animation.TargetState = APE_STATE_POUND_GROUND; item->Animation.TargetState = APE_STATE_POUND_GROUND;
else if (random < APE_RUN_LEFT_CHANCE) else if (TestProbability(APE_IDLE_RUN_LEFT_CHANCE))
{ {
item->Animation.TargetState = APE_STATE_RUN_LEFT; item->Animation.TargetState = APE_STATE_RUN_LEFT;
creatureInfo->MaxTurn = 0; creatureInfo->MaxTurn = 0;
@ -246,18 +247,17 @@ namespace TEN::Entities::TR1
} }
else if (creatureInfo->Mood != MoodType::Escape) else if (creatureInfo->Mood != MoodType::Escape)
{ {
random = (short)GetRandomControl(); if (TestProbability(APE_RUN_JUMP_CHANCE))
if (random < APE_JUMP_CHANCE)
{ {
item->Animation.RequiredState = APE_STATE_JUMP; item->Animation.RequiredState = APE_STATE_JUMP;
item->Animation.TargetState = APE_STATE_IDLE; item->Animation.TargetState = APE_STATE_IDLE;
} }
else if (random < APE_POUND_CHEST_CHANCE) else if (TestProbability(APE_RUN_POUND_CHEST_CHANCE))
{ {
item->Animation.RequiredState = APE_STATE_POUND_CHEST; item->Animation.RequiredState = APE_STATE_POUND_CHEST;
item->Animation.TargetState = APE_STATE_IDLE; item->Animation.TargetState = APE_STATE_IDLE;
} }
else if (random < APE_POUND_GROUND_CHANCE) else if (TestProbability(APE_RUN_POUND_GROUND_CHANCE))
{ {
item->Animation.RequiredState = APE_STATE_POUND_GROUND; item->Animation.RequiredState = APE_STATE_POUND_GROUND;
item->Animation.TargetState = APE_STATE_IDLE; item->Animation.TargetState = APE_STATE_IDLE;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void ApeControl(short itemNumber); void ApeControl(short itemNumber);
} }

View file

@ -11,9 +11,10 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto BEAR_RUN_DAMAGE = 3; constexpr auto BEAR_RUN_DAMAGE = 3;
constexpr auto BEAR_ATTACK_DAMAGE = 200; constexpr auto BEAR_ATTACK_DAMAGE = 200;
@ -24,10 +25,10 @@ namespace TEN::Entities::TR1
constexpr auto BEAR_REAR_RANGE = SECTOR(2); constexpr auto BEAR_REAR_RANGE = SECTOR(2);
constexpr auto BEAR_REAR_SWIPE_ATTACK_RANGE = SECTOR(0.6f); constexpr auto BEAR_REAR_SWIPE_ATTACK_RANGE = SECTOR(0.6f);
constexpr auto BEAR_EAT_RANGE = CLICK(3); constexpr auto BEAR_EAT_RANGE = CLICK(3);
constexpr auto BEAR_ROAR_CHANCE = 0x50; constexpr auto BEAR_ROAR_CHANCE = 1.0f / 400;
constexpr auto BEAR_REAR_CHANCE = 0x300; constexpr auto BEAR_REAR_CHANCE = 1.0f / 40;
constexpr auto BEAR_DROP_CHANCE = 0x600; constexpr auto BEAR_DROP_CHANCE = 1.0f / 22;
#define BEAR_WALK_TURN_RATE_MAX ANGLE(2.0f) #define BEAR_WALK_TURN_RATE_MAX ANGLE(2.0f)
#define BEAR_RUN_TURN_RATE_MAX ANGLE(5.0f) #define BEAR_RUN_TURN_RATE_MAX ANGLE(5.0f)
@ -81,8 +82,8 @@ namespace TEN::Entities::TR1
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0; short angle = 0;
short head = 0;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
@ -117,8 +118,8 @@ namespace TEN::Entities::TR1
{ {
if (creature->Flags && item->TestBits(JointBitType::Touch, BearAttackJoints)) if (creature->Flags && item->TestBits(JointBitType::Touch, BearAttackJoints))
{ {
creature->Flags = 0;
DoDamage(creature->Enemy, BEAR_SLAM_DAMAGE); DoDamage(creature->Enemy, BEAR_SLAM_DAMAGE);
creature->Flags = 0;
} }
break; break;
@ -141,12 +142,12 @@ namespace TEN::Entities::TR1
if (item->HitStatus) if (item->HitStatus)
creature->Flags = 1; creature->Flags = 1;
const bool laraDead = LaraItem->HitPoints <= 0; bool isLaraDead = LaraItem->HitPoints <= 0;
switch (item->Animation.ActiveState) switch (item->Animation.ActiveState)
{ {
case BEAR_STATE_IDLE: case BEAR_STATE_IDLE:
if (laraDead) if (isLaraDead)
{ {
if (AI.bite && AI.distance < pow(BEAR_EAT_RANGE, 2)) if (AI.bite && AI.distance < pow(BEAR_EAT_RANGE, 2))
item->Animation.TargetState = BEAR_STATE_EAT; item->Animation.TargetState = BEAR_STATE_EAT;
@ -165,7 +166,7 @@ namespace TEN::Entities::TR1
case BEAR_STATE_STROLL: case BEAR_STATE_STROLL:
creature->MaxTurn = BEAR_WALK_TURN_RATE_MAX; creature->MaxTurn = BEAR_WALK_TURN_RATE_MAX;
if (laraDead && item->TestBits(JointBitType::Touch, BearAttackJoints) && AI.ahead) if (isLaraDead && item->TestBits(JointBitType::Touch, BearAttackJoints) && AI.ahead)
item->Animation.TargetState = BEAR_STATE_IDLE; item->Animation.TargetState = BEAR_STATE_IDLE;
else if (creature->Mood != MoodType::Bored) else if (creature->Mood != MoodType::Bored)
{ {
@ -174,10 +175,10 @@ namespace TEN::Entities::TR1
if (creature->Mood == MoodType::Escape) if (creature->Mood == MoodType::Escape)
item->Animation.RequiredState = BEAR_STATE_STROLL; item->Animation.RequiredState = BEAR_STATE_STROLL;
} }
else if (GetRandomControl() < BEAR_ROAR_CHANCE) else if (TestProbability(BEAR_ROAR_CHANCE))
{ {
item->Animation.RequiredState = BEAR_STATE_ROAR;
item->Animation.TargetState = BEAR_STATE_IDLE; item->Animation.TargetState = BEAR_STATE_IDLE;
item->Animation.RequiredState = BEAR_STATE_ROAR;
} }
break; break;
@ -186,16 +187,14 @@ namespace TEN::Entities::TR1
creature->MaxTurn = BEAR_RUN_TURN_RATE_MAX; creature->MaxTurn = BEAR_RUN_TURN_RATE_MAX;
if (item->TestBits(JointBitType::Touch, BearAttackJoints)) if (item->TestBits(JointBitType::Touch, BearAttackJoints))
{
DoDamage(creature->Enemy, BEAR_RUN_DAMAGE); DoDamage(creature->Enemy, BEAR_RUN_DAMAGE);
}
if (creature->Mood == MoodType::Bored || laraDead) if (creature->Mood == MoodType::Bored || isLaraDead)
item->Animation.TargetState = BEAR_STATE_IDLE; item->Animation.TargetState = BEAR_STATE_IDLE;
else if (AI.ahead && !item->Animation.RequiredState) else if (AI.ahead && !item->Animation.RequiredState)
{ {
if (AI.distance < pow(BEAR_REAR_RANGE, 2) && if (AI.distance < pow(BEAR_REAR_RANGE, 2) &&
GetRandomControl() < BEAR_REAR_CHANCE && TestProbability(BEAR_REAR_CHANCE) &&
!creature->Flags) !creature->Flags)
{ {
item->Animation.RequiredState = BEAR_STATE_REAR; item->Animation.RequiredState = BEAR_STATE_REAR;
@ -227,8 +226,8 @@ namespace TEN::Entities::TR1
case BEAR_STATE_WALK_FORWARD: case BEAR_STATE_WALK_FORWARD:
if (creature->Flags) if (creature->Flags)
{ {
item->Animation.RequiredState = BEAR_STATE_STROLL;
item->Animation.TargetState = BEAR_STATE_REAR; item->Animation.TargetState = BEAR_STATE_REAR;
item->Animation.RequiredState = BEAR_STATE_STROLL;
} }
else if (AI.ahead && item->TestBits(JointBitType::Touch, BearAttackJoints)) else if (AI.ahead && item->TestBits(JointBitType::Touch, BearAttackJoints))
item->Animation.TargetState = BEAR_STATE_REAR; item->Animation.TargetState = BEAR_STATE_REAR;
@ -237,15 +236,15 @@ namespace TEN::Entities::TR1
item->Animation.TargetState = BEAR_STATE_REAR; item->Animation.TargetState = BEAR_STATE_REAR;
item->Animation.RequiredState = BEAR_STATE_STROLL; item->Animation.RequiredState = BEAR_STATE_STROLL;
} }
else if (creature->Mood == MoodType::Bored || GetRandomControl() < BEAR_ROAR_CHANCE) else if (creature->Mood == MoodType::Bored || TestProbability(BEAR_ROAR_CHANCE))
{ {
item->Animation.TargetState = BEAR_STATE_REAR;
item->Animation.RequiredState = BEAR_STATE_ROAR; item->Animation.RequiredState = BEAR_STATE_ROAR;
item->Animation.TargetState = BEAR_STATE_REAR;
} }
else if (AI.distance > pow(BEAR_REAR_RANGE, 2) || GetRandomControl() < BEAR_DROP_CHANCE) else if (AI.distance > pow(BEAR_REAR_RANGE, 2) || TestProbability(BEAR_DROP_CHANCE))
{ {
item->Animation.RequiredState = BEAR_STATE_IDLE;
item->Animation.TargetState = BEAR_STATE_REAR; item->Animation.TargetState = BEAR_STATE_REAR;
item->Animation.RequiredState = BEAR_STATE_IDLE;
} }
break; break;
@ -264,8 +263,8 @@ namespace TEN::Entities::TR1
if (!item->Animation.RequiredState && if (!item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, BearAttackJoints)) item->TestBits(JointBitType::Touch, BearAttackJoints))
{ {
CreatureEffect(item, BearBite, DoBloodSplat);
DoDamage(creature->Enemy, BEAR_ATTACK_DAMAGE); DoDamage(creature->Enemy, BEAR_ATTACK_DAMAGE);
CreatureEffect(item, BearBite, DoBloodSplat);
item->Animation.RequiredState = BEAR_STATE_IDLE; item->Animation.RequiredState = BEAR_STATE_IDLE;
} }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void BearControl(short itemNumber); void BearControl(short itemNumber);
} }

View file

@ -16,7 +16,7 @@
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto BIG_RAT_BITE_ATTACK_DAMAGE = 20; constexpr auto BIG_RAT_BITE_ATTACK_DAMAGE = 20;
constexpr auto BIG_RAT_POUNCE_ATTACK_DAMAGE = 25; constexpr auto BIG_RAT_POUNCE_ATTACK_DAMAGE = 25;
@ -27,7 +27,7 @@ namespace TEN::Entities::TR1
constexpr auto BIG_RAT_POUNCE_ATTACK_RANGE = SQUARE(SECTOR(0.5f)); constexpr auto BIG_RAT_POUNCE_ATTACK_RANGE = SQUARE(SECTOR(0.5f));
constexpr auto BIG_RAT_WATER_BITE_ATTACK_RANGE = SQUARE(SECTOR(0.3f)); constexpr auto BIG_RAT_WATER_BITE_ATTACK_RANGE = SQUARE(SECTOR(0.3f));
constexpr auto BIG_RAT_REAR_POSE_CHANCE = 0.008f; constexpr auto BIG_RAT_REAR_POSE_CHANCE = 1.0f / 128;
constexpr auto BIG_RAT_SWIM_UP_DOWN_SPEED = 32; constexpr auto BIG_RAT_SWIM_UP_DOWN_SPEED = 32;
constexpr auto BIG_RAT_WATER_SURFACE_OFFSET = 10; constexpr auto BIG_RAT_WATER_SURFACE_OFFSET = 10;
@ -115,10 +115,11 @@ namespace TEN::Entities::TR1
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
int waterHeight = GetRatWaterHeight(item); int waterHeight = GetRatWaterHeight(item);
short head = 0;
short angle = 0;
bool isOnWater = waterHeight != NO_HEIGHT; bool isOnWater = waterHeight != NO_HEIGHT;
short angle = 0;
short head = 0;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
if (item->Animation.ActiveState != BIG_RAT_STATE_LAND_DEATH && if (item->Animation.ActiveState != BIG_RAT_STATE_LAND_DEATH &&
@ -183,9 +184,9 @@ namespace TEN::Entities::TR1
if (!item->Animation.RequiredState && AI.ahead && if (!item->Animation.RequiredState && AI.ahead &&
item->TestBits(JointBitType::Touch, BigRatBite.meshNum)) item->TestBits(JointBitType::Touch, BigRatBite.meshNum))
{ {
item->Animation.RequiredState = BIG_RAT_STATE_IDLE;
DoDamage(creature->Enemy, BIG_RAT_BITE_ATTACK_DAMAGE); DoDamage(creature->Enemy, BIG_RAT_BITE_ATTACK_DAMAGE);
CreatureEffect(item, BigRatBite, DoBloodSplat); CreatureEffect(item, BigRatBite, DoBloodSplat);
item->Animation.RequiredState = BIG_RAT_STATE_IDLE;
} }
break; break;
@ -194,9 +195,9 @@ namespace TEN::Entities::TR1
if (!item->Animation.RequiredState && AI.ahead && if (!item->Animation.RequiredState && AI.ahead &&
item->TestBits(JointBitType::Touch, BigRatBite.meshNum)) item->TestBits(JointBitType::Touch, BigRatBite.meshNum))
{ {
item->Animation.RequiredState = BIG_RAT_STATE_RUN_FORWARD;
DoDamage(creature->Enemy, BIG_RAT_POUNCE_ATTACK_DAMAGE); DoDamage(creature->Enemy, BIG_RAT_POUNCE_ATTACK_DAMAGE);
CreatureEffect(item, BigRatBite, DoBloodSplat); CreatureEffect(item, BigRatBite, DoBloodSplat);
item->Animation.RequiredState = BIG_RAT_STATE_RUN_FORWARD;
} }
break; break;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void InitialiseBigRat(short itemNumber); void InitialiseBigRat(short itemNumber);
void BigRatControl(short itemNumber); void BigRatControl(short itemNumber);

View file

@ -17,19 +17,20 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto CENTAUR_REAR_DAMAGE = 200; constexpr auto CENTAUR_REAR_DAMAGE = 200;
constexpr auto CENTAUR_REAR_RANGE = SECTOR(1.5f); constexpr auto CENTAUR_REAR_RANGE = SECTOR(1.5f);
constexpr auto CENTAUR_REAR_CHANCE = 0x60; constexpr auto CENTAUR_REAR_CHANCE = 1.0f / 340;
constexpr auto CENTAUR_BOMB_VELOCITY = 20; constexpr auto CENTAUR_BOMB_VELOCITY = 20;
#define CENTAUR_TURN_RATE_MAX ANGLE(4.0f) #define CENTAUR_TURN_RATE_MAX ANGLE(4.0f)
const auto CentaurRocketBite = BiteInfo(Vector3(11.0f, 415.0f, 41.0f), 13); const auto CentaurRocketBite = BiteInfo(Vector3(11.0f, 415.0f, 41.0f), 13);
const auto CentaurRearBite = BiteInfo(Vector3(50.0f, 30.0f, 0.0f), 5); const auto CentaurRearBite = BiteInfo(Vector3(50.0f, 30.0f, 0.0f), 5);
const vector<int> CentaurAttackJoints = { 0, 3, 4, 7, 8, 16, 17 }; const vector<int> CentaurAttackJoints = { 0, 3, 4, 7, 8, 16, 17 };
enum CentaurState enum CentaurState
@ -56,17 +57,14 @@ namespace TEN::Entities::TR1
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0; short angle = 0;
short head = 0;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
if (item->Animation.ActiveState != CENTAUR_STATE_DEATH) if (item->Animation.ActiveState != CENTAUR_STATE_DEATH)
{ SetAnimation(item, CENTAUR_ANIM_DEATH);
item->Animation.AnimNumber = Objects[ID_CENTAUR_MUTANT].animIndex + CENTAUR_ANIM_DEATH;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = CENTAUR_STATE_DEATH;
}
} }
else else
{ {
@ -98,18 +96,18 @@ namespace TEN::Entities::TR1
case CENTAUR_STATE_RUN_FORWARD: case CENTAUR_STATE_RUN_FORWARD:
if (AI.bite && AI.distance < pow(CENTAUR_REAR_RANGE, 2)) if (AI.bite && AI.distance < pow(CENTAUR_REAR_RANGE, 2))
{ {
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
item->Animation.TargetState = CENTAUR_STATE_IDLE; item->Animation.TargetState = CENTAUR_STATE_IDLE;
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
} }
else if (Targetable(item, &AI)) else if (Targetable(item, &AI))
{ {
item->Animation.TargetState = CENTAUR_STATE_IDLE;
item->Animation.RequiredState = CENTAUR_STATE_AIM; item->Animation.RequiredState = CENTAUR_STATE_AIM;
item->Animation.TargetState = CENTAUR_STATE_IDLE;
} }
else if (GetRandomControl() < CENTAUR_REAR_CHANCE) else if (TestProbability(CENTAUR_REAR_CHANCE))
{ {
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
item->Animation.TargetState = CENTAUR_STATE_IDLE; item->Animation.TargetState = CENTAUR_STATE_IDLE;
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
} }
break; break;
@ -137,8 +135,8 @@ namespace TEN::Entities::TR1
if (!item->Animation.RequiredState && if (!item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, CentaurAttackJoints)) item->TestBits(JointBitType::Touch, CentaurAttackJoints))
{ {
CreatureEffect(item, CentaurRearBite, DoBloodSplat);
DoDamage(creature->Enemy, CENTAUR_REAR_DAMAGE); DoDamage(creature->Enemy, CENTAUR_REAR_DAMAGE);
CreatureEffect(item, CentaurRearBite, DoBloodSplat);
item->Animation.RequiredState = CENTAUR_STATE_IDLE; item->Animation.RequiredState = CENTAUR_STATE_IDLE;
} }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto SHARD_VELOCITY = 250; constexpr auto SHARD_VELOCITY = 250;
constexpr auto BOMB_VELOCITY = 220; constexpr auto BOMB_VELOCITY = 220;

View file

@ -14,7 +14,7 @@
// - Bacon Lara cannot be targeted. // - Bacon Lara cannot be targeted.
// - Bacon Lara cannot move like Lara. // - Bacon Lara cannot move like Lara.
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
// Original: // Original:
void InitialiseDoppelganger(short itemNumber) void InitialiseDoppelganger(short itemNumber)
@ -80,26 +80,24 @@ namespace TEN::Entities::TR1
int laraFloorHeight = GetCollision(LaraItem).Position.Floor; int laraFloorHeight = GetCollision(LaraItem).Position.Floor;
// Animate bacon Lara, mirroring Lara's position. // Animate bacon Lara, mirroring Lara's position.
item->Animation.FrameNumber = LaraItem->Animation.FrameNumber;
item->Animation.AnimNumber = LaraItem->Animation.AnimNumber; item->Animation.AnimNumber = LaraItem->Animation.AnimNumber;
item->Pose.Position.x = pos.x; item->Animation.FrameNumber = LaraItem->Animation.FrameNumber;
item->Pose.Position.y = pos.y; item->Pose.Position = pos;
item->Pose.Position.z = pos.z;
item->Pose.Orientation.x = LaraItem->Pose.Orientation.x; item->Pose.Orientation.x = LaraItem->Pose.Orientation.x;
item->Pose.Orientation.y = LaraItem->Pose.Orientation.y - ANGLE(180.0f); item->Pose.Orientation.y = LaraItem->Pose.Orientation.y - ANGLE(180.0f);
item->Pose.Orientation.z = LaraItem->Pose.Orientation.z; item->Pose.Orientation.z = LaraItem->Pose.Orientation.z;
ItemNewRoom(itemNumber, LaraItem->RoomNumber); ItemNewRoom(itemNumber, LaraItem->RoomNumber);
// Compare floor heights. // Compare floor heights.
if (item->Floor >= laraFloorHeight + SECTOR(1) + 1 && // Add 1 to avoid bacon Lara dying when exiting water. if (item->Floor >= (laraFloorHeight + SECTOR(1) + 1) && // Add 1 to avoid bacon Lara dying when exiting water.
!LaraItem->Animation.IsAirborne) !LaraItem->Animation.IsAirborne)
{ {
SetAnimation(item, LA_JUMP_WALL_SMASH_START); SetAnimation(item, LA_JUMP_WALL_SMASH_START);
item->Animation.Velocity.z = 0;
item->Animation.Velocity.y = 0;
item->Animation.IsAirborne = true; item->Animation.IsAirborne = true;
item->Data = -1; item->Animation.Velocity.y = 0.0f;
item->Animation.Velocity.z = 0.0f;
item->Pose.Position.y += 50; item->Pose.Position.y += 50;
item->Data = -1;
} }
} }
@ -114,8 +112,8 @@ namespace TEN::Entities::TR1
item->Pose.Position.y = item->Floor; item->Pose.Position.y = item->Floor;
TestTriggers(item, true); TestTriggers(item, true);
item->Animation.Velocity.y = 0;
item->Animation.IsAirborne = false; item->Animation.IsAirborne = false;
item->Animation.Velocity.y = 0.0f;
item->Animation.TargetState = LS_DEATH; item->Animation.TargetState = LS_DEATH;
item->Animation.RequiredState = LS_DEATH; item->Animation.RequiredState = LS_DEATH;
} }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void InitialiseDoppelganger(short itemNumber); void InitialiseDoppelganger(short itemNumber);
void DoppelgangerControl(short itemNumber); void DoppelgangerControl(short itemNumber);

View file

@ -14,9 +14,10 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto MUTANT_ATTACK_DAMAGE = 500; constexpr auto MUTANT_ATTACK_DAMAGE = 500;
constexpr auto MUTANT_CONTACT_DAMAGE = 6; constexpr auto MUTANT_CONTACT_DAMAGE = 6;
@ -24,16 +25,17 @@ namespace TEN::Entities::TR1
constexpr auto MUTANT_ATTACK_RANGE = SQUARE(SECTOR(2.5f)); constexpr auto MUTANT_ATTACK_RANGE = SQUARE(SECTOR(2.5f));
constexpr auto MUTANT_CLOSE_RANGE = SQUARE(SECTOR(2.2f)); constexpr auto MUTANT_CLOSE_RANGE = SQUARE(SECTOR(2.2f));
constexpr auto MUTANT_ATTACK_1_CHANCE = 0x2AF8; // TODO: Unused.
constexpr auto MUTANT_ATTACK_2_CHANCE = 0x55F0; constexpr auto MUTANT_ATTACK_1_CHANCE = 1.0f / 3.0f;
constexpr auto MUTANT_ATTACK_2_CHANCE = MUTANT_ATTACK_1_CHANCE * 2;
#define MUTANT_NEED_TURN ANGLE(45.0f) #define MUTANT_NEED_TURN ANGLE(45.0f)
#define MUTANT_TURN ANGLE(3.0f) #define MUTANT_TURN ANGLE(3.0f)
#define LARA_GIANT_MUTANT_DEATH 6 // TODO: Not 13? Check this. #define LARA_GIANT_MUTANT_DEATH 6 // TODO: Not 13? Check this.
const vector<int> MutantAttackJoints = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }; const vector<int> MutantAttackJoints = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
const vector<int> MutantAttackLeftJoints = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }; const vector<int> MutantAttackLeftJoint = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
const vector<int> MutantAttackRightJoints = { 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }; const vector<int> MutantAttackRightJoints = { 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
enum GiantMutantState enum GiantMutantState
@ -66,17 +68,13 @@ namespace TEN::Entities::TR1
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0; short angle = 0;
short head = 0;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
if (item->Animation.ActiveState != MUTANT_STATE_DEATH) if (item->Animation.ActiveState != MUTANT_STATE_DEATH)
{ SetAnimation(item, MUTANT_ANIM_DEATH);
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + MUTANT_ANIM_DEATH;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = MUTANT_STATE_DEATH;
}
} }
else else
{ {
@ -92,9 +90,7 @@ namespace TEN::Entities::TR1
angle = (short)phd_atan(creature->Target.z - item->Pose.Position.z, creature->Target.x - item->Pose.Position.x) - item->Pose.Orientation.y; angle = (short)phd_atan(creature->Target.z - item->Pose.Position.z, creature->Target.x - item->Pose.Position.x) - item->Pose.Orientation.y;
if (item->TouchBits) if (item->TouchBits)
{
DoDamage(creature->Enemy, MUTANT_CONTACT_DAMAGE); DoDamage(creature->Enemy, MUTANT_CONTACT_DAMAGE);
}
switch (item->Animation.ActiveState) switch (item->Animation.ActiveState)
{ {
@ -122,7 +118,7 @@ namespace TEN::Entities::TR1
else else
item->Animation.TargetState = MUTANT_STATE_FORWARD; item->Animation.TargetState = MUTANT_STATE_FORWARD;
} }
else if (GetRandomControl() < 0x4000) else if (TestProbability(0.5f))
item->Animation.TargetState = MUTANT_STATE_ATTACK_1; item->Animation.TargetState = MUTANT_STATE_ATTACK_1;
else else
item->Animation.TargetState = MUTANT_STATE_ATTACK_2; item->Animation.TargetState = MUTANT_STATE_ATTACK_2;
@ -178,8 +174,8 @@ namespace TEN::Entities::TR1
case MUTANT_STATE_ATTACK_1: case MUTANT_STATE_ATTACK_1:
if (!creature->Flags && item->TestBits(JointBitType::Touch, MutantAttackRightJoints)) if (!creature->Flags && item->TestBits(JointBitType::Touch, MutantAttackRightJoints))
{ {
creature->Flags = 1;
DoDamage(creature->Enemy, MUTANT_ATTACK_DAMAGE); DoDamage(creature->Enemy, MUTANT_ATTACK_DAMAGE);
creature->Flags = 1;
} }
break; break;
@ -187,8 +183,8 @@ namespace TEN::Entities::TR1
case MUTANT_STATE_ATTACK_2: case MUTANT_STATE_ATTACK_2:
if (!creature->Flags && item->TestBits(JointBitType::Touch, MutantAttackJoints)) if (!creature->Flags && item->TestBits(JointBitType::Touch, MutantAttackJoints))
{ {
creature->Flags = 1;
DoDamage(creature->Enemy, MUTANT_ATTACK_DAMAGE); DoDamage(creature->Enemy, MUTANT_ATTACK_DAMAGE);
creature->Flags = 1;
} }
break; break;
@ -202,14 +198,11 @@ namespace TEN::Entities::TR1
LaraItem->Animation.AnimNumber = Objects[ID_LARA_EXTRA_ANIMS].animIndex + LARA_GIANT_MUTANT_DEATH; LaraItem->Animation.AnimNumber = Objects[ID_LARA_EXTRA_ANIMS].animIndex + LARA_GIANT_MUTANT_DEATH;
LaraItem->Animation.FrameNumber = g_Level.Anims[LaraItem->Animation.AnimNumber].frameBase; LaraItem->Animation.FrameNumber = g_Level.Anims[LaraItem->Animation.AnimNumber].frameBase;
LaraItem->Animation.ActiveState = LaraItem->Animation.TargetState = 46; LaraItem->Animation.ActiveState = 46;
LaraItem->RoomNumber = item->RoomNumber; LaraItem->Animation.TargetState = 46;
LaraItem->Pose.Position.x = item->Pose.Position.x;
LaraItem->Pose.Position.y = item->Pose.Position.y;
LaraItem->Pose.Position.z = item->Pose.Position.z;
LaraItem->Pose.Orientation.y = item->Pose.Orientation.y;
LaraItem->Pose.Orientation.x = LaraItem->Pose.Orientation.z = 0;
LaraItem->Animation.IsAirborne = false; LaraItem->Animation.IsAirborne = false;
LaraItem->Pose = PoseData(item->Pose.Position, 0, item->Pose.Orientation.y, 0);
LaraItem->RoomNumber = item->RoomNumber;
LaraItem->HitPoints = -1; LaraItem->HitPoints = -1;
Lara.Air = -1; Lara.Air = -1;
Lara.Control.HandStatus = HandStatus::Busy; Lara.Control.HandStatus = HandStatus::Busy;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void GiantMutantControl(short itemNumber); void GiantMutantControl(short itemNumber);
} }

View file

@ -8,11 +8,13 @@
#include "Game/misc.h" #include "Game/misc.h"
#include "Game/missile.h" #include "Game/missile.h"
#include "Game/people.h" #include "Game/people.h"
#include "Math/Math.h"
#include "Sound/sound.h" #include "Sound/sound.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Math/Math.h"
namespace TEN::Entities::TR1 using namespace TEN::Math::Random;
namespace TEN::Entities::Creatures::TR1
{ {
// TODO: Organise. // TODO: Organise.
constexpr auto NATLA_SHOT_DAMAGE = 100; constexpr auto NATLA_SHOT_DAMAGE = 100;
@ -20,9 +22,10 @@ namespace TEN::Entities::TR1
constexpr auto NATLA_DEATH_TIME = (FPS * 16); // 16 seconds. constexpr auto NATLA_DEATH_TIME = (FPS * 16); // 16 seconds.
constexpr auto NATLA_FLYMODE = 0x8000; constexpr auto NATLA_FLYMODE = 0x8000;
constexpr auto NATLA_TIMER = 0x7FFF; constexpr auto NATLA_TIMER = 0x7FFF;
constexpr auto NATLA_LAND_CHANCE = 0x100;
constexpr auto NATLA_GUN_VELOCITY = 400; constexpr auto NATLA_GUN_VELOCITY = 400;
constexpr auto NATLA_LAND_CHANCE = 0.008f;
#define NATLA_TURN_NEAR_DEATH_SPEED ANGLE(6.0f) #define NATLA_TURN_NEAR_DEATH_SPEED ANGLE(6.0f)
#define NATLA_TURN_SPEED ANGLE(5.0f) #define NATLA_TURN_SPEED ANGLE(5.0f)
#define NATLA_FLY_ANGLE_SPEED ANGLE(5.0f) #define NATLA_FLY_ANGLE_SPEED ANGLE(5.0f)
@ -183,7 +186,7 @@ namespace TEN::Entities::TR1
if (item->Animation.ActiveState == NATLA_STATE_FLY && (creature->Flags & NATLA_FLYMODE)) if (item->Animation.ActiveState == NATLA_STATE_FLY && (creature->Flags & NATLA_FLYMODE))
{ {
if (creature->Flags & NATLA_FLYMODE && shoot && GetRandomControl() < NATLA_LAND_CHANCE) if (creature->Flags & NATLA_FLYMODE && shoot && TestProbability(NATLA_LAND_CHANCE))
creature->Flags -= NATLA_FLYMODE; creature->Flags -= NATLA_FLYMODE;
if (!(creature->Flags & NATLA_FLYMODE)) if (!(creature->Flags & NATLA_FLYMODE))

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void NatlaControl(short itemNumber); void NatlaControl(short itemNumber);
} }

View file

@ -11,13 +11,14 @@
#include "Game/misc.h" #include "Game/misc.h"
#include "Game/missile.h" #include "Game/missile.h"
#include "Game/people.h" #include "Game/people.h"
#include "Math/Math.h"
#include "Sound/sound.h" #include "Sound/sound.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Math/Math.h"
using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE = 150; constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_DAMAGE = 150;
constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE = 100; constexpr auto WINGED_MUTANT_RUN_JUMP_ATTACK_DAMAGE = 100;
@ -29,8 +30,8 @@ namespace TEN::Entities::TR1
constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE = SQUARE(SECTOR(2.5f)); constexpr auto WINGED_MUTANT_IDLE_JUMP_ATTACK_RANGE = SQUARE(SECTOR(2.5f));
constexpr auto WINGED_MUTANT_ATTACK_RANGE = SQUARE(SECTOR(3.75f)); constexpr auto WINGED_MUTANT_ATTACK_RANGE = SQUARE(SECTOR(3.75f));
constexpr auto WINGED_MUTANT_POSE_CHANCE = 85; constexpr auto WINGED_MUTANT_POSE_CHANCE = 1.0f / 400;
constexpr auto WINGED_MUTANT_UNPOSE_CHANCE = 200; constexpr auto WINGED_MUTANT_UNPOSE_CHANCE = 1.0f / 164;
constexpr auto WINGED_MUTANT_FLY_VELOCITY = CLICK(1) / 8; constexpr auto WINGED_MUTANT_FLY_VELOCITY = CLICK(1) / 8;
constexpr auto WINGED_MUTANT_SHARD_VELOCITY = 250; constexpr auto WINGED_MUTANT_SHARD_VELOCITY = 250;
@ -337,7 +338,7 @@ namespace TEN::Entities::TR1
if (AI.distance < WINGED_MUTANT_WALK_RANGE) if (AI.distance < WINGED_MUTANT_WALK_RANGE)
{ {
if (AI.zoneNumber == AI.enemyZone || if (AI.zoneNumber == AI.enemyZone ||
GetRandomControl() < WINGED_MUTANT_UNPOSE_CHANCE) TestProbability(WINGED_MUTANT_UNPOSE_CHANCE))
{ {
item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD; item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD;
} }
@ -345,7 +346,7 @@ namespace TEN::Entities::TR1
else else
item->Animation.TargetState = WMUTANT_STATE_IDLE; item->Animation.TargetState = WMUTANT_STATE_IDLE;
} }
else if (creature->Mood == MoodType::Bored && GetRandomControl() < WINGED_MUTANT_UNPOSE_CHANCE) else if (creature->Mood == MoodType::Bored && TestProbability(WINGED_MUTANT_UNPOSE_CHANCE))
item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD; item->Animation.TargetState = WMUTANT_STATE_WALK_FORWARD;
else if (creature->Mood == MoodType::Attack || else if (creature->Mood == MoodType::Attack ||
creature->Mood == MoodType::Escape) creature->Mood == MoodType::Escape)
@ -363,7 +364,7 @@ namespace TEN::Entities::TR1
else if (creature->Mood == MoodType::Bored || else if (creature->Mood == MoodType::Bored ||
(creature->Mood == MoodType::Stalk && AI.zoneNumber != AI.enemyZone)) (creature->Mood == MoodType::Stalk && AI.zoneNumber != AI.enemyZone))
{ {
if (GetRandomControl() < WINGED_MUTANT_POSE_CHANCE) if (TestProbability(WINGED_MUTANT_POSE_CHANCE))
item->Animation.TargetState = WMUTANT_STATE_POSE; item->Animation.TargetState = WMUTANT_STATE_POSE;
} }
else if (creature->Mood == MoodType::Stalk && else if (creature->Mood == MoodType::Stalk &&

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void InitialiseWingedMutant(short itemNumber); void InitialiseWingedMutant(short itemNumber);
void WingedMutantControl(short itemNumber); void WingedMutantControl(short itemNumber);

View file

@ -11,9 +11,10 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
constexpr auto WOLF_BITE_DAMAGE = 100; constexpr auto WOLF_BITE_DAMAGE = 100;
constexpr auto WOLF_LUNGE_DAMAGE = 50; constexpr auto WOLF_LUNGE_DAMAGE = 50;
@ -21,9 +22,9 @@ namespace TEN::Entities::TR1
constexpr auto WOLF_ATTACK_RANGE = SQUARE(SECTOR(1.5f)); constexpr auto WOLF_ATTACK_RANGE = SQUARE(SECTOR(1.5f));
constexpr auto WOLF_STALK_RANGE = SQUARE(SECTOR(2)); constexpr auto WOLF_STALK_RANGE = SQUARE(SECTOR(2));
constexpr auto WOLF_WAKE_CHANCE = 0x20; constexpr auto WOLF_WAKE_CHANCE = 1.0f / 1000;
constexpr auto WOLF_SLEEP_CHANCE = 0x20; constexpr auto WOLF_SLEEP_CHANCE = 1.0f / 1000;
constexpr auto WOLF_HOWL_CHANCE = 0x180; constexpr auto WOLF_HOWL_CHANCE = 1.0f / 85;
constexpr auto WOLF_SLEEP_FRAME = 96; constexpr auto WOLF_SLEEP_FRAME = 96;
@ -110,7 +111,7 @@ namespace TEN::Entities::TR1
item->Animation.RequiredState = WOLF_STATE_CROUCH; item->Animation.RequiredState = WOLF_STATE_CROUCH;
item->Animation.TargetState = WOLF_STATE_IDLE; item->Animation.TargetState = WOLF_STATE_IDLE;
} }
else if (GetRandomControl() < WOLF_WAKE_CHANCE) else if (TestProbability(WOLF_WAKE_CHANCE))
{ {
item->Animation.RequiredState = WOLF_STATE_WALK; item->Animation.RequiredState = WOLF_STATE_WALK;
item->Animation.TargetState = WOLF_STATE_IDLE; item->Animation.TargetState = WOLF_STATE_IDLE;
@ -133,7 +134,7 @@ namespace TEN::Entities::TR1
item->Animation.TargetState = WOLF_STATE_STALK; item->Animation.TargetState = WOLF_STATE_STALK;
item->Animation.RequiredState = WOLF_STATE_NONE; item->Animation.RequiredState = WOLF_STATE_NONE;
} }
else if (GetRandomControl() < WOLF_SLEEP_CHANCE) else if (TestProbability(WOLF_SLEEP_CHANCE))
{ {
item->Animation.RequiredState = WOLF_STATE_SLEEP; item->Animation.RequiredState = WOLF_STATE_SLEEP;
item->Animation.TargetState = WOLF_STATE_IDLE; item->Animation.TargetState = WOLF_STATE_IDLE;
@ -174,7 +175,7 @@ namespace TEN::Entities::TR1
item->Animation.TargetState = WOLF_STATE_RUN; item->Animation.TargetState = WOLF_STATE_RUN;
} }
} }
else if (GetRandomControl() < WOLF_HOWL_CHANCE) else if (TestProbability(WOLF_HOWL_CHANCE))
{ {
item->Animation.RequiredState = WOLF_STATE_HOWL; item->Animation.RequiredState = WOLF_STATE_HOWL;
item->Animation.TargetState = WOLF_STATE_CROUCH; item->Animation.TargetState = WOLF_STATE_CROUCH;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR1 namespace TEN::Entities::Creatures::TR1
{ {
void InitialiseWolf(short itemNumber); void InitialiseWolf(short itemNumber);
void WolfControl(short itemNumber); void WolfControl(short itemNumber);

View file

@ -1,7 +1,6 @@
#include "framework.h" #include "framework.h"
#include "Objects/TR1/tr1_objects.h" #include "Objects/TR1/tr1_objects.h"
/// necessary import
#include "Game/control/box.h" #include "Game/control/box.h"
#include "Game/collision/collide_item.h" #include "Game/collision/collide_item.h"
#include "Game/itemdata/creature_info.h" #include "Game/itemdata/creature_info.h"
@ -9,7 +8,7 @@
#include "Specific/setup.h" #include "Specific/setup.h"
#include "Specific/level.h" #include "Specific/level.h"
/// entities // Creatures
#include "Objects/TR1/Entity/tr1_ape.h" // OK #include "Objects/TR1/Entity/tr1_ape.h" // OK
#include "Objects/TR1/Entity/tr1_bear.h" // OK #include "Objects/TR1/Entity/tr1_bear.h" // OK
#include "Objects/TR1/Entity/tr1_doppelganger.h" // OK #include "Objects/TR1/Entity/tr1_doppelganger.h" // OK
@ -21,7 +20,7 @@
#include "Objects/TR1/Entity/tr1_winged_mutant.h" #include "Objects/TR1/Entity/tr1_winged_mutant.h"
#include "Objects/Utils/object_helper.h" #include "Objects/Utils/object_helper.h"
using namespace TEN::Entities::TR1; using namespace TEN::Entities::Creatures::TR1;
static void StartEntity(ObjectInfo* obj) static void StartEntity(ObjectInfo* obj)
{ {
@ -78,7 +77,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveHitpoints = true; obj->saveHitpoints = true;
obj->saveAnim = true; obj->saveAnim = true;
obj->saveFlags = true; obj->saveFlags = true;
obj->zoneType = ZONE_APE; obj->ZoneType = ZoneType::Ape;
} }
obj = &Objects[ID_BIG_RAT]; obj = &Objects[ID_BIG_RAT];
@ -98,7 +97,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveAnim = true; obj->saveAnim = true;
obj->saveFlags = true; obj->saveFlags = true;
obj->waterCreature = true; obj->waterCreature = true;
obj->zoneType = ZONE_WATER; obj->ZoneType = ZoneType::Water;
obj->SetBoneRotation(1, ROT_Y); // head obj->SetBoneRotation(1, ROT_Y); // head
} }
@ -174,7 +173,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveHitpoints = true; obj->saveHitpoints = true;
obj->saveAnim = true; obj->saveAnim = true;
obj->saveFlags = true; obj->saveFlags = true;
obj->zoneType = ZONE_BLOCKABLE; obj->ZoneType = ZoneType::Blockable;
obj->SetBoneRotation(10, ROT_X | ROT_Y); obj->SetBoneRotation(10, ROT_X | ROT_Y);
} }
@ -194,7 +193,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveFlags = true; obj->saveFlags = true;
obj->saveHitpoints = true; obj->saveHitpoints = true;
obj->savePosition = true; obj->savePosition = true;
obj->zoneType = ZONE_FLYER; obj->ZoneType = ZoneType::Flyer;
obj->SetBoneRotation(1, ROT_Y); // torso obj->SetBoneRotation(1, ROT_Y); // torso
obj->SetBoneRotation(2, ROT_Y); // head obj->SetBoneRotation(2, ROT_Y); // head
} }

View file

@ -12,7 +12,7 @@
using std::vector; using std::vector;
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
constexpr auto BARRACUDA_ATTACK_DAMAGE = 100; constexpr auto BARRACUDA_ATTACK_DAMAGE = 100;
constexpr auto BARRACUDA_IDLE_ATTACK_RANGE = SQUARE(SECTOR(0.67f)); constexpr auto BARRACUDA_IDLE_ATTACK_RANGE = SQUARE(SECTOR(0.67f));
@ -23,7 +23,7 @@ namespace TEN::Entities::TR2
enum BarracudaState enum BarracudaState
{ {
BARRACUDA_STATE_NONE = 0, // No state 0.
BARRACUDA_STATE_IDLE = 1, BARRACUDA_STATE_IDLE = 1,
BARRACUDA_STATE_SWIM_SLOW = 2, BARRACUDA_STATE_SWIM_SLOW = 2,
BARRACUDA_STATE_SWIM_FAST = 3, BARRACUDA_STATE_SWIM_FAST = 3,

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void BarracudaControl(short itemNumber); void BarracudaControl(short itemNumber);
} }

View file

@ -14,7 +14,7 @@
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
constexpr auto BIRD_MONSTER_ATTACK_DAMAGE = 200; constexpr auto BIRD_MONSTER_ATTACK_DAMAGE = 200;
constexpr auto BIRD_MONSTER_SLAM_CRUSH_ATTACK_RANGE = SQUARE(SECTOR(1)); constexpr auto BIRD_MONSTER_SLAM_CRUSH_ATTACK_RANGE = SQUARE(SECTOR(1));
@ -29,7 +29,7 @@ namespace TEN::Entities::TR2
enum BirdMonsterState enum BirdMonsterState
{ {
BMONSTER_STATE_NONE = 0, // No state 0.
BMONSTER_STATE_IDLE = 1, BMONSTER_STATE_IDLE = 1,
BMONSTER_STATE_WALK_FORWARD = 2, BMONSTER_STATE_WALK_FORWARD = 2,
BMONSTER_STATE_SLAM_ATTACK_START = 3, BMONSTER_STATE_SLAM_ATTACK_START = 3,

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void BirdMonsterControl(short itemNumber); void BirdMonsterControl(short itemNumber);
} }

View file

@ -18,28 +18,23 @@
#include "Specific/setup.h" #include "Specific/setup.h"
using namespace TEN::Input; using namespace TEN::Input;
using std::vector;
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
const auto DragonMouthBite = BiteInfo(Vector3(35.0f, 171.0f, 1168.0f), 12);
constexpr auto DRAGON_SWIPE_ATTACK_DAMAGE = 250; constexpr auto DRAGON_SWIPE_ATTACK_DAMAGE = 250;
constexpr auto DRAGON_TOUCH_DAMAGE = 10; constexpr auto DRAGON_CONTACT_DAMAGE = 10;
const auto DragonMouthBite = BiteInfo(Vector3(35.0f, 171.0f, 1168.0f), 12);
const vector<int> DragonSwipeAttackJointsLeft = { 24, 25, 26, 27, 28, 29, 30 };
const vector<int> DragonSwipeAttackJointsRight = { 1, 2, 3, 4, 5, 6, 7 };
// TODO: Organise. // TODO: Organise.
#define DRAGON_SWIPE_DAMAGE 250
#define DRAGON_TOUCH_DAMAGE 10
#define DRAGON_LIVE_TIME (30 * 11) #define DRAGON_LIVE_TIME (30 * 11)
#define DRAGON_CLOSE_RANGE pow(SECTOR(3), 2) #define DRAGON_CLOSE_RANGE pow(SECTOR(3), 2)
#define DRAGON_STATE_IDLE_RANGE pow(SECTOR(6), 2) #define DRAGON_STATE_IDLE_RANGE pow(SECTOR(6), 2)
#define DRAGON_FLAME_SPEED 200 #define DRAGON_FLAME_SPEED 200
#define DRAGON_TOUCH_R 0x0fe
#define DRAGON_TOUCH_L 0x7f000000
#define DRAGON_ALMOST_LIVE 100 #define DRAGON_ALMOST_LIVE 100
#define BOOM_TIME 130 #define BOOM_TIME 130
#define BOOM_TIME_MIDDLE 140 #define BOOM_TIME_MIDDLE 140
@ -58,7 +53,7 @@ namespace TEN::Entities::TR2
enum DragonState enum DragonState
{ {
DRAGON_STATE_NONE = 0, // No state 0.
DRAGON_STATE_WALK = 1, DRAGON_STATE_WALK = 1,
DRAGON_STATE_LEFT = 2, DRAGON_STATE_LEFT = 2,
DRAGON_STATE_RIGHT = 3, DRAGON_STATE_RIGHT = 3,
@ -255,8 +250,8 @@ namespace TEN::Entities::TR2
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0; short angle = 0;
short head = 0;
bool ahead; bool ahead;
@ -318,9 +313,7 @@ namespace TEN::Entities::TR2
ahead = (AI.ahead && AI.distance > DRAGON_CLOSE_RANGE && AI.distance < DRAGON_STATE_IDLE_RANGE); ahead = (AI.ahead && AI.distance > DRAGON_CLOSE_RANGE && AI.distance < DRAGON_STATE_IDLE_RANGE);
if (item->TouchBits) if (item->TouchBits)
{ DoDamage(creature->Enemy, DRAGON_CONTACT_DAMAGE);
DoDamage(creature->Enemy, DRAGON_TOUCH_DAMAGE);
}
switch (item->Animation.ActiveState) switch (item->Animation.ActiveState)
{ {
@ -350,19 +343,19 @@ namespace TEN::Entities::TR2
break; break;
case DRAGON_STATE_SWIPE_LEFT: case DRAGON_STATE_SWIPE_LEFT:
if (item->TouchBits & DRAGON_TOUCH_L) if (item->TestBits(JointBitType::Touch, DragonSwipeAttackJointsLeft))
{ {
DoDamage(creature->Enemy, DRAGON_SWIPE_ATTACK_DAMAGE);
creature->Flags = 0; creature->Flags = 0;
DoDamage(creature->Enemy, DRAGON_SWIPE_DAMAGE);
} }
break; break;
case DRAGON_STATE_SWIPE_RIGHT: case DRAGON_STATE_SWIPE_RIGHT:
if (item->TouchBits & DRAGON_TOUCH_R) if (item->TestBits(JointBitType::Touch, DragonSwipeAttackJointsRight))
{ {
DoDamage(creature->Enemy, DRAGON_SWIPE_ATTACK_DAMAGE);
creature->Flags = 0; creature->Flags = 0;
DoDamage(creature->Enemy, DRAGON_SWIPE_DAMAGE);
} }
break; break;
@ -404,13 +397,11 @@ namespace TEN::Entities::TR2
case DRAGON_STATE_TURN_LEFT: case DRAGON_STATE_TURN_LEFT:
item->Pose.Orientation.y += -(ANGLE(1.0f) - angle); item->Pose.Orientation.y += -(ANGLE(1.0f) - angle);
creature->Flags = 0; creature->Flags = 0;
break; break;
case DRAGON_STATE_TURN_RIGHT: case DRAGON_STATE_TURN_RIGHT:
item->Pose.Orientation.y += (ANGLE(1.0f) - angle); item->Pose.Orientation.y += (ANGLE(1.0f) - angle);
creature->Flags = 0; creature->Flags = 0;
break; break;
case DRAGON_STATE_AIM_1: case DRAGON_STATE_AIM_1:
@ -434,12 +425,11 @@ namespace TEN::Entities::TR2
case DRAGON_STATE_FIRE_1: case DRAGON_STATE_FIRE_1:
item->Pose.Orientation.y -= angle; item->Pose.Orientation.y -= angle;
SoundEffect(SFX_TR2_DRAGON_FIRE, &item->Pose);
if (AI.ahead) if (AI.ahead)
head = -AI.angle; head = -AI.angle;
SoundEffect(SFX_TR2_DRAGON_FIRE, &item->Pose);
if (creature->Flags) if (creature->Flags)
{ {
if (AI.ahead) if (AI.ahead)
@ -459,12 +449,7 @@ namespace TEN::Entities::TR2
back->Animation.ActiveState = item->Animation.ActiveState; back->Animation.ActiveState = item->Animation.ActiveState;
back->Animation.AnimNumber = Objects[ID_DRAGON_BACK].animIndex + (item->Animation.AnimNumber - Objects[ID_DRAGON_FRONT].animIndex); back->Animation.AnimNumber = Objects[ID_DRAGON_BACK].animIndex + (item->Animation.AnimNumber - Objects[ID_DRAGON_FRONT].animIndex);
back->Animation.FrameNumber = g_Level.Anims[back->Animation.AnimNumber].frameBase + (item->Animation.FrameNumber - g_Level.Anims[item->Animation.AnimNumber].frameBase); back->Animation.FrameNumber = g_Level.Anims[back->Animation.AnimNumber].frameBase + (item->Animation.FrameNumber - g_Level.Anims[item->Animation.AnimNumber].frameBase);
back->Pose.Position.x = item->Pose.Position.x; back->Pose = item->Pose;
back->Pose.Position.y = item->Pose.Position.y;
back->Pose.Position.z = item->Pose.Position.z;
back->Pose.Orientation.x = item->Pose.Orientation.x;
back->Pose.Orientation.y = item->Pose.Orientation.y;
back->Pose.Orientation.z = item->Pose.Orientation.z;
if (back->RoomNumber != item->RoomNumber) if (back->RoomNumber != item->RoomNumber)
ItemNewRoom(backItemNumber, item->RoomNumber); ItemNewRoom(backItemNumber, item->RoomNumber);
@ -484,9 +469,7 @@ namespace TEN::Entities::TR2
{ {
auto* back = &g_Level.Items[backItem]; auto* back = &g_Level.Items[backItem];
back->ObjectNumber = ID_DRAGON_BACK; back->ObjectNumber = ID_DRAGON_BACK;
back->Pose.Position.x = item->Pose.Position.x; back->Pose.Position = item->Pose.Position;
back->Pose.Position.y = item->Pose.Position.y;
back->Pose.Position.z = item->Pose.Position.z;
back->Pose.Orientation.y = item->Pose.Orientation.y; back->Pose.Orientation.y = item->Pose.Orientation.y;
back->RoomNumber = item->RoomNumber; back->RoomNumber = item->RoomNumber;
back->Status = ITEM_INVISIBLE; back->Status = ITEM_INVISIBLE;
@ -500,9 +483,7 @@ namespace TEN::Entities::TR2
auto* front = &g_Level.Items[frontItem]; auto* front = &g_Level.Items[frontItem];
front->ObjectNumber = ID_DRAGON_FRONT; front->ObjectNumber = ID_DRAGON_FRONT;
front->Pose.Position.x = item->Pose.Position.x; front->Pose.Position = item->Pose.Position;
front->Pose.Position.y = item->Pose.Position.y;
front->Pose.Position.z = item->Pose.Position.z;
front->Pose.Orientation.y = item->Pose.Orientation.y; front->Pose.Orientation.y = item->Pose.Orientation.y;
front->RoomNumber = item->RoomNumber; front->RoomNumber = item->RoomNumber;
front->Status = ITEM_INVISIBLE; front->Status = ITEM_INVISIBLE;

View file

@ -2,7 +2,7 @@
#include "Game/collision/collide_room.h" #include "Game/collision/collide_room.h"
#include "Game/items.h" #include "Game/items.h"
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void DragonCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); void DragonCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);
void DragonControl(short backNumber); void DragonControl(short backNumber);

View file

@ -10,7 +10,7 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
const auto EagleBite = BiteInfo(Vector3(15.0f, 46.0f, 21.0f), 6); const auto EagleBite = BiteInfo(Vector3(15.0f, 46.0f, 21.0f), 6);
const auto CrowBite = BiteInfo(Vector3(2.0f, 10.0f, 60.0f), 14); const auto CrowBite = BiteInfo(Vector3(2.0f, 10.0f, 60.0f), 14);

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void InitialiseEagle(short itemNumber); void InitialiseEagle(short itemNumber);
void EagleControl(short itemNumber); void EagleControl(short itemNumber);

View file

@ -18,7 +18,7 @@
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
constexpr auto KNIFE_PROJECTILE_DAMAGE = 50; constexpr auto KNIFE_PROJECTILE_DAMAGE = 50;
@ -29,7 +29,7 @@ namespace TEN::Entities::TR2
enum KnifeThrowerState enum KnifeThrowerState
{ {
KTHROWER_STATE_NONE = 0, // No state 0.
KTHROWER_STATE_IDLE = 1, KTHROWER_STATE_IDLE = 1,
KTHROWER_STATE_WALK_FORWARD = 2, KTHROWER_STATE_WALK_FORWARD = 2,
KTHROWER_STATE_RUN_FORWARD = 3, KTHROWER_STATE_RUN_FORWARD = 3,
@ -101,7 +101,7 @@ namespace TEN::Entities::TR2
fx->pos.Orientation.z += ANGLE(30.0f); fx->pos.Orientation.z += ANGLE(30.0f);
if (ItemNearLara(&fx->pos, 200)) if (ItemNearLara(&fx->pos.Position, 200))
{ {
DoDamage(LaraItem, KNIFE_PROJECTILE_DAMAGE); DoDamage(LaraItem, KNIFE_PROJECTILE_DAMAGE);

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void KnifeControl(short fxNumber); void KnifeControl(short fxNumber);
void KnifeThrowerControl(short itemNumber); void KnifeThrowerControl(short itemNumber);

View file

@ -11,7 +11,7 @@
#include "Specific/setup.h" #include "Specific/setup.h"
#include "Math/Math.h" #include "Math/Math.h"
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
const auto MercenaryUziBite = BiteInfo(Vector3(0.0f, 150.0f, 19.0f), 17); const auto MercenaryUziBite = BiteInfo(Vector3(0.0f, 150.0f, 19.0f), 17);
const auto MercenaryAutoPistolBite = BiteInfo(Vector3(0.0f, 230.0f, 9.0f), 17); const auto MercenaryAutoPistolBite = BiteInfo(Vector3(0.0f, 230.0f, 9.0f), 17);

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void MercenaryUziControl(short itemNumber); void MercenaryUziControl(short itemNumber);
void MercenaryAutoPistolControl(short itemNumber); void MercenaryAutoPistolControl(short itemNumber);

View file

@ -11,7 +11,7 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
const auto MonkBite = BiteInfo(Vector3(-23.0f, 16.0f, 265.0f), 14); const auto MonkBite = BiteInfo(Vector3(-23.0f, 16.0f, 265.0f), 14);

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void MonkControl(short itemNumber); void MonkControl(short itemNumber);
} }

View file

@ -14,7 +14,7 @@
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
constexpr auto RAT_ATTACK_DAMAGE = 20; constexpr auto RAT_ATTACK_DAMAGE = 20;
constexpr auto RAT_ATTACK_RANGE = SQUARE(CLICK(0.7f)); constexpr auto RAT_ATTACK_RANGE = SQUARE(CLICK(0.7f));
@ -29,7 +29,7 @@ namespace TEN::Entities::TR2
enum RatState enum RatState
{ {
RAT_STATE_NONE = 0, // No state 0.
RAT_STATE_WALK_FORWARD = 1, RAT_STATE_WALK_FORWARD = 1,
RAT_STATE_IDLE = 2, RAT_STATE_IDLE = 2,
RAT_STATE_SQUEAK = 3, RAT_STATE_SQUEAK = 3,

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void RatControl(short itemNumber); void RatControl(short itemNumber);
} }

View file

@ -11,9 +11,14 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
namespace TEN::Entities::TR2 using std::vector;
namespace TEN::Entities::Creatures::TR2
{ {
constexpr auto SHARK_BITE_ATTACK_DAMAGE = 400;
const auto SharkBite = BiteInfo(Vector3(17.0f, -22.0f, 344.0f), 12); const auto SharkBite = BiteInfo(Vector3(17.0f, -22.0f, 344.0f), 12);
const vector<int> SharkBiteAttackJoints = { 10, 12, 13 };
void SharkControl(short itemNumber) void SharkControl(short itemNumber)
{ {
@ -21,7 +26,7 @@ namespace TEN::Entities::TR2
return; return;
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* info = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short angle = 0; short angle = 0;
short head = 0; short head = 0;
@ -29,11 +34,7 @@ namespace TEN::Entities::TR2
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
if (item->Animation.ActiveState != 5) if (item->Animation.ActiveState != 5)
{ SetAnimation(item, 4);
item->Animation.AnimNumber = Objects[ID_SHARK].animIndex + 4;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 5;
}
CreatureFloat(itemNumber); CreatureFloat(itemNumber);
return; return;
@ -46,39 +47,40 @@ namespace TEN::Entities::TR2
GetCreatureMood(item, &AI, true); GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true); CreatureMood(item, &AI, true);
angle = CreatureTurn(item, info->MaxTurn); angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState) switch (item->Animation.ActiveState)
{ {
case 0: case 0:
info->Flags = 0; creature->MaxTurn = 0;
info->MaxTurn = 0; creature->Flags = 0;
if (AI.ahead && AI.distance < pow(SECTOR(0.75f), 2) && AI.zoneNumber == AI.enemyZone) if (AI.ahead && AI.distance < pow(SECTOR(0.75f), 2) && AI.zoneNumber == AI.enemyZone)
item->Animation.TargetState = 3; item->Animation.TargetState = 3;
else else
item->Animation.TargetState = 1; item->Animation.TargetState = 1;
break; break;
case 1: case 1:
info->MaxTurn = ANGLE(0.5f); creature->MaxTurn = ANGLE(0.5f);
if (info->Mood == MoodType::Bored) if (creature->Mood == MoodType::Bored)
break; break;
else if (AI.ahead && AI.distance < pow(SECTOR(0.75f), 2)) else if (AI.ahead && AI.distance < pow(SECTOR(0.75f), 2))
item->Animation.TargetState = 0; item->Animation.TargetState = 0;
else if (info->Mood == MoodType::Escape || AI.distance > pow(SECTOR(3), 2) || !AI.ahead) else if (creature->Mood == MoodType::Escape || AI.distance > pow(SECTOR(3), 2) || !AI.ahead)
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
break; break;
case 2: case 2:
info->MaxTurn = ANGLE(2.0f); creature->MaxTurn = ANGLE(2.0f);
info->Flags = 0; creature->Flags = 0;
if (info->Mood == MoodType::Bored) if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 1; item->Animation.TargetState = 1;
else if (info->Mood == MoodType::Escape) else if (creature->Mood == MoodType::Escape)
break; break;
else if (AI.ahead && AI.distance < pow(1365, 2) && AI.zoneNumber == AI.enemyZone) else if (AI.ahead && AI.distance < pow(1365, 2) && AI.zoneNumber == AI.enemyZone)
{ {
@ -95,11 +97,11 @@ namespace TEN::Entities::TR2
if (AI.ahead) if (AI.ahead)
head = AI.angle; head = AI.angle;
if (!info->Flags && item->TouchBits & 0x3400) if (!creature->Flags && item->TestBits(JointBitType::Touch, SharkBiteAttackJoints))
{ {
DoDamage(creature->Enemy, SHARK_BITE_ATTACK_DAMAGE);
CreatureEffect(item, SharkBite, DoBloodSplat); CreatureEffect(item, SharkBite, DoBloodSplat);
DoDamage(info->Enemy, 400); creature->Flags = 1;
info->Flags = 1;
} }
break; break;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void SharkControl(short itemNumber); void SharkControl(short itemNumber);
} }

View file

@ -10,20 +10,67 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
namespace TEN::Entities::TR2 using namespace TEN::Math::Random;
namespace TEN::Entities::Creatures::TR2
{ {
constexpr auto SILENCER_SHOOT_ATTACK_DAMAGE = 50;
constexpr auto SILENCER_RUN_RANGE = SQUARE(SECTOR(2));
const auto SilencerGunBite = BiteInfo(Vector3(3.0f, 331.0f, 56.0f), 10); const auto SilencerGunBite = BiteInfo(Vector3(3.0f, 331.0f, 56.0f), 10);
// TODO #define SILENCER_WALK_TURN_RATE_MAX ANGLE(5.0f)
#define SILENCER_RUN_TURN_RATE_MAX ANGLE(5.0f)
enum SilencerState enum SilencerState
{ {
// No state 0.
SILENCER_STATE_WALK_FORWARD = 1,
SILENCER_STATE_RUN_FORWARD = 2,
SILENCER_STATE_IDLE_FRAME = 3,
SILENCER_STATE_IDLE = 4,
SILENCER_STATE_POSE = 5,
SILENCER_STATE_AIM_1 = 6,
SILENCER_STATE_SHOOT_1 = 7,
// No state 8.
SILENCER_STATE_RUN_SHOOT = 9,
SILENCER_STATE_AIM_2 = 10,
SILENCER_STATE_SHOOT_2 = 11,
SILENCER_STATE_DEATH_1 = 12,
SILENCER_STATE_DEATH_2 = 13
}; };
// TODO
enum SilencerAnim enum SilencerAnim
{ {
SILENCER_ANIM_IDLE_FRAME = 0,
SILENCER_ANIM_IDLE_TO_WALK_FORWARD = 1,
SILENCER_ANIM_WALK_FORWARD = 2,
SILENCER_ANIM_WALK_FORWARD_TO_IDLE = 3,
SILENCER_ANIM_WALK_FORWARD_TO_POSE = 4,
SILENCER_ANIM_POSE = 5,
SILENCER_ANIM_WALK_FORWARD_TO_RUN_FORWARD = 6,
SILENCER_ANIM_RUN_FORWARD = 7,
SILENCER_ANIM_RUN_FORWARD_TO_IDLE = 8,
SILENCER_ANIM_IDLE_TO_RUN_FORWARD = 9,
SILENCER_ANIM_POSE_TO_IDLE = 10,
SILENCER_ANIM_RUN_FORWARD_AIM_LEFT = 11,
SILENCER_ANIM_RUN_FORWARD_SHOOT_LEFT = 12,
SILENCER_ANIM_RUN_FORWARD_UNAIM_LEFT = 13,
SILENCER_ANIM_AIM_1_START = 14,
SILENCER_ANIM_AIM_1_CONTINUE = 15,
SILENCER_ANIM_SHOOT_1 = 16,
SILENCER_ANIM_UNAIM_1 = 17,
SILENCER_ANIM_POSE_TO_AIM_1 = 18,
SILENCER_ANIM_IDLE = 19,
SILENCER_ANIM_DEATH_1 = 20,
SILENCER_ANIM_DEATH_2 = 21, // Unused.
SILENCER_ANIM_AIM_2_START = 22,
SILENCER_ANIM_AIM_2_CONTINUE = 23,
SILENCER_ANIM_SHOOT_2 = 24,
SILENCER_ANIM_UNAIM_2 = 25,
SILENCER_ANIM_RUN_FORWARD_AIM_RIGHT = 26,
SILENCER_ANIM_RUN_FORWARD_SHOOT_RIGHT = 27,
SILENCER_ANIM_RUN_FORWARD_UNAIM_RIGHT = 28
}; };
void SilencerControl(short itemNumber) void SilencerControl(short itemNumber)
@ -32,21 +79,19 @@ namespace TEN::Entities::TR2
return; return;
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* info = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short angle = 0; short angle = 0;
short torsoX = 0;
short torsoY = 0;
short head = 0;
short tilt = 0; short tilt = 0;
auto extraHeadRot = EulerAngles::Zero;
auto extraTorsoRot = EulerAngles::Zero;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
if (item->Animation.ActiveState != 12 && item->Animation.ActiveState != 13) if (item->Animation.ActiveState != SILENCER_STATE_DEATH_1 &&
item->Animation.ActiveState != SILENCER_STATE_DEATH_2)
{ {
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 20; SetAnimation(item, SILENCER_ANIM_DEATH_1);
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 13;
} }
} }
else else
@ -57,205 +102,207 @@ namespace TEN::Entities::TR2
GetCreatureMood(item, &AI, true); GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true); CreatureMood(item, &AI, true);
angle = CreatureTurn(item, info->MaxTurn); angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState) switch (item->Animation.ActiveState)
{ {
case 3: case SILENCER_STATE_IDLE_FRAME:
creature->MaxTurn = 0;
if (AI.ahead) if (AI.ahead)
head = AI.angle; extraHeadRot.y = AI.angle;
info->MaxTurn = 0;
if (item->Animation.RequiredState) if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState; item->Animation.TargetState = item->Animation.RequiredState;
break; break;
case 4: case SILENCER_STATE_IDLE:
if (AI.ahead) creature->MaxTurn = 0;
head = AI.angle;
info->MaxTurn = 0;
if (info->Mood == MoodType::Escape) if (AI.ahead)
extraHeadRot.y = AI.angle;
if (creature->Mood == MoodType::Escape)
{ {
item->Animation.RequiredState = 2; item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
item->Animation.TargetState = 3; item->Animation.RequiredState = SILENCER_STATE_RUN_FORWARD;
} }
else else
{ {
if (Targetable(item, &AI)) if (Targetable(item, &AI))
{ {
item->Animation.RequiredState = (GetRandomControl() >= 0x4000 ? 10 : 6); item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
item->Animation.TargetState = 3; item->Animation.RequiredState = TestProbability(0.5f) ? SILENCER_STATE_AIM_1 : SILENCER_STATE_AIM_2;
} }
if (info->Mood == MoodType::Attack || !AI.ahead) if (creature->Mood == MoodType::Attack || !AI.ahead)
{ {
if (AI.distance >= pow(SECTOR(2), 2)) if (AI.distance >= SILENCER_RUN_RANGE)
{ {
item->Animation.RequiredState = 2; item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
item->Animation.TargetState = 3; item->Animation.RequiredState = SILENCER_STATE_RUN_FORWARD;
} }
else else
{ {
item->Animation.RequiredState = 1; item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
item->Animation.TargetState = 3; item->Animation.RequiredState = SILENCER_STATE_WALK_FORWARD;
} }
} }
else else
{ {
if (GetRandomControl() >= 1280) if (TestProbability(0.96f))
{ {
if (GetRandomControl() < 2560) if (TestProbability(0.08f))
{ {
item->Animation.RequiredState = 1; item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
item->Animation.TargetState = 3; item->Animation.RequiredState = SILENCER_STATE_WALK_FORWARD;
} }
} }
else else
{ {
item->Animation.RequiredState = 5; item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
item->Animation.TargetState = 3; item->Animation.RequiredState = SILENCER_STATE_POSE;
} }
} }
} }
break; break;
case 1: case SILENCER_STATE_WALK_FORWARD:
creature->MaxTurn = SILENCER_WALK_TURN_RATE_MAX;
if (AI.ahead) if (AI.ahead)
head = AI.angle; extraHeadRot.y = AI.angle;
info->MaxTurn = 910; if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = SILENCER_STATE_RUN_FORWARD;
if (info->Mood == MoodType::Escape)
item->Animation.TargetState = 2;
else if (Targetable(item, &AI)) else if (Targetable(item, &AI))
{ {
item->Animation.RequiredState = (GetRandomControl() >= 0x4000 ? 10 : 6); item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
item->Animation.TargetState = 3; item->Animation.RequiredState = TestProbability(0.5f) ? SILENCER_STATE_AIM_1 : SILENCER_STATE_AIM_2;
} }
else else
{ {
if (AI.distance > pow(SECTOR(2), 2) || !AI.ahead) if (AI.distance > SILENCER_RUN_RANGE || !AI.ahead)
item->Animation.TargetState = 2; item->Animation.TargetState = SILENCER_STATE_RUN_FORWARD;
if (info->Mood == MoodType::Bored && GetRandomControl() < 0x300) if (creature->Mood == MoodType::Bored && TestProbability(0.025f))
item->Animation.TargetState = 3; item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
} }
break; break;
case 2: case SILENCER_STATE_RUN_FORWARD:
if (AI.ahead) creature->MaxTurn = SILENCER_RUN_TURN_RATE_MAX;
head = AI.angle; creature->Flags = 0;
info->MaxTurn = ANGLE(5.0f);
info->Flags = 0;
tilt = angle / 4; tilt = angle / 4;
if (info->Mood == MoodType::Escape) if (AI.ahead)
extraHeadRot.y = AI.angle;
if (creature->Mood == MoodType::Escape)
{ {
if (Targetable(item, &AI)) if (Targetable(item, &AI))
item->Animation.TargetState = 9; item->Animation.TargetState = SILENCER_STATE_RUN_SHOOT;
break; break;
} }
if (Targetable(item, &AI)) if (Targetable(item, &AI))
{ {
if (AI.distance >= pow(SECTOR(2), 2) && AI.zoneNumber == AI.enemyZone) if (AI.distance >= SILENCER_RUN_RANGE && AI.zoneNumber == AI.enemyZone)
item->Animation.TargetState = 9; item->Animation.TargetState = SILENCER_STATE_RUN_SHOOT;
break; break;
} }
else if (info->Mood == MoodType::Attack) else if (creature->Mood == MoodType::Attack)
item->Animation.TargetState = (GetRandomControl() >= 0x4000) ? 3 : 2; item->Animation.TargetState = TestProbability(0.5f) ? SILENCER_STATE_RUN_FORWARD : SILENCER_STATE_IDLE_FRAME;
else else
item->Animation.TargetState = 3; item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
break; break;
case 5: case SILENCER_STATE_POSE:
if (AI.ahead) creature->MaxTurn = 0;
head = AI.angle;
info->MaxTurn = 0; if (AI.ahead)
extraHeadRot.y = AI.angle;
if (Targetable(item, &AI)) if (Targetable(item, &AI))
{ {
item->Animation.RequiredState = 6; item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
item->Animation.TargetState = 3; item->Animation.RequiredState = SILENCER_STATE_AIM_1;
} }
else else
{ {
if (info->Mood == MoodType::Attack || GetRandomControl() < 0x100) if (creature->Mood == MoodType::Attack || TestProbability(1.0f / 128))
item->Animation.TargetState = 3; item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
if (!AI.ahead) if (!AI.ahead)
item->Animation.TargetState = 3; item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
} }
break; break;
case 6: case SILENCER_STATE_AIM_1:
case 10: case SILENCER_STATE_AIM_2:
info->MaxTurn = 0; creature->MaxTurn = 0;
info->Flags = 0; creature->Flags = 0;
if (AI.ahead) if (AI.ahead)
{ {
torsoY = AI.angle; extraTorsoRot.x = AI.xAngle;
torsoX = AI.xAngle; extraTorsoRot.y = AI.angle;
} }
else else
head = AI.angle; extraHeadRot.y = AI.angle;
if (info->Mood == MoodType::Escape) if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 3; item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
else if (Targetable(item, &AI)) else if (Targetable(item, &AI))
item->Animation.TargetState = item->Animation.ActiveState != 6 ? 11 : 7; item->Animation.TargetState = (item->Animation.ActiveState != SILENCER_STATE_AIM_1) ? SILENCER_STATE_SHOOT_2 : SILENCER_STATE_SHOOT_1;
else else
item->Animation.TargetState = 3; item->Animation.TargetState = SILENCER_STATE_IDLE_FRAME;
break; break;
case 7: case SILENCER_STATE_SHOOT_1:
case 11: case SILENCER_STATE_SHOOT_2:
info->MaxTurn = 0; creature->MaxTurn = 0;
if (AI.ahead) if (AI.ahead)
{ {
torsoY = AI.angle; extraTorsoRot.x = AI.xAngle;
torsoX = AI.xAngle; extraTorsoRot.y = AI.angle;
} }
else else
head = AI.angle; extraHeadRot.y = AI.angle;
if (!info->Flags) if (!creature->Flags)
{ {
ShotLara(item, &AI, SilencerGunBite, torsoY, 50); ShotLara(item, &AI, SilencerGunBite, extraTorsoRot.y, SILENCER_SHOOT_ATTACK_DAMAGE);
info->Flags = 1; creature->Flags = 1;
} }
break; break;
case 9: case SILENCER_STATE_RUN_SHOOT:
info->MaxTurn = ANGLE(5.0f); creature->MaxTurn = SILENCER_RUN_TURN_RATE_MAX;
if (AI.ahead) if (AI.ahead)
{ {
torsoY = AI.angle; extraTorsoRot.x = AI.xAngle;
torsoX = AI.xAngle; extraTorsoRot.y = AI.angle;
} }
else else
head = AI.angle; extraHeadRot.y = AI.angle;
if (!item->Animation.RequiredState) if (!item->Animation.RequiredState)
{ {
if (!ShotLara(item, &AI, SilencerGunBite, torsoY, 50)) if (!ShotLara(item, &AI, SilencerGunBite, extraTorsoRot.y, SILENCER_SHOOT_ATTACK_DAMAGE))
item->Animation.TargetState = 2; item->Animation.TargetState = SILENCER_STATE_RUN_FORWARD;
item->Animation.RequiredState = 9; item->Animation.RequiredState = SILENCER_STATE_RUN_SHOOT;
} }
break; break;
@ -263,9 +310,9 @@ namespace TEN::Entities::TR2
} }
CreatureTilt(item, tilt); CreatureTilt(item, tilt);
CreatureJoint(item, 0, torsoY); CreatureJoint(item, 0, extraTorsoRot.y);
CreatureJoint(item, 1, torsoX); CreatureJoint(item, 1, extraTorsoRot.x);
CreatureJoint(item, 2, head); CreatureJoint(item, 2, extraHeadRot.y);
CreatureAnimation(itemNumber, angle, tilt); CreatureAnimation(itemNumber, angle, tilt);
} }
} }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void SilencerControl(short itemNumber); void SilencerControl(short itemNumber);
} }

View file

@ -17,7 +17,7 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
#define SMAN_MIN_TURN (ANGLE(2.0f)) #define SMAN_MIN_TURN (ANGLE(2.0f))
#define SMAN_TARGET_ANGLE ANGLE(15.0f) #define SMAN_TARGET_ANGLE ANGLE(15.0f)
@ -28,7 +28,7 @@ namespace TEN::Entities::TR2
enum SnowmobileManState enum SnowmobileManState
{ {
SMAN_STATE_NONE = 0, // No state 0.
SMAN_STATE_WAIT = 1, SMAN_STATE_WAIT = 1,
SMAN_STATE_MOVING = 2, SMAN_STATE_MOVING = 2,
SMAN_STATE_START_LEFT = 3, SMAN_STATE_START_LEFT = 3,

View file

@ -2,7 +2,7 @@
#include "Game/collision/collide_room.h" #include "Game/collision/collide_room.h"
#include "Game/items.h" #include "Game/items.h"
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void InitialiseSkidooMan(short itemNumber); void InitialiseSkidooMan(short itemNumber);
void SkidooManCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); void SkidooManCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);

View file

@ -12,7 +12,9 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
namespace TEN::Entities::TR2 using namespace TEN::Math::Random;
namespace TEN::Entities::Creatures::TR2
{ {
const auto SpearBiteLeft = BiteInfo(Vector3(0.0f, 0.0f, 920.0f), 11); const auto SpearBiteLeft = BiteInfo(Vector3(0.0f, 0.0f, 920.0f), 11);
const auto SpearBiteRight = BiteInfo(Vector3(0.0f, 0.0f, 920.0f), 18); const auto SpearBiteRight = BiteInfo(Vector3(0.0f, 0.0f, 920.0f), 18);
@ -29,7 +31,7 @@ namespace TEN::Entities::TR2
}; };
static void XianDamage(ItemInfo* item, int damage) void XianDamage(ItemInfo* item, int damage)
{ {
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
@ -76,7 +78,7 @@ namespace TEN::Entities::TR2
short neck = 0; short neck = 0;
short tilt = 0; short tilt = 0;
bool laraAlive = LaraItem->HitPoints > 0; bool isLaraAlive = LaraItem->HitPoints > 0;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
@ -125,10 +127,9 @@ namespace TEN::Entities::TR2
if (creature->Mood == MoodType::Bored) if (creature->Mood == MoodType::Bored)
{ {
int random = GetRandomControl(); if (TestProbability(1.0f / 64))
if (random < 0x200)
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
else if (random < 0x400) else if (TestProbability(1.0f / 30))
item->Animation.TargetState = 3; item->Animation.TargetState = 3;
} }
else if (AI.ahead && AI.distance < pow(SECTOR(1), 2)) else if (AI.ahead && AI.distance < pow(SECTOR(1), 2))
@ -148,10 +149,9 @@ namespace TEN::Entities::TR2
item->Animation.TargetState = 3; item->Animation.TargetState = 3;
else if (creature->Mood == MoodType::Bored) else if (creature->Mood == MoodType::Bored)
{ {
int random = GetRandomControl(); if (TestProbability(1.0f / 64))
if (random < 0x200)
item->Animation.TargetState = 1; item->Animation.TargetState = 1;
else if (random < 0x400) else if (TestProbability(1.0f / 30))
item->Animation.TargetState = 3; item->Animation.TargetState = 3;
} }
else if (AI.ahead && AI.distance < pow(SECTOR(1), 2)) else if (AI.ahead && AI.distance < pow(SECTOR(1), 2))
@ -171,17 +171,16 @@ namespace TEN::Entities::TR2
item->Animation.TargetState = 4; item->Animation.TargetState = 4;
else if (creature->Mood == MoodType::Bored) else if (creature->Mood == MoodType::Bored)
{ {
int random = GetRandomControl(); if (TestProbability(1.0f / 64))
if (random < 0x200)
item->Animation.TargetState = 1; item->Animation.TargetState = 1;
else if (random < 0x400) else if (TestProbability(1.0f / 30))
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
} }
else if (AI.ahead && AI.distance < pow(SECTOR(2), 2)) else if (AI.ahead && AI.distance < pow(SECTOR(2), 2))
{ {
if (AI.distance < pow(SECTOR(1.5f), 2)) if (AI.distance < pow(SECTOR(1.5f), 2))
item->Animation.TargetState = 7; item->Animation.TargetState = 7;
else if (GetRandomControl() < 0x4000) else if (TestProbability(0.5f))
item->Animation.TargetState = 9; item->Animation.TargetState = 9;
else else
item->Animation.TargetState = 11; item->Animation.TargetState = 11;
@ -201,7 +200,7 @@ namespace TEN::Entities::TR2
break; break;
else if (creature->Mood == MoodType::Bored) else if (creature->Mood == MoodType::Bored)
{ {
if (GetRandomControl() < 0x4000) if (TestProbability(0.5f))
item->Animation.TargetState = 1; item->Animation.TargetState = 1;
else else
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
@ -301,7 +300,7 @@ namespace TEN::Entities::TR2
if (AI.ahead && AI.distance < pow(SECTOR(1), 2)) if (AI.ahead && AI.distance < pow(SECTOR(1), 2))
{ {
if (GetRandomControl() < 0x4000) if (TestProbability(0.5f))
item->Animation.TargetState = 1; item->Animation.TargetState = 1;
else else
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
@ -332,7 +331,7 @@ namespace TEN::Entities::TR2
if (AI.ahead && AI.distance < pow(SECTOR(1), 2)) if (AI.ahead && AI.distance < pow(SECTOR(1), 2))
{ {
if (GetRandomControl() < 0x4000) if (TestProbability(0.5f))
item->Animation.TargetState = 1; item->Animation.TargetState = 1;
else else
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
@ -346,7 +345,7 @@ namespace TEN::Entities::TR2
} }
} }
if (laraAlive && LaraItem->HitPoints <= 0) if (isLaraAlive && LaraItem->HitPoints <= 0)
{ {
CreatureKill(item, 49, 19, 2); CreatureKill(item, 49, 19, 2);
return; return;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void InitialiseSpearGuardian(short itemNumber); void InitialiseSpearGuardian(short itemNumber);
void SpearGuardianControl(short itemNumber); void SpearGuardianControl(short itemNumber);

View file

@ -14,17 +14,17 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
const auto SpiderBite = BiteInfo(Vector3(0.0f, 0.0f, 41.0f), 1); const auto SpiderBite = BiteInfo(Vector3(0.0f, 0.0f, 41.0f), 1);
static void S_SpiderBite(ItemInfo* item) void S_SpiderBite(ItemInfo* item)
{ {
auto pos = GetJointPosition(item, SpiderBite.meshNum, Vector3i(SpiderBite.Position)); auto pos = GetJointPosition(item, SpiderBite.meshNum, Vector3i(SpiderBite.Position));
DoBloodSplat(pos.x, pos.y, pos.z, 10, item->Pose.Position.y, item->RoomNumber); DoBloodSplat(pos.x, pos.y, pos.z, 10, item->Pose.Position.y, item->RoomNumber);
} }
static void SpiderLeap(short itemNumber, ItemInfo* item, short angle) void SpiderLeap(short itemNumber, ItemInfo* item, short angle)
{ {
auto vec = GameVector( auto vec = GameVector(
item->Pose.Position.x, item->Pose.Position.x,
@ -38,9 +38,7 @@ namespace TEN::Entities::TR2
if (item->Pose.Position.y > (vec.y - CLICK(1.5f))) if (item->Pose.Position.y > (vec.y - CLICK(1.5f)))
return; return;
item->Pose.Position.x = vec.x; item->Pose.Position = Vector3i(vec.x, vec.y, vec.z);
item->Pose.Position.y = vec.y;
item->Pose.Position.z = vec.z;
if (item->RoomNumber != vec.roomNumber) if (item->RoomNumber != vec.roomNumber)
ItemNewRoom(item->RoomNumber, vec.roomNumber); ItemNewRoom(item->RoomNumber, vec.roomNumber);

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void SmallSpiderControl(short itemNumber); void SmallSpiderControl(short itemNumber);
void BigSpiderControl(short itemNumber); void BigSpiderControl(short itemNumber);

View file

@ -13,7 +13,9 @@
#include "Sound/sound.h" #include "Sound/sound.h"
#include "Specific/level.h" #include "Specific/level.h"
namespace TEN::Entities::TR2 using namespace TEN::Math::Random;
namespace TEN::Entities::Creatures::TR2
{ {
const auto SwordBite = BiteInfo(Vector3(0.0f, 37.0f, 550.0f), 15); const auto SwordBite = BiteInfo(Vector3(0.0f, 37.0f, 550.0f), 15);
@ -24,7 +26,7 @@ namespace TEN::Entities::TR2
ClearItem(itemNumber); ClearItem(itemNumber);
} }
static void SwordGuardianFly(ItemInfo* item) void SwordGuardianFly(ItemInfo* item)
{ {
Vector3i pos; Vector3i pos;
pos.x = (GetRandomControl() * 256 / 32768) + item->Pose.Position.x - 128; pos.x = (GetRandomControl() * 256 / 32768) + item->Pose.Position.x - 128;
@ -47,7 +49,7 @@ namespace TEN::Entities::TR2
short head = 0; short head = 0;
short torso = 0; short torso = 0;
bool laraAlive = LaraItem->HitPoints > 0; bool isLaraAlive = LaraItem->HitPoints > 0;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
@ -68,7 +70,7 @@ namespace TEN::Entities::TR2
creature->LOT.Step = STEP_SIZE; creature->LOT.Step = STEP_SIZE;
creature->LOT.Drop = -STEP_SIZE; creature->LOT.Drop = -STEP_SIZE;
creature->LOT.Fly = NO_FLYING; creature->LOT.Fly = NO_FLYING;
creature->LOT.Zone = ZONE_BASIC; creature->LOT.Zone = ZoneType::Basic;
AI_INFO AI; AI_INFO AI;
CreatureAIInfo(item, &AI); CreatureAIInfo(item, &AI);
@ -80,7 +82,7 @@ namespace TEN::Entities::TR2
creature->LOT.Step = WALL_SIZE * 20; creature->LOT.Step = WALL_SIZE * 20;
creature->LOT.Drop = -WALL_SIZE * 20; creature->LOT.Drop = -WALL_SIZE * 20;
creature->LOT.Fly = STEP_SIZE / 4; creature->LOT.Fly = STEP_SIZE / 4;
creature->LOT.Zone = ZONE_FLYER; creature->LOT.Zone = ZoneType::Flyer;
CreatureAIInfo(item, &AI); CreatureAIInfo(item, &AI);
} }
} }
@ -114,15 +116,10 @@ namespace TEN::Entities::TR2
if (AI.ahead) if (AI.ahead)
head = AI.angle; head = AI.angle;
if (laraAlive) if (isLaraAlive)
{ {
if (AI.bite && AI.distance < pow(SECTOR(1), 2)) if (AI.bite && AI.distance < pow(SECTOR(1), 2))
{ item->Animation.TargetState = TestProbability(0.5f) ? 3 : 5;
if (GetRandomControl() >= 0x4000)
item->Animation.TargetState = 5;
else
item->Animation.TargetState = 3;
}
else else
{ {
if (AI.zoneNumber == AI.enemyZone) if (AI.zoneNumber == AI.enemyZone)
@ -142,7 +139,7 @@ namespace TEN::Entities::TR2
if (AI.ahead) if (AI.ahead)
head = AI.angle; head = AI.angle;
if (laraAlive) if (isLaraAlive)
{ {
if (AI.bite && AI.distance < pow(SECTOR(2), 2)) if (AI.bite && AI.distance < pow(SECTOR(2), 2))
item->Animation.TargetState = 10; item->Animation.TargetState = 10;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void InitialiseSwordGuardian(short itemNumber); void InitialiseSwordGuardian(short itemNumber);
void SwordGuardianControl(short itemNumber); void SwordGuardianControl(short itemNumber);

View file

@ -10,7 +10,7 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
const auto WorkerDualGunBiteLeft = BiteInfo(Vector3(-2.0f, 275.0f, 23.0f), 6); const auto WorkerDualGunBiteLeft = BiteInfo(Vector3(-2.0f, 275.0f, 23.0f), 6);
const auto WorkerDualGunBiteRight = BiteInfo(Vector3(2.0f, 275.0f, 23.0f), 10); const auto WorkerDualGunBiteRight = BiteInfo(Vector3(2.0f, 275.0f, 23.0f), 10);

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void WorkerDualGunControl(short itemNumber); void WorkerDualGunControl(short itemNumber);
} }

View file

@ -16,7 +16,7 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Math/Math.h" #include "Math/Math.h"
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
const auto WorkerFlamethrowerOffset = Vector3i(0, 140, 0); const auto WorkerFlamethrowerOffset = Vector3i(0, 140, 0);
const auto WorkerFlamethrowerBite = BiteInfo(Vector3(0.0f, 250.0f, 32.0f), 9); const auto WorkerFlamethrowerBite = BiteInfo(Vector3(0.0f, 250.0f, 32.0f), 9);

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void InitialiseWorkerFlamethrower(short itemNumber); void InitialiseWorkerFlamethrower(short itemNumber);
void WorkerFlamethrower(short itemNumber); void WorkerFlamethrower(short itemNumber);

View file

@ -11,7 +11,7 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
const auto WorkerMachineGunBite = BiteInfo(Vector3(0.0f, 308.0f, 32.0f), 9); const auto WorkerMachineGunBite = BiteInfo(Vector3(0.0f, 308.0f, 32.0f), 9);

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void InitialiseWorkerMachineGun(short itemNumber); void InitialiseWorkerMachineGun(short itemNumber);
void WorkerMachineGunControl(short itemNumber); void WorkerMachineGunControl(short itemNumber);

View file

@ -12,7 +12,7 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
const auto WorkerShotgunBite = BiteInfo(Vector3(0.0f, 281.0f, 40.0f), 9); const auto WorkerShotgunBite = BiteInfo(Vector3(0.0f, 281.0f, 40.0f), 9);

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void InitialiseWorkerShotgun(short itemNumber); void InitialiseWorkerShotgun(short itemNumber);
void WorkerShotgunControl(short itemNumber); void WorkerShotgunControl(short itemNumber);

View file

@ -13,11 +13,14 @@
#include "Specific/setup.h" #include "Specific/setup.h"
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
using std::vector;
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
const auto YetiBiteLeft = BiteInfo(Vector3(12.0f, 101.0f, 19.0f), 13); const auto YetiBiteLeft = BiteInfo(Vector3(12.0f, 101.0f, 19.0f), 13);
const auto YetiBiteRight = BiteInfo(Vector3(12.0f, 101.0f, 19.0f), 10); const auto YetiBiteRight = BiteInfo(Vector3(12.0f, 101.0f, 19.0f), 10);
const vector<int> YetiAttackJoints1 = { 10, 12 }; // TODO: Rename.
const vector<int> YetiAttackJoints2 = { 8, 9, 10 };
// TODO // TODO
enum YetiState enum YetiState
@ -50,9 +53,9 @@ namespace TEN::Entities::TR2
bool isLaraAlive = LaraItem->HitPoints > 0; bool isLaraAlive = LaraItem->HitPoints > 0;
short angle = 0; short angle = 0;
short tilt = 0;
short torso = 0; short torso = 0;
short head = 0; short head = 0;
short tilt = 0;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
@ -88,9 +91,9 @@ namespace TEN::Entities::TR2
item->Animation.TargetState = item->Animation.RequiredState; item->Animation.TargetState = item->Animation.RequiredState;
else if (info->Mood == MoodType::Bored) else if (info->Mood == MoodType::Bored)
{ {
if (TestProbability(0.008f) || !isLaraAlive) if (TestProbability(1.0f / 128) || !isLaraAlive)
item->Animation.TargetState = 7; item->Animation.TargetState = 7;
else if (TestProbability(0.015f)) else if (TestProbability(1.0f / 64))
item->Animation.TargetState = 9; item->Animation.TargetState = 9;
else if (TestProbability(0.025f)) else if (TestProbability(0.025f))
item->Animation.TargetState = 3; item->Animation.TargetState = 3;
@ -116,9 +119,9 @@ namespace TEN::Entities::TR2
{ {
if (isLaraAlive) if (isLaraAlive)
{ {
if (TestProbability(0.008f)) if (TestProbability(1.0f / 128))
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
else if (TestProbability(0.015f)) else if (TestProbability(1.0f / 64))
item->Animation.TargetState = 9; item->Animation.TargetState = 9;
else if (TestProbability(0.025f)) else if (TestProbability(0.025f))
{ {
@ -127,7 +130,7 @@ namespace TEN::Entities::TR2
} }
} }
} }
else if (TestProbability(0.015f)) else if (TestProbability(1.0f / 64))
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
break; break;
@ -140,9 +143,9 @@ namespace TEN::Entities::TR2
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
else if (info->Mood == MoodType::Bored) else if (info->Mood == MoodType::Bored)
{ {
if (TestProbability(0.008f) || !isLaraAlive) if (TestProbability(1.0f / 128) || !isLaraAlive)
item->Animation.TargetState = 7; item->Animation.TargetState = 7;
else if (TestProbability(0.015f)) else if (TestProbability(1.0f / 64))
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
else if (TestProbability(0.025f)) else if (TestProbability(0.025f))
{ {
@ -150,7 +153,7 @@ namespace TEN::Entities::TR2
item->Animation.RequiredState = 3; item->Animation.RequiredState = 3;
} }
} }
else if (TestProbability(0.015f)) else if (TestProbability(1.0f / 64))
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
break; break;
@ -165,12 +168,12 @@ namespace TEN::Entities::TR2
item->Animation.TargetState = 1; item->Animation.TargetState = 1;
else if (info->Mood == MoodType::Bored) else if (info->Mood == MoodType::Bored)
{ {
if (TestProbability(0.008f) || !isLaraAlive) if (TestProbability(1.0f / 128) || !isLaraAlive)
{ {
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
item->Animation.RequiredState = 7; item->Animation.RequiredState = 7;
} }
else if (TestProbability(0.015f)) else if (TestProbability(1.0f / 64))
{ {
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
item->Animation.RequiredState = 9; item->Animation.RequiredState = 9;
@ -213,8 +216,7 @@ namespace TEN::Entities::TR2
if (AI.ahead) if (AI.ahead)
torso = AI.angle; torso = AI.angle;
if (!info->Flags && if (!info->Flags && item->TestBits(JointBitType::Touch, YetiAttackJoints1))
item->TouchBits & 0x1400)
{ {
CreatureEffect(item, YetiBiteRight, DoBloodSplat); CreatureEffect(item, YetiBiteRight, DoBloodSplat);
DoDamage(info->Enemy, 100); DoDamage(info->Enemy, 100);
@ -230,11 +232,12 @@ namespace TEN::Entities::TR2
torso = AI.angle; torso = AI.angle;
if (!info->Flags && if (!info->Flags &&
item->TouchBits & (0x0700 | 0x1400)) (item->TestBits(JointBitType::Touch, YetiAttackJoints1) || item->TestBits(JointBitType::Touch, YetiAttackJoints2)))
{ {
if (item->TouchBits & 0x0700) if (item->TestBits(JointBitType::Touch, YetiAttackJoints2))
CreatureEffect(item, YetiBiteLeft, DoBloodSplat); CreatureEffect(item, YetiBiteLeft, DoBloodSplat);
if (item->TouchBits & 0x1400)
if (item->TestBits(JointBitType::Touch, YetiAttackJoints1))
CreatureEffect(item, YetiBiteRight, DoBloodSplat); CreatureEffect(item, YetiBiteRight, DoBloodSplat);
DoDamage(info->Enemy, 150); DoDamage(info->Enemy, 150);
@ -248,11 +251,12 @@ namespace TEN::Entities::TR2
torso = AI.angle; torso = AI.angle;
if (!info->Flags && if (!info->Flags &&
item->TouchBits & (0x0700 | 0x1400)) (item->TestBits(JointBitType::Touch, YetiAttackJoints1) || item->TestBits(JointBitType::Touch, YetiAttackJoints2)))
{ {
if (item->TouchBits & 0x0700) if (item->TestBits(JointBitType::Touch, YetiAttackJoints2))
CreatureEffect(item, YetiBiteLeft, DoBloodSplat); CreatureEffect(item, YetiBiteLeft, DoBloodSplat);
if (item->TouchBits & 0x1400)
if (item->TestBits(JointBitType::Touch, YetiAttackJoints1))
CreatureEffect(item, YetiBiteRight, DoBloodSplat); CreatureEffect(item, YetiBiteRight, DoBloodSplat);
DoDamage(info->Enemy, 200); DoDamage(info->Enemy, 200);

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR2 namespace TEN::Entities::Creatures::TR2
{ {
void InitialiseYeti(short itemNumber); void InitialiseYeti(short itemNumber);
void YetiControl(short itemNumber); void YetiControl(short itemNumber);

View file

@ -37,7 +37,7 @@
#include "Objects/TR2/Vehicles/speedboat.h" #include "Objects/TR2/Vehicles/speedboat.h"
#include "Objects/TR2/Vehicles/skidoo.h" #include "Objects/TR2/Vehicles/skidoo.h"
using namespace TEN::Entities::TR2; using namespace TEN::Entities::Creatures::TR2;
static void StartEntity(ObjectInfo* obj) static void StartEntity(ObjectInfo* obj)
{ {
@ -57,7 +57,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveHitpoints = true; obj->saveHitpoints = true;
obj->saveAnim = true; obj->saveAnim = true;
obj->saveFlags = true; obj->saveFlags = true;
obj->zoneType = ZONE_WATER; obj->ZoneType = ZoneType::Water;
g_Level.Bones[obj->boneIndex + 9 * 4] |= ROT_Y; g_Level.Bones[obj->boneIndex + 9 * 4] |= ROT_Y;
} }
@ -78,7 +78,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveHitpoints = true; obj->saveHitpoints = true;
obj->saveAnim = true; obj->saveAnim = true;
obj->saveFlags = true; obj->saveFlags = true;
obj->zoneType = ZONE_WATER; obj->ZoneType = ZoneType::Water;
g_Level.Bones[obj->boneIndex + 6 * 4] |= ROT_Y; g_Level.Bones[obj->boneIndex + 6 * 4] |= ROT_Y;
} }
@ -99,7 +99,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveAnim = true; obj->saveAnim = true;
obj->saveFlags = true; obj->saveFlags = true;
obj->pivotLength = 0; obj->pivotLength = 0;
obj->zoneType = ZONE_FLYER; obj->ZoneType = ZoneType::Flyer;
} }
obj = &Objects[ID_CROW]; obj = &Objects[ID_CROW];
@ -118,7 +118,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveAnim = true; obj->saveAnim = true;
obj->saveFlags = true; obj->saveFlags = true;
obj->pivotLength = 0; obj->pivotLength = 0;
obj->zoneType = ZONE_FLYER; obj->ZoneType = ZoneType::Flyer;
} }
obj = &Objects[ID_RAT]; obj = &Objects[ID_RAT];
@ -154,7 +154,7 @@ static void StartEntity(ObjectInfo* obj)
obj->saveFlags = true; obj->saveFlags = true;
obj->saveHitpoints = true; obj->saveHitpoints = true;
obj->savePosition = true; obj->savePosition = true;
obj->zoneType = ZONE_HUMAN_CLASSIC; obj->ZoneType = ZoneType::HumanClassic;
g_Level.Bones[obj->boneIndex + 6 * 4] |= (ROT_Y); g_Level.Bones[obj->boneIndex + 6 * 4] |= (ROT_Y);
g_Level.Bones[obj->boneIndex + 14 * 4] |= (ROT_Y); g_Level.Bones[obj->boneIndex + 14 * 4] |= (ROT_Y);
} }

View file

@ -16,7 +16,7 @@
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
constexpr auto CIVVY_ATTACK_DAMAGE = 40; constexpr auto CIVVY_ATTACK_DAMAGE = 40;
constexpr auto CIVVY_SWIPE_DAMAGE = 50; constexpr auto CIVVY_SWIPE_DAMAGE = 50;
@ -42,7 +42,6 @@ namespace TEN::Entities::TR3
// TODO // TODO
enum CivvyState enum CivvyState
{ {
CIVVY_STATE_NONE,
CIVVY_STATE_IDLE, CIVVY_STATE_IDLE,
CIVVY_STATE_WALK_FORWARD, CIVVY_STATE_WALK_FORWARD,
CIVVY_PUNCH2, CIVVY_PUNCH2,
@ -89,11 +88,10 @@ namespace TEN::Entities::TR3
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short torsoX = 0;
short torsoY = 0;
short head = 0;
short angle = 0; short angle = 0;
short tilt = 0; short tilt = 0;
auto extraHeadRot = EulerAngles::Zero;
auto extraTorsoRot = EulerAngles::Zero;
if (item->BoxNumber != NO_BOX && (g_Level.Boxes[item->BoxNumber].flags & BLOCKED)) if (item->BoxNumber != NO_BOX && (g_Level.Boxes[item->BoxNumber].flags & BLOCKED))
{ {
@ -119,18 +117,18 @@ namespace TEN::Entities::TR3
AI_INFO AI; AI_INFO AI;
CreatureAIInfo(item, &AI); CreatureAIInfo(item, &AI);
AI_INFO laraAiInfo; AI_INFO laraAI;
if (creature->Enemy == LaraItem) if (creature->Enemy == LaraItem)
{ {
laraAiInfo.angle = AI.angle; laraAI.angle = AI.angle;
laraAiInfo.distance = AI.distance; laraAI.distance = AI.distance;
} }
else else
{ {
int laraDz = LaraItem->Pose.Position.z - item->Pose.Position.z; int laraDz = LaraItem->Pose.Position.z - item->Pose.Position.z;
int laraDx = LaraItem->Pose.Position.x - item->Pose.Position.x; int laraDx = LaraItem->Pose.Position.x - item->Pose.Position.x;
laraAiInfo.angle = phd_atan(laraDz, laraDx) - item->Pose.Orientation.y; laraAI.angle = phd_atan(laraDz, laraDx) - item->Pose.Orientation.y;
laraAiInfo.distance = pow(laraDx, 2) + pow(laraDz, 2); laraAI.distance = pow(laraDx, 2) + pow(laraDz, 2);
} }
GetCreatureMood(item, &AI, true); GetCreatureMood(item, &AI, true);
@ -150,7 +148,7 @@ namespace TEN::Entities::TR3
auto* realEnemy = creature->Enemy; auto* realEnemy = creature->Enemy;
creature->Enemy = LaraItem; creature->Enemy = LaraItem;
if ((laraAiInfo.distance < CIVVY_AWARE_RANGE || item->HitStatus || TargetVisible(item, &laraAiInfo)) && if ((laraAI.distance < CIVVY_AWARE_RANGE || item->HitStatus || TargetVisible(item, &laraAI)) &&
!(item->AIBits & FOLLOW)) !(item->AIBits & FOLLOW))
{ {
if (!creature->Alerted) if (!creature->Alerted)
@ -170,13 +168,13 @@ namespace TEN::Entities::TR3
} }
case CIVVY_STATE_IDLE: case CIVVY_STATE_IDLE:
head = laraAiInfo.angle;
creature->MaxTurn = 0; creature->MaxTurn = 0;
creature->Flags = 0; creature->Flags = 0;
extraHeadRot.y = laraAI.angle;
if (item->AIBits & GUARD) if (item->AIBits & GUARD)
{ {
head = AIGuard(creature); extraHeadRot.y = AIGuard(creature);
if (!(GetRandomControl() & 0xFF)) if (!(GetRandomControl() & 0xFF))
{ {
if (item->Animation.ActiveState == CIVVY_STATE_IDLE) if (item->Animation.ActiveState == CIVVY_STATE_IDLE)
@ -199,7 +197,7 @@ namespace TEN::Entities::TR3
item->Animation.TargetState = CIVVY_STATE_RUN_FORWARD; item->Animation.TargetState = CIVVY_STATE_RUN_FORWARD;
} }
else if (creature->Mood == MoodType::Bored || else if (creature->Mood == MoodType::Bored ||
(item->AIBits & FOLLOW && (creature->ReachedGoal || laraAiInfo.distance > pow(SECTOR(2), 2)))) (item->AIBits & FOLLOW && (creature->ReachedGoal || laraAI.distance > pow(SECTOR(2), 2))))
{ {
if (item->Animation.RequiredState) if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState; item->Animation.TargetState = item->Animation.RequiredState;
@ -221,11 +219,11 @@ namespace TEN::Entities::TR3
case CIVVY_STATE_WALK_FORWARD: case CIVVY_STATE_WALK_FORWARD:
creature->MaxTurn = CIVVY_WALK_TURN_RATE_MAX; creature->MaxTurn = CIVVY_WALK_TURN_RATE_MAX;
head = laraAiInfo.angle; extraHeadRot.y = laraAI.angle;
if (item->AIBits & PATROL1) if (item->AIBits & PATROL1)
{ {
head = 0; extraHeadRot.y = 0;
item->Animation.TargetState = CIVVY_STATE_WALK_FORWARD; item->Animation.TargetState = CIVVY_STATE_WALK_FORWARD;
} }
else if (creature->Mood == MoodType::Escape) else if (creature->Mood == MoodType::Escape)
@ -252,7 +250,7 @@ namespace TEN::Entities::TR3
tilt = angle / 2; tilt = angle / 2;
if (AI.ahead) if (AI.ahead)
head = AI.angle; extraHeadRot.y = AI.angle;
if (item->AIBits & GUARD) if (item->AIBits & GUARD)
item->Animation.TargetState = CIVVY_WAIT; item->Animation.TargetState = CIVVY_WAIT;
@ -262,7 +260,7 @@ namespace TEN::Entities::TR3
item->Animation.TargetState = CIVVY_STATE_IDLE; item->Animation.TargetState = CIVVY_STATE_IDLE;
break; break;
} }
else if ((item->AIBits & FOLLOW) && (creature->ReachedGoal || laraAiInfo.distance > pow(SECTOR(2), 2))) else if ((item->AIBits & FOLLOW) && (creature->ReachedGoal || laraAI.distance > pow(SECTOR(2), 2)))
item->Animation.TargetState = CIVVY_STATE_IDLE; item->Animation.TargetState = CIVVY_STATE_IDLE;
else if (creature->Mood == MoodType::Bored) else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = CIVVY_STATE_WALK_FORWARD; item->Animation.TargetState = CIVVY_STATE_WALK_FORWARD;
@ -273,11 +271,12 @@ namespace TEN::Entities::TR3
case CIVVY_AIM0: case CIVVY_AIM0:
creature->MaxTurn = CIVVY_WALK_TURN_RATE_MAX; creature->MaxTurn = CIVVY_WALK_TURN_RATE_MAX;
creature->Flags = 0;
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (AI.bite && AI.distance < CIVVY_ATTACK0_RANGE) if (AI.bite && AI.distance < CIVVY_ATTACK0_RANGE)
@ -285,16 +284,16 @@ namespace TEN::Entities::TR3
else else
item->Animation.TargetState = CIVVY_STATE_IDLE; item->Animation.TargetState = CIVVY_STATE_IDLE;
creature->Flags = 0;
break; break;
case CIVVY_AIM1: case CIVVY_AIM1:
creature->MaxTurn = CIVVY_WALK_TURN_RATE_MAX; creature->MaxTurn = CIVVY_WALK_TURN_RATE_MAX;
creature->Flags = 0;
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (AI.ahead && AI.distance < CIVVY_ATTACK1_RANGE) if (AI.ahead && AI.distance < CIVVY_ATTACK1_RANGE)
@ -302,7 +301,6 @@ namespace TEN::Entities::TR3
else else
item->Animation.TargetState = CIVVY_STATE_IDLE; item->Animation.TargetState = CIVVY_STATE_IDLE;
creature->Flags = 0;
break; break;
case CIVVY_AIM2: case CIVVY_AIM2:
@ -311,8 +309,8 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (AI.bite && AI.distance < CIVVY_ATTACK2_RANGE) if (AI.bite && AI.distance < CIVVY_ATTACK2_RANGE)
@ -327,8 +325,8 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (!creature->Flags && item->TestBits(JointBitType::Touch, CivvyAttackJoints)) if (!creature->Flags && item->TestBits(JointBitType::Touch, CivvyAttackJoints))
@ -346,8 +344,8 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (!creature->Flags && item->TestBits(JointBitType::Touch, CivvyAttackJoints)) if (!creature->Flags && item->TestBits(JointBitType::Touch, CivvyAttackJoints))
@ -368,8 +366,8 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (creature->Flags != 2 && item->TestBits(JointBitType::Touch, CivvyAttackJoints)) if (creature->Flags != 2 && item->TestBits(JointBitType::Touch, CivvyAttackJoints))
@ -385,9 +383,9 @@ namespace TEN::Entities::TR3
} }
CreatureTilt(item, tilt); CreatureTilt(item, tilt);
CreatureJoint(item, 0, torsoY); CreatureJoint(item, 0, extraTorsoRot.y);
CreatureJoint(item, 1, torsoX); CreatureJoint(item, 1, extraTorsoRot.x);
CreatureJoint(item, 2, head); CreatureJoint(item, 2, extraHeadRot.y);
if (item->Animation.ActiveState < CIVVY_DEATH) if (item->Animation.ActiveState < CIVVY_DEATH)
{ {

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
void InitialiseCivvy(short itemNumber); void InitialiseCivvy(short itemNumber);
void CivvyControl(short itemNumber); void CivvyControl(short itemNumber);

View file

@ -14,7 +14,7 @@
using std::vector; using std::vector;
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
constexpr auto COBRA_BITE_ATTACK_DAMAGE = 80; constexpr auto COBRA_BITE_ATTACK_DAMAGE = 80;
constexpr auto COBRA_BITE_POISON_POTENCY = 8; constexpr auto COBRA_BITE_POISON_POTENCY = 8;
@ -23,8 +23,7 @@ namespace TEN::Entities::TR3
constexpr auto COBRA_AWARE_RANGE = SQUARE(SECTOR(1.5f)); constexpr auto COBRA_AWARE_RANGE = SQUARE(SECTOR(1.5f));
constexpr auto COBRA_SLEEP_RANGE = SQUARE(SECTOR(2.5f)); constexpr auto COBRA_SLEEP_RANGE = SQUARE(SECTOR(2.5f));
constexpr auto PLAYER_DISTURB_VELOCITY = 15; constexpr auto COBRA_DISTURBANCE_VELOCITY = 15;
constexpr auto COBRA_SLEEP_FRAME = 45; constexpr auto COBRA_SLEEP_FRAME = 45;
const auto CobraBite = BiteInfo(Vector3::Zero, 13); const auto CobraBite = BiteInfo(Vector3::Zero, 13);
@ -42,7 +41,7 @@ namespace TEN::Entities::TR3
enum CobraAnim enum CobraAnim
{ {
COBRA_ANIM_IDLE = 0, COBRA_ANIM_IDLE = 0,
COBRA_ANIM_WAKE_UP = 1, COBRA_ANIM_SLEEP_TO_IDLE = 1,
COBRA_ANIM_IDLE_TO_SLEEP = 2, COBRA_ANIM_IDLE_TO_SLEEP = 2,
COBRA_ANIM_BITE_ATTACK = 3, COBRA_ANIM_BITE_ATTACK = 3,
COBRA_ANIM_DEATH = 4 COBRA_ANIM_DEATH = 4
@ -65,9 +64,9 @@ namespace TEN::Entities::TR3
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0; short angle = 0;
short tilt = 0; short tilt = 0;
short head = 0;
if (item->HitPoints <= 0 && item->HitPoints != NOT_TARGETABLE) if (item->HitPoints <= 0 && item->HitPoints != NOT_TARGETABLE)
{ {
@ -84,19 +83,23 @@ namespace TEN::Entities::TR3
GetCreatureMood(item, &AI, 1); GetCreatureMood(item, &AI, 1);
CreatureMood(item, &AI, 1); CreatureMood(item, &AI, 1);
bool enemyMoving = false; bool isEnemyMoving = false;
bool enemyVisible = false; bool isEnemyVisible = false;
if (creature->Enemy && (GlobalCounter & 2))
{
auto src = GameVector(creature->Enemy->Pose.Position, creature->Enemy->RoomNumber);
auto dest = GameVector(item->Pose.Position, item->RoomNumber);
enemyVisible = LOS(&src, &dest);
enemyMoving = creature->Enemy->Animation.Velocity.z > PLAYER_DISTURB_VELOCITY || if (creature->Enemy != nullptr && (GlobalCounter & 2))
abs(creature->Enemy->Animation.Velocity.y) > PLAYER_DISTURB_VELOCITY; {
auto origin = GameVector(creature->Enemy->Pose.Position, creature->Enemy->RoomNumber);
auto target = GameVector(item->Pose.Position, item->RoomNumber);
isEnemyVisible = LOS(&origin, &target);
if (creature->Enemy->Animation.Velocity.z > COBRA_DISTURBANCE_VELOCITY ||
abs(creature->Enemy->Animation.Velocity.y) > COBRA_DISTURBANCE_VELOCITY)
{
isEnemyMoving = true;
}
} }
if (enemyVisible && item->Animation.ActiveState != COBRA_STATE_SLEEP) if (isEnemyVisible && item->Animation.ActiveState != COBRA_STATE_SLEEP)
{ {
creature->Target.x = creature->Enemy->Pose.Position.x; creature->Target.x = creature->Enemy->Pose.Position.x;
creature->Target.z = creature->Enemy->Pose.Position.z; creature->Target.z = creature->Enemy->Pose.Position.z;
@ -119,12 +122,10 @@ namespace TEN::Entities::TR3
creature->Flags = 0; creature->Flags = 0;
if (AI.distance > COBRA_SLEEP_RANGE) if (AI.distance > COBRA_SLEEP_RANGE)
{
item->Animation.TargetState = COBRA_STATE_SLEEP; item->Animation.TargetState = COBRA_STATE_SLEEP;
} else if (creature->Enemy->HitPoints > 0 && isEnemyVisible &&
else if (creature->Enemy->HitPoints > 0 && enemyVisible && ((AI.ahead && AI.distance < COBRA_ATTACK_RANGE && AI.verticalDistance <= GetBoundsAccurate(item)->Height()) ||
((AI.ahead && AI.distance < COBRA_ATTACK_RANGE && AI.verticalDistance <= GetBoundsAccurate(item)->Height()) || item->HitStatus || isEnemyMoving))
item->HitStatus || enemyMoving))
{ {
item->Animation.TargetState = COBRA_STATE_ATTACK; item->Animation.TargetState = COBRA_STATE_ATTACK;
} }
@ -149,12 +150,12 @@ namespace TEN::Entities::TR3
break; break;
case COBRA_STATE_ATTACK: case COBRA_STATE_ATTACK:
if (creature->Flags != 1 && if (!(creature->Flags & 1) && // 1 = is attacking.
item->TestBits(JointBitType::Touch, CobraAttackJoints)) item->TestBits(JointBitType::Touch, CobraAttackJoints))
{ {
DoDamage(creature->Enemy, COBRA_BITE_ATTACK_DAMAGE); DoDamage(creature->Enemy, COBRA_BITE_ATTACK_DAMAGE);
CreatureEffect(item, CobraBite, DoBloodSplat); CreatureEffect(item, CobraBite, DoBloodSplat);
creature->Flags = 1; creature->Flags |= 1; // 1 = is attacking.
if (creature->Enemy->IsLara()) if (creature->Enemy->IsLara())
GetLaraInfo(creature->Enemy)->PoisonPotency += COBRA_BITE_POISON_POTENCY; GetLaraInfo(creature->Enemy)->PoisonPotency += COBRA_BITE_POISON_POTENCY;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
void InitialiseCobra(short itemNum); void InitialiseCobra(short itemNum);
void CobraControl(short itemNum); void CobraControl(short itemNum);

View file

@ -9,7 +9,7 @@
#include "Objects/TR3/fish.h" #include "Objects/TR3/fish.h"
#include "Specific/level.h" #include "Specific/level.h"
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
int PirahnaHitWait = false; int PirahnaHitWait = false;
int CarcassItem = NO_ITEM; int CarcassItem = NO_ITEM;

View file

@ -1,7 +1,7 @@
#pragma once #pragma once
#include "Game/items.h" #include "Game/items.h"
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
void SetupShoal(int shoalNumber); void SetupShoal(int shoalNumber);
void ControlFish(short itemNumber); void ControlFish(short itemNumber);

View file

@ -16,7 +16,9 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
namespace TEN::Entities::TR3 using namespace TEN::Math::Random;
namespace TEN::Entities::Creatures::TR3
{ {
const auto FlamethrowerOffset = Vector3i(0, 340, 0); const auto FlamethrowerOffset = Vector3i(0, 340, 0);
const auto FlamethrowerBite = BiteInfo(Vector3(0.0f, 340.0f, 64.0f), 7); const auto FlamethrowerBite = BiteInfo(Vector3(0.0f, 340.0f, 64.0f), 7);
@ -41,33 +43,26 @@ namespace TEN::Entities::TR3
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short torsoX = 0;
short torsoY = 0;
short angle = 0; short angle = 0;
short tilt = 0; short tilt = 0;
short head = 0; auto extraHeadRot = EulerAngles::Zero;
auto extraTorsoRot = EulerAngles::Zero;
auto pos = GetJointPosition(item, FlamethrowerBite.meshNum, Vector3i(FlamethrowerBite.Position)); auto pos = GetJointPosition(item, FlamethrowerBite.meshNum, Vector3i(FlamethrowerBite.Position));
int random = GetRandomControl(); int randomInt = GetRandomControl();
if (item->Animation.ActiveState != 6 && item->Animation.ActiveState != 11) if (item->Animation.ActiveState != 6 && item->Animation.ActiveState != 11)
{ {
TriggerDynamicLight(pos.x, pos.y, pos.z, (random & 3) + 6, 24 - ((random / 16) & 3), 16 - ((random / 64) & 3), random & 3); TriggerDynamicLight(pos.x, pos.y, pos.z, (randomInt & 3) + 6, 24 - ((randomInt / 16) & 3), 16 - ((randomInt / 64) & 3), randomInt & 3);
TriggerPilotFlame(itemNumber, 9); TriggerPilotFlame(itemNumber, 9);
} }
else else
{ TriggerDynamicLight(pos.x, pos.y, pos.z, (randomInt & 3) + 10, 31 - ((randomInt / 16) & 3), 24 - ((randomInt / 64) & 3), randomInt & 7);
TriggerDynamicLight(pos.x, pos.y, pos.z, (random & 3) + 10, 31 - ((random / 16) & 3), 24 - ((random / 64) & 3), random & 7);
}
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
if (item->Animation.ActiveState != 7) if (item->Animation.ActiveState != 7)
{ SetAnimation(item, 19);
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 19;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 7;
}
} }
else else
{ {
@ -82,9 +77,8 @@ namespace TEN::Entities::TR3
ItemInfo* target = nullptr; ItemInfo* target = nullptr;
int minDistance = INT_MAX; int minDistance = INT_MAX;
for (int i = 0; i < ActiveCreatures.size(); i++) for (auto& currentCreature : ActiveCreatures)
{ {
auto* currentCreature = ActiveCreatures[i];
if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber) if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber)
continue; continue;
@ -147,13 +141,13 @@ namespace TEN::Entities::TR3
case 1: case 1:
creature->MaxTurn = 0; creature->MaxTurn = 0;
creature->Flags = 0; creature->Flags = 0;
head = laraAI.angle; extraHeadRot.y = laraAI.angle;
if (item->AIBits & GUARD) if (item->AIBits & GUARD)
{ {
head = AIGuard(creature); extraHeadRot.y = AIGuard(creature);
if (!(GetRandomControl() & 0xFF)) if (TestProbability(1.0f / 128))
item->Animation.TargetState = 4; item->Animation.TargetState = 4;
break; break;
@ -169,21 +163,21 @@ namespace TEN::Entities::TR3
else else
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
} }
else if (creature->Mood == MoodType::Bored && AI.ahead && !(GetRandomControl() & 0xFF)) else if (creature->Mood == MoodType::Bored && AI.ahead && TestProbability(1.0f / 128))
item->Animation.TargetState = 4; item->Animation.TargetState = 4;
else if (creature->Mood == MoodType::Attack || !(GetRandomControl() & 0xFF)) else if (creature->Mood == MoodType::Attack || TestProbability(1.0f / 128))
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
break; break;
case 4: case 4:
head = laraAI.angle; extraHeadRot.y = laraAI.angle;
if (item->AIBits & GUARD) if (item->AIBits & GUARD)
{ {
head = AIGuard(creature); extraHeadRot.y = AIGuard(creature);
if (!(GetRandomControl() & 0xFF)) if (TestProbability(1.0f / 128))
item->Animation.TargetState = 1; item->Animation.TargetState = 1;
break; break;
@ -192,7 +186,7 @@ namespace TEN::Entities::TR3
AI.distance < pow(SECTOR(4), 2) && AI.distance < pow(SECTOR(4), 2) &&
(realEnemy != LaraItem || creature->HurtByLara) || (realEnemy != LaraItem || creature->HurtByLara) ||
creature->Mood != MoodType::Bored || creature->Mood != MoodType::Bored ||
!(GetRandomControl() & 0xFF))) TestProbability(1.0f / 128)))
{ {
item->Animation.TargetState = 1; item->Animation.TargetState = 1;
} }
@ -200,17 +194,12 @@ namespace TEN::Entities::TR3
break; break;
case 2: case 2:
creature->Flags = 0;
creature->MaxTurn = ANGLE(5.0f); creature->MaxTurn = ANGLE(5.0f);
head = laraAI.angle; creature->Flags = 0;
extraHeadRot.y = laraAI.angle;
if (item->AIBits & GUARD) if (item->AIBits & GUARD)
{ SetAnimation(item, 12);
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 12;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 1;
item->Animation.TargetState = 1;
}
else if (item->AIBits & PATROL1) else if (item->AIBits & PATROL1)
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
else if (creature->Mood == MoodType::Escape) else if (creature->Mood == MoodType::Escape)
@ -235,8 +224,8 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
if (Targetable(item, &AI) && if (Targetable(item, &AI) &&
AI.distance < pow(SECTOR(4), 2) && AI.distance < pow(SECTOR(4), 2) &&
@ -255,8 +244,8 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
if (Targetable(item, &AI) && if (Targetable(item, &AI) &&
AI.distance < pow(SECTOR(4), 2) && AI.distance < pow(SECTOR(4), 2) &&
@ -276,8 +265,8 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
if (Targetable(item, &AI) && if (Targetable(item, &AI) &&
AI.distance < pow(SECTOR(4), 2) && AI.distance < pow(SECTOR(4), 2) &&
@ -311,12 +300,12 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
if (Targetable(item, &AI) && if (Targetable(item, &AI) &&
AI.distance < pow(SECTOR(4), 2) && AI.distance < pow(SECTOR(4), 2) &&
(realEnemy != LaraItem || creature->HurtByLara)) (!realEnemy->IsLara() || creature->HurtByLara))
{ {
item->Animation.TargetState = 6; item->Animation.TargetState = 6;
} }
@ -343,9 +332,9 @@ namespace TEN::Entities::TR3
} }
CreatureTilt(item, tilt); CreatureTilt(item, tilt);
CreatureJoint(item, 0, torsoY); CreatureJoint(item, 0, extraTorsoRot.y);
CreatureJoint(item, 1, torsoX); CreatureJoint(item, 1, extraTorsoRot.x);
CreatureJoint(item, 2, head); CreatureJoint(item, 2, extraHeadRot.y);
CreatureAnimation(itemNumber, angle, 0); CreatureAnimation(itemNumber, angle, 0);
} }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
void FlameThrowerControl(short itemNumber); void FlameThrowerControl(short itemNumber);
} }

View file

@ -12,23 +12,85 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
const vector<int> MonkeyAttackJoints = { 10, 13 }; // TODO: Work out damage constants.
constexpr auto MONKEY_SWIPE_ATTACK_PLAYER_DAMAGE = 40;
constexpr auto MONKEY_SWIPE_ATTACK_CREATURE_DAMAGE = 20;
// TODO: Range constants.
const auto MonkeyBite = BiteInfo(Vector3(10.0f, 10.0f, 11.0f), 13); const auto MonkeyBite = BiteInfo(Vector3(10.0f, 10.0f, 11.0f), 13);
const vector<int> MonkeyAttackJoints = { 10, 13 };
enum MonkeyState
{
// No states 0-1.
MONKEY_STATE_WALK_FORWARD = 2,
MONKEY_STATE_IDLE = 3,
MONKEY_STATE_RUN_FORWARD = 4,
MONKEY_STATE_BITE_ATTACK = 5, // Check.
MONKEY_STATE_SIT = 6,
MONKEY_STATE_SIT_EAT = 7,
MONKEY_STATE_SIT_SCRATCH = 8,
MONKEY_STATE_RUN_FORWARD_ROLL = 9,
MONKEY_STATE_POUND_GROUND = 10,
MONKEY_STATE_DEATH = 11,
MONKEY_STATE_SWIPE_ATTACK = 12,
MONKEY_STATE_JUMP_ATTACK = 13,
MONKEY_STATE_HIGH_JUMP_ATTACK = 14,
MONKEY_STATE_VAULT_UP_1_BLOCK = 15,
MONKEY_STATE_VAULT_UP_0_POINT_3_BLOCKS = 16,
MONKEY_STATE_VAULT_UP_0_POINT_2_BLOCKS = 17,
MONKEY_STATE_VAULT_DOWN_1_BLOCK = 18,
MONKEY_STATE_VAULT_DOWN_0_POINT_3_BLOCKS = 19,
MONKEY_STATE_VAULT_DOWN_0_POINT_2_BLOCKS = 20
};
enum MonkeyAnim
{
MONKEY_ANIM_WALK_FORWARD = 0,
MONKEY_ANIM_WALK_FORWARD_TO_SIT = 1,
MONKEY_ANIM_SIT = 2,
MONKEY_ANIM_SIT_TO_WALK_FORWARD = 3,
MONKEY_ANIM_SIT_EAT = 4,
MONKEY_ANIM_SIT_SCRATCH = 5,
MONKEY_ANIM_RUN_FORWARD = 6,
MONKEY_ANIM_RUN_FORWARD_ROLL = 7,
MONKEY_ANIM_IDLE_POUND_GROUND = 8,
MONKEY_ANIM_IDLE = 9,
MONKEY_ANIM_IDLE_TO_RUN_FORWARD = 10,
MONKEY_ANIM_WALK_FORWARD_TO_RUN_FORWARD = 11,
MONKEY_ANIM_RUN_FORWARD_TO_IDLE = 12,
MONKEY_ANIM_SIT_TO_IDLE = 13,
MONKEY_ANIM_DEATH = 14,
MONKEY_ANIM_RUN_FORWARD_TO_WALK_FORWARD = 15,
MONKEY_ANIM_IDLE_TO_SIT = 16,
MONKEY_ANIM_VAULT_UP_1_BLOCK = 17,
MONKEY_ANIM_VAULT_UP_0_POINT_3_BLOCKS = 18,
MONKEY_ANIM_VAULT_UP_0_POINT_2_BLOCKS = 19,
MONKEY_ANIM_VAULT_DOWN_1_BLOCK = 20,
MONKEY_ANIM_VAULT_DOWN_0_POINT_3_BLOCKS = 21,
MONKEY_ANIM_VAULT_DOWN_0_POINT_2_BLOCKS = 22,
MONKEY_ANIM_SWIPE_ATTACK = 23,
MONKEY_ANIM_JUMP_ATTACK = 24,
MONKEY_ANIM_BITE_ATTACK = 25,
MONKEY_ANIM_HIGH_JUMP_ATTACK_START = 26,
MONKEY_ANIM_HIGH_JUMP_ATTACK_CONTINUE = 27,
MONKEY_ANIM_HIGH_JUMP_ATTACK_END = 28,
MONKEY_ANIM_IDLE_TO_WALK_FORWARD = 29,
MONKEY_ANIM_WALK_FORWARD_TO_IDLE = 30
};
void InitialiseMonkey(short itemNumber) void InitialiseMonkey(short itemNumber)
{ {
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
ClearItem(itemNumber); ClearItem(itemNumber);
SetAnimation(item, MONKEY_ANIM_SIT);
item->Animation.AnimNumber = Objects[ID_MONKEY].animIndex + 2;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 6;
item->Animation.TargetState = 6;
} }
void MonkeyControl(short itemNumber) void MonkeyControl(short itemNumber)
@ -39,20 +101,17 @@ namespace TEN::Entities::TR3
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short headX = 0;
short headY = 0;
short torsoY = 0;
short angle = 0; short angle = 0;
short tilt = 0; short tilt = 0;
auto extraHeadRot = EulerAngles::Zero;
auto extraTorsoRot = EulerAngles::Zero;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
if (item->Animation.ActiveState != 11) if (item->Animation.ActiveState != MONKEY_STATE_DEATH)
{ {
SetAnimation(item, MONKEY_ANIM_DEATH);
item->MeshBits = ALL_JOINT_BITS; item->MeshBits = ALL_JOINT_BITS;
item->Animation.AnimNumber = Objects[ID_MONKEY].animIndex + 14;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 11;
} }
} }
else else
@ -63,12 +122,11 @@ namespace TEN::Entities::TR3
creature->Enemy = LaraItem; creature->Enemy = LaraItem;
else else
{ {
int minDistance = 0x7FFFFFFF;
creature->Enemy = nullptr; creature->Enemy = nullptr;
int minDistance = INT_MAX;
for (int i = 0; i < ActiveCreatures.size(); i++) for (auto& currentCreature : ActiveCreatures)
{ {
auto* currentCreature = ActiveCreatures[i];
if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber) if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber)
continue; continue;
@ -109,11 +167,11 @@ namespace TEN::Entities::TR3
AI_INFO AI; AI_INFO AI;
CreatureAIInfo(item, &AI); CreatureAIInfo(item, &AI);
if (!creature->HurtByLara && creature->Enemy == LaraItem) if (!creature->HurtByLara && creature->Enemy->IsLara())
creature->Enemy = nullptr; creature->Enemy = nullptr;
AI_INFO laraAI; AI_INFO laraAI;
if (creature->Enemy == LaraItem) if (creature->Enemy->IsLara())
{ {
laraAI.angle = AI.angle; laraAI.angle = AI.angle;
laraAI.distance = AI.distance; laraAI.distance = AI.distance;
@ -146,144 +204,147 @@ namespace TEN::Entities::TR3
switch (item->Animation.ActiveState) switch (item->Animation.ActiveState)
{ {
case 6: case MONKEY_STATE_SIT:
creature->Flags = 0;
creature->MaxTurn = 0; creature->MaxTurn = 0;
torsoY = laraAI.angle; creature->Flags = 0;
extraTorsoRot.y = laraAI.angle;
if (item->AIBits & GUARD) if (item->AIBits & GUARD)
{ {
torsoY = AIGuard(creature); extraTorsoRot.y = AIGuard(creature);
if (!(GetRandomControl() & 0xF)) if (TestProbability(0.06f))
{ {
if (GetRandomControl() & 0x1) if (TestProbability(0.5f))
item->Animation.TargetState = 8; item->Animation.TargetState = MONKEY_STATE_SIT_EAT;
else else
item->Animation.TargetState = 7; item->Animation.TargetState = MONKEY_STATE_SIT_SCRATCH;
} }
break; break;
} }
else if (item->AIBits & PATROL1) else if (item->AIBits & PATROL1)
item->Animation.TargetState = 2; item->Animation.TargetState = MONKEY_STATE_WALK_FORWARD;
else if (creature->Mood == MoodType::Escape) else if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 3; item->Animation.TargetState = MONKEY_STATE_IDLE;
else if (creature->Mood == MoodType::Bored) else if (creature->Mood == MoodType::Bored)
{ {
if (item->Animation.RequiredState) if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState; item->Animation.TargetState = item->Animation.RequiredState;
else if (!(GetRandomControl() & 0xF)) else if (TestProbability(0.06f))
item->Animation.TargetState = 2; item->Animation.TargetState = MONKEY_STATE_WALK_FORWARD;
else if (!(GetRandomControl() & 0xF)) else if (TestProbability(0.06f))
{ {
if (GetRandomControl() & 0x1) if (TestProbability(0.5f))
item->Animation.TargetState = 8; item->Animation.TargetState = MONKEY_STATE_SIT_EAT;
else else
item->Animation.TargetState = 7; item->Animation.TargetState = MONKEY_STATE_SIT_SCRATCH;
} }
} }
else if ((item->AIBits & FOLLOW) && (creature->ReachedGoal || laraAI.distance > pow(SECTOR(2), 2))) else if ((item->AIBits & FOLLOW) &&
(creature->ReachedGoal || laraAI.distance > pow(SECTOR(2), 2)))
{ {
if (item->Animation.RequiredState) if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState; item->Animation.TargetState = item->Animation.RequiredState;
else if (AI.ahead) else if (AI.ahead)
item->Animation.TargetState = 6; item->Animation.TargetState = MONKEY_STATE_SIT;
else else
item->Animation.TargetState = 3; item->Animation.TargetState = MONKEY_STATE_IDLE;
} }
else if (AI.bite && AI.distance < pow(682, 2)) else if (AI.bite && AI.distance < pow(682, 2))
item->Animation.TargetState = 3; item->Animation.TargetState = MONKEY_STATE_IDLE;
else if (AI.bite && AI.distance < pow(682, 2)) else if (AI.bite && AI.distance < pow(682, 2))
item->Animation.TargetState = 2; item->Animation.TargetState = MONKEY_STATE_WALK_FORWARD;
else else
item->Animation.TargetState = 3; item->Animation.TargetState = MONKEY_STATE_IDLE;
break; break;
case 3: case MONKEY_STATE_IDLE:
creature->MaxTurn = 0; creature->MaxTurn = 0;
creature->Flags = 0; creature->Flags = 0;
torsoY = laraAI.angle; extraTorsoRot.y = laraAI.angle;
if (item->AIBits & GUARD) if (item->AIBits & GUARD)
{ {
torsoY = AIGuard(creature); extraTorsoRot.y = AIGuard(creature);
if (!(GetRandomControl() & 15)) if (TestProbability(0.06f))
{ {
if (GetRandomControl() & 1) if (TestProbability(0.5f))
item->Animation.TargetState = 10; item->Animation.TargetState = MONKEY_STATE_POUND_GROUND;
else else
item->Animation.TargetState = 6; item->Animation.TargetState = MONKEY_STATE_SIT;
} }
break; break;
} }
else if (item->AIBits & PATROL1) else if (item->AIBits & PATROL1)
item->Animation.TargetState = 2; item->Animation.TargetState = MONKEY_STATE_WALK_FORWARD;
else if (creature->Mood == MoodType::Escape) else if (creature->Mood == MoodType::Escape)
{ {
if (Lara.TargetEntity != item && AI.ahead) if (Lara.TargetEntity != item && AI.ahead)
item->Animation.TargetState = 3; item->Animation.TargetState = MONKEY_STATE_IDLE;
else else
item->Animation.TargetState = 4; item->Animation.TargetState = MONKEY_STATE_RUN_FORWARD;
} }
else if (creature->Mood == MoodType::Bored) else if (creature->Mood == MoodType::Bored)
{ {
if (item->Animation.RequiredState) if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState; item->Animation.TargetState = item->Animation.RequiredState;
else if (!(GetRandomControl() & 15)) else if (TestProbability(0.06f))
item->Animation.TargetState = 2; item->Animation.TargetState = MONKEY_STATE_WALK_FORWARD;
else if (!(GetRandomControl() & 15)) else if (TestProbability(0.06f))
{ {
if (GetRandomControl() & 1) if (TestProbability(0.5f))
item->Animation.TargetState = 10; item->Animation.TargetState = MONKEY_STATE_POUND_GROUND;
else else
item->Animation.TargetState = 6; item->Animation.TargetState = MONKEY_STATE_SIT;
} }
} }
else if (item->AIBits & FOLLOW && (creature->ReachedGoal || laraAI.distance > pow(SECTOR(2), 2))) else if (item->AIBits & FOLLOW &&
(creature->ReachedGoal || laraAI.distance > pow(SECTOR(2), 2)))
{ {
if (item->Animation.RequiredState) if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState; item->Animation.TargetState = item->Animation.RequiredState;
else if (AI.ahead) else if (AI.ahead)
item->Animation.TargetState = 6; item->Animation.TargetState = MONKEY_STATE_SIT;
else else
item->Animation.TargetState = 4; item->Animation.TargetState = MONKEY_STATE_RUN_FORWARD;
} }
else if (AI.bite && AI.distance < pow(341, 2)) else if (AI.bite && AI.distance < pow(341, 2))
{ {
if (LaraItem->Pose.Position.y < item->Pose.Position.y) if (LaraItem->Pose.Position.y < item->Pose.Position.y)
item->Animation.TargetState = 13; item->Animation.TargetState = MONKEY_STATE_JUMP_ATTACK;
else else
item->Animation.TargetState = 12; item->Animation.TargetState = MONKEY_STATE_SWIPE_ATTACK;
} }
else if (AI.bite && AI.distance < pow(682, 2)) else if (AI.bite && AI.distance < pow(682, 2))
item->Animation.TargetState = 14; item->Animation.TargetState = MONKEY_STATE_HIGH_JUMP_ATTACK;
else if (AI.bite && AI.distance < pow(682, 2)) else if (AI.bite && AI.distance < pow(682, 2))
item->Animation.TargetState = 2; item->Animation.TargetState = MONKEY_STATE_WALK_FORWARD;
else if (AI.distance < pow(682, 2) && creature->Enemy != LaraItem && creature->Enemy != nullptr && else if (AI.distance < pow(682, 2) &&
creature->Enemy->ObjectNumber != ID_AI_PATROL1 && creature->Enemy->ObjectNumber != ID_AI_PATROL2 && !creature->Enemy->IsLara() && creature->Enemy != nullptr &&
abs(item->Pose.Position.y - creature->Enemy->Pose.Position.y) < 256) creature->Enemy->ObjectNumber != ID_AI_PATROL1 &&
creature->Enemy->ObjectNumber != ID_AI_PATROL2 &&
abs(item->Pose.Position.y - creature->Enemy->Pose.Position.y) < CLICK(1))
{ {
item->Animation.TargetState = 5; item->Animation.TargetState = MONKEY_STATE_BITE_ATTACK;
} }
else if (AI.bite && AI.distance < pow(SECTOR(1), 2)) else if (AI.bite && AI.distance < pow(SECTOR(1), 2))
item->Animation.TargetState = 9; item->Animation.TargetState = MONKEY_STATE_RUN_FORWARD_ROLL;
else else
item->Animation.TargetState = 4; item->Animation.TargetState = MONKEY_STATE_RUN_FORWARD;
break; break;
case 5: case MONKEY_STATE_BITE_ATTACK:
creature->ReachedGoal = true; creature->ReachedGoal = true;
if (creature->Enemy == nullptr) if (creature->Enemy == nullptr)
break; break;
else if ((creature->Enemy->ObjectNumber == ID_SMALLMEDI_ITEM || else if ((creature->Enemy->ObjectNumber == ID_SMALLMEDI_ITEM ||
creature->Enemy->ObjectNumber == ID_KEY_ITEM4) && creature->Enemy->ObjectNumber == ID_KEY_ITEM4) &&
item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 12) item->Animation.FrameNumber == (g_Level.Anims[item->Animation.AnimNumber].frameBase + 12))
{ {
if (creature->Enemy->RoomNumber == NO_ROOM || if (creature->Enemy->RoomNumber == NO_ROOM ||
creature->Enemy->Status == ITEM_INVISIBLE || creature->Enemy->Status == ITEM_INVISIBLE ||
@ -298,9 +359,8 @@ namespace TEN::Entities::TR3
creature->Enemy->RoomNumber = NO_ROOM; creature->Enemy->RoomNumber = NO_ROOM;
creature->Enemy->CarriedItem = NO_ITEM; creature->Enemy->CarriedItem = NO_ITEM;
for (int i = 0; i < ActiveCreatures.size(); i++) for (auto& currentCreature : ActiveCreatures)
{ {
auto* currentCreature = ActiveCreatures[i];
if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber) if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber)
continue; continue;
@ -318,15 +378,14 @@ namespace TEN::Entities::TR3
} }
} }
} }
else if (creature->Enemy->ObjectNumber == ID_AI_AMBUSH && item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 12) else if (creature->Enemy->ObjectNumber == ID_AI_AMBUSH &&
item->Animation.FrameNumber == (g_Level.Anims[item->Animation.AnimNumber].frameBase + 12))
{ {
item->AIBits = 0; item->AIBits = 0;
auto* carriedItem = &g_Level.Items[item->CarriedItem]; auto* carriedItem = &g_Level.Items[item->CarriedItem];
carriedItem->Pose.Position.x = item->Pose.Position.x; carriedItem->Pose.Position = item->Pose.Position;
carriedItem->Pose.Position.y = item->Pose.Position.y;
carriedItem->Pose.Position.z = item->Pose.Position.z;
ItemNewRoom(item->CarriedItem, item->RoomNumber); ItemNewRoom(item->CarriedItem, item->RoomNumber);
item->CarriedItem = NO_ITEM; item->CarriedItem = NO_ITEM;
@ -348,60 +407,106 @@ namespace TEN::Entities::TR3
break; break;
case 2: case MONKEY_STATE_WALK_FORWARD:
creature->MaxTurn = ANGLE(7.0f); creature->MaxTurn = ANGLE(7.0f);
torsoY = laraAI.angle; extraTorsoRot.y = laraAI.angle;
if (item->AIBits & PATROL1) if (item->AIBits & PATROL1)
{ {
item->Animation.TargetState = 2; item->Animation.TargetState = MONKEY_STATE_WALK_FORWARD;
torsoY = 0; extraTorsoRot.y = 0;
} }
else if (creature->Mood == MoodType::Escape) else if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = 4; item->Animation.TargetState = MONKEY_STATE_RUN_FORWARD;
else if (creature->Mood == MoodType::Bored) else if (creature->Mood == MoodType::Bored)
{ {
if (GetRandomControl() < 256) if (TestProbability(1.0f / 128))
item->Animation.TargetState = 6; item->Animation.TargetState = MONKEY_STATE_SIT;
} }
else if (AI.bite && AI.distance < pow(682, 2)) else if (AI.bite && AI.distance < pow(682, 2))
item->Animation.TargetState = 3; item->Animation.TargetState = MONKEY_STATE_IDLE;
break; break;
case 4: case MONKEY_STATE_RUN_FORWARD:
creature->MaxTurn = ANGLE(11.0f); creature->MaxTurn = ANGLE(11.0f);
tilt = angle / 2; tilt = angle / 2;
if (AI.ahead) if (AI.ahead)
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
if (item->AIBits & GUARD) if (item->AIBits & GUARD)
item->Animation.TargetState = 3; item->Animation.TargetState = MONKEY_STATE_IDLE;
else if (creature->Mood == MoodType::Escape) else if (creature->Mood == MoodType::Escape)
{ {
if (Lara.TargetEntity != item && AI.ahead) if (Lara.TargetEntity != item && AI.ahead)
item->Animation.TargetState = 3; item->Animation.TargetState = MONKEY_STATE_IDLE;
break; break;
} }
else if ((item->AIBits & FOLLOW) && (creature->ReachedGoal || laraAI.distance > pow(SECTOR(2), 2))) else if ((item->AIBits & FOLLOW) &&
item->Animation.TargetState = 3; (creature->ReachedGoal || laraAI.distance > pow(SECTOR(2), 2)))
{
item->Animation.TargetState = MONKEY_STATE_IDLE;
}
else if (creature->Mood == MoodType::Bored) else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 9; item->Animation.TargetState = MONKEY_STATE_RUN_FORWARD_ROLL;
else if (AI.distance < pow(682, 2)) else if (AI.distance < pow(682, 2))
item->Animation.TargetState = 3; item->Animation.TargetState = MONKEY_STATE_IDLE;
else if (AI.bite && AI.distance < pow(SECTOR(1), 2)) else if (AI.bite && AI.distance < pow(SECTOR(1), 2))
item->Animation.TargetState = 9; item->Animation.TargetState = MONKEY_STATE_RUN_FORWARD_ROLL;
break; break;
case 12: case MONKEY_STATE_SWIPE_ATTACK:
creature->MaxTurn = 0; creature->MaxTurn = 0;
if (AI.ahead) if (AI.ahead)
{ {
headY = AI.angle; extraHeadRot.x = AI.xAngle;
headX = AI.xAngle; extraHeadRot.y = AI.angle;
}
if (abs(AI.angle) < ANGLE(7.0f))
item->Pose.Orientation.y += AI.angle;
else if (AI.angle < 0)
item->Pose.Orientation.y -= ANGLE(7.0f);
else
item->Pose.Orientation.y += ANGLE(7.0f);
if (enemy->IsLara())
{
if (!creature->Flags && item->TestBits(JointBitType::Touch, MonkeyAttackJoints))
{
DoDamage(enemy, MONKEY_SWIPE_ATTACK_PLAYER_DAMAGE);
CreatureEffect(item, MonkeyBite, DoBloodSplat);
creature->Flags = 1;
}
}
else
{
if (!creature->Flags && enemy)
{
if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < CLICK(1) &&
abs(enemy->Pose.Position.y - item->Pose.Position.y) <= CLICK(1) &&
abs(enemy->Pose.Position.z - item->Pose.Position.z) < CLICK(1))
{
DoDamage(enemy, MONKEY_SWIPE_ATTACK_CREATURE_DAMAGE);
CreatureEffect(item, MonkeyBite, DoBloodSplat);
creature->Flags = 1;
}
}
}
break;
case MONKEY_STATE_JUMP_ATTACK:
creature->MaxTurn = 0;
if (AI.ahead)
{
extraHeadRot.x = AI.xAngle;
extraHeadRot.y = AI.angle;
} }
if (abs(AI.angle) < ANGLE(7.0f)) if (abs(AI.angle) < ANGLE(7.0f))
@ -422,11 +527,9 @@ namespace TEN::Entities::TR3
} }
else else
{ {
if (!creature->Flags && enemy) if (!creature->Flags && enemy != nullptr)
{ {
if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < CLICK(1) && if (Vector3i::Distance(item->Pose.Position, enemy->Pose.Position) <= CLICK(1))
abs(enemy->Pose.Position.y - item->Pose.Position.y) <= CLICK(1) &&
abs(enemy->Pose.Position.z - item->Pose.Position.z) < CLICK(1))
{ {
DoDamage(enemy, 20); DoDamage(enemy, 20);
CreatureEffect(item, MonkeyBite, DoBloodSplat); CreatureEffect(item, MonkeyBite, DoBloodSplat);
@ -437,55 +540,13 @@ namespace TEN::Entities::TR3
break; break;
case 13: case MONKEY_STATE_HIGH_JUMP_ATTACK:
creature->MaxTurn = 0; creature->MaxTurn = 0;
if (AI.ahead) if (AI.ahead)
{ {
headY = AI.angle; extraHeadRot.x = AI.xAngle;
headX = AI.xAngle; extraHeadRot.y = AI.angle;
}
if (abs(AI.angle) < ANGLE(7.0f))
item->Pose.Orientation.y += AI.angle;
else if (AI.angle < 0)
item->Pose.Orientation.y -= ANGLE(7.0f);
else
item->Pose.Orientation.y += ANGLE(7.0f);
if (enemy->IsLara())
{
if (!creature->Flags && item->TestBits(JointBitType::Touch, MonkeyAttackJoints))
{
DoDamage(enemy, 40);
CreatureEffect(item, MonkeyBite, DoBloodSplat);
creature->Flags = 1;
}
}
else
{
if (!creature->Flags && enemy)
{
if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < CLICK(1) &&
abs(enemy->Pose.Position.y - item->Pose.Position.y) <= CLICK(1) &&
abs(enemy->Pose.Position.z - item->Pose.Position.z) < CLICK(1))
{
DoDamage(enemy, 20);
CreatureEffect(item, MonkeyBite, DoBloodSplat);
creature->Flags = 1;
}
}
}
break;
case 14:
creature->MaxTurn = 0;
if (AI.ahead)
{
headX = AI.xAngle;
headY = AI.angle;
} }
if (abs(AI.angle) < ANGLE(7.0f)) if (abs(AI.angle) < ANGLE(7.0f))
@ -508,9 +569,7 @@ namespace TEN::Entities::TR3
{ {
if (creature->Flags != 1 && enemy) if (creature->Flags != 1 && enemy)
{ {
if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < CLICK(1) && if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= CLICK(1))
abs(enemy->Pose.Position.y - item->Pose.Position.y) <= CLICK(1) &&
abs(enemy->Pose.Position.z - item->Pose.Position.z) < CLICK(1))
{ {
DoDamage(enemy, 25); DoDamage(enemy, 25);
CreatureEffect(item, MonkeyBite, DoBloodSplat); CreatureEffect(item, MonkeyBite, DoBloodSplat);
@ -524,54 +583,42 @@ namespace TEN::Entities::TR3
} }
CreatureTilt(item, tilt); CreatureTilt(item, tilt);
CreatureJoint(item, 0, headY); CreatureJoint(item, 0, extraHeadRot.y);
CreatureJoint(item, 1, headX); CreatureJoint(item, 1, extraHeadRot.x);
CreatureJoint(item, 2, torsoY); CreatureJoint(item, 2, extraTorsoRot.y);
if (item->Animation.ActiveState < 15) if (item->Animation.ActiveState < MONKEY_STATE_VAULT_UP_1_BLOCK)
{ {
switch (CreatureVault(itemNumber, angle, 2, 128)) switch (CreatureVault(itemNumber, angle, 2, CLICK(0.5f)))
{ {
case 2: case 2:
SetAnimation(item, MONKEY_ANIM_VAULT_UP_0_POINT_2_BLOCKS);
creature->MaxTurn = 0; creature->MaxTurn = 0;
item->Animation.AnimNumber = Objects[ID_MONKEY].animIndex + 19;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 17;
break; break;
case 3: case 3:
SetAnimation(item, MONKEY_ANIM_VAULT_UP_0_POINT_3_BLOCKS);
creature->MaxTurn = 0; creature->MaxTurn = 0;
item->Animation.AnimNumber = Objects[ID_MONKEY].animIndex + 18;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 16;
break; break;
case 4: case 4:
SetAnimation(item, MONKEY_ANIM_VAULT_UP_1_BLOCK);
creature->MaxTurn = 0; creature->MaxTurn = 0;
item->Animation.AnimNumber = Objects[ID_MONKEY].animIndex + 17;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 15;
break; break;
case -2: case -2:
SetAnimation(item, MONKEY_ANIM_VAULT_DOWN_0_POINT_2_BLOCKS);
creature->MaxTurn = 0; creature->MaxTurn = 0;
item->Animation.AnimNumber = Objects[ID_MONKEY].animIndex + 22;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 20;
break; break;
case -3: case -3:
SetAnimation(item, MONKEY_ANIM_VAULT_DOWN_0_POINT_3_BLOCKS);
creature->MaxTurn = 0; creature->MaxTurn = 0;
item->Animation.AnimNumber = Objects[ID_MONKEY].animIndex + 21;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 19;
break; break;
case -4: case -4:
SetAnimation(item, MONKEY_ANIM_VAULT_DOWN_1_BLOCK);
creature->MaxTurn = 0; creature->MaxTurn = 0;
item->Animation.AnimNumber = Objects[ID_MONKEY].animIndex + 20;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 18;
break; break;
} }
} }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
void InitialiseMonkey(short itemNumber); void InitialiseMonkey(short itemNumber);
void MonkeyControl(short itemNumber); void MonkeyControl(short itemNumber);

View file

@ -16,13 +16,14 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
namespace TEN::Entities::TR3 using namespace TEN::Math::Random;
namespace TEN::Entities::Creatures::TR3
{ {
const auto MPGunBite = BiteInfo(Vector3(0.0f, 160.0f, 40.0f), 13); const auto MPGunBite = BiteInfo(Vector3(0.0f, 160.0f, 40.0f), 13);
enum MPGunState enum MPGunState
{ {
MPGUN_STATE_NONE = 0,
MPGUN_STATE_WAIT = 1, MPGUN_STATE_WAIT = 1,
MPGUN_STATE_WALK = 2, MPGUN_STATE_WALK = 2,
MPGUN_STATE_RUN = 3, MPGUN_STATE_RUN = 3,
@ -59,11 +60,10 @@ namespace TEN::Entities::TR3
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0; short angle = 0;
short tilt = 0; short tilt = 0;
short torsoX = 0; short head = 0;
short torsoY = 0; auto extraTorsoRot = EulerAngles::Zero;
if (creature->FiredWeapon) if (creature->FiredWeapon)
{ {
@ -75,8 +75,8 @@ namespace TEN::Entities::TR3
if (item->BoxNumber != NO_BOX && (g_Level.Boxes[item->BoxNumber].flags & BLOCKED)) if (item->BoxNumber != NO_BOX && (g_Level.Boxes[item->BoxNumber].flags & BLOCKED))
{ {
DoLotsOfBlood(item->Pose.Position.x, item->Pose.Position.y - (GetRandomControl() & 255) - 32, item->Pose.Position.z, (GetRandomControl() & 127) + 128, GetRandomControl() * 2, item->RoomNumber, 3);
DoDamage(item, 20); DoDamage(item, 20);
DoLotsOfBlood(item->Pose.Position.x, item->Pose.Position.y - (GetRandomControl() & 255) - 32, item->Pose.Position.z, (GetRandomControl() & 127) + 128, GetRandomControl() * 2, item->RoomNumber, 3);
} }
AI_INFO AI; AI_INFO AI;
@ -90,7 +90,7 @@ namespace TEN::Entities::TR3
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase; item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 13; item->Animation.ActiveState = 13;
} }
else if (!(GetRandomControl() & 3) && item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 1) else if (TestProbability(0.25f) && item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 1)
{ {
CreatureAIInfo(item, &AI); CreatureAIInfo(item, &AI);
@ -100,8 +100,8 @@ namespace TEN::Entities::TR3
AI.angle < ANGLE(45.0f)) AI.angle < ANGLE(45.0f))
{ {
head = AI.angle; head = AI.angle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
ShotLara(item, &AI, MPGunBite, torsoY, 32); ShotLara(item, &AI, MPGunBite, extraTorsoRot.y, 32);
SoundEffect(SFX_TR3_OIL_SMG_FIRE, &item->Pose, SoundEnvironment::Land, 1.0f, 0.7f); SoundEffect(SFX_TR3_OIL_SMG_FIRE, &item->Pose, SoundEnvironment::Land, 1.0f, 0.7f);
creature->FiredWeapon = 1; creature->FiredWeapon = 1;
} }
@ -219,10 +219,9 @@ namespace TEN::Entities::TR3
item->Animation.TargetState = MPGUN_STATE_RUN; item->Animation.TargetState = MPGUN_STATE_RUN;
else if (Targetable(item, &AI)) else if (Targetable(item, &AI))
{ {
int random = GetRandomControl(); if (TestProbability(0.25f))
if (random < 0x2000)
item->Animation.TargetState = MPGUN_STATE_SHOOT_1; item->Animation.TargetState = MPGUN_STATE_SHOOT_1;
else if (random < 0x4000) else if (TestProbability(0.5f))
item->Animation.TargetState = MPGUN_STATE_SHOOT_2; item->Animation.TargetState = MPGUN_STATE_SHOOT_2;
else else
item->Animation.TargetState = MPGUN_STATE_AIM_3; item->Animation.TargetState = MPGUN_STATE_AIM_3;
@ -304,17 +303,18 @@ namespace TEN::Entities::TR3
case MPGUN_STATE_AIM_1: case MPGUN_STATE_AIM_1:
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (item->Animation.AnimNumber == Objects[ID_MP_WITH_GUN].animIndex + 12 || if (item->Animation.AnimNumber == Objects[ID_MP_WITH_GUN].animIndex + 12 ||
(item->Animation.AnimNumber == Objects[ID_MP_WITH_GUN].animIndex + 1 && item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 10)) (item->Animation.AnimNumber == Objects[ID_MP_WITH_GUN].animIndex + 1 &&
item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 10))
{ {
if (!ShotLara(item, &AI, MPGunBite, torsoY, 32)) if (!ShotLara(item, &AI, MPGunBite, extraTorsoRot.y, 32))
item->Animation.RequiredState = MPGUN_STATE_WAIT; item->Animation.RequiredState = MPGUN_STATE_WAIT;
} }
else if (item->HitStatus && !(GetRandomControl() & 0x3) && cover) else if (item->HitStatus && TestProbability(0.25f) && cover)
{ {
item->Animation.RequiredState = MPGUN_STATE_CROUCH; item->Animation.RequiredState = MPGUN_STATE_CROUCH;
item->Animation.TargetState = MPGUN_STATE_WAIT; item->Animation.TargetState = MPGUN_STATE_WAIT;
@ -325,8 +325,8 @@ namespace TEN::Entities::TR3
case MPGUN_STATE_SHOOT_1: case MPGUN_STATE_SHOOT_1:
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (item->Animation.RequiredState == MPGUN_STATE_WAIT) if (item->Animation.RequiredState == MPGUN_STATE_WAIT)
@ -337,16 +337,16 @@ namespace TEN::Entities::TR3
case MPGUN_STATE_SHOOT_2: case MPGUN_STATE_SHOOT_2:
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase) if (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase)
{ {
if (!ShotLara(item, &AI, MPGunBite, torsoY, 32)) if (!ShotLara(item, &AI, MPGunBite, extraTorsoRot.y, 32))
item->Animation.TargetState = MPGUN_STATE_WAIT; item->Animation.TargetState = MPGUN_STATE_WAIT;
} }
else if (item->HitStatus && !(GetRandomControl() & 0x3) && cover) else if (item->HitStatus && TestProbability(0.25f) && cover)
{ {
item->Animation.RequiredState = MPGUN_STATE_CROUCH; item->Animation.RequiredState = MPGUN_STATE_CROUCH;
item->Animation.TargetState = MPGUN_STATE_WAIT; item->Animation.TargetState = MPGUN_STATE_WAIT;
@ -358,17 +358,17 @@ namespace TEN::Entities::TR3
case MPGUN_STATE_SHOOT_3B: case MPGUN_STATE_SHOOT_3B:
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase || if (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase ||
item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 11) item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 11)
{ {
if (!ShotLara(item, &AI, MPGunBite, torsoY, 32)) if (!ShotLara(item, &AI, MPGunBite, extraTorsoRot.y, 32))
item->Animation.TargetState = MPGUN_STATE_WAIT; item->Animation.TargetState = MPGUN_STATE_WAIT;
} }
else if (item->HitStatus && !(GetRandomControl() & 0x3) && cover) else if (item->HitStatus && TestProbability(0.25f) && cover)
{ {
item->Animation.RequiredState = MPGUN_STATE_CROUCH; item->Animation.RequiredState = MPGUN_STATE_CROUCH;
item->Animation.TargetState = MPGUN_STATE_WAIT; item->Animation.TargetState = MPGUN_STATE_WAIT;
@ -379,17 +379,19 @@ namespace TEN::Entities::TR3
case MPGUN_STATE_AIM_4: case MPGUN_STATE_AIM_4:
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if ((item->Animation.AnimNumber == Objects[ID_MP_WITH_GUN].animIndex + 18 && item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 17) || if ((item->Animation.AnimNumber == Objects[ID_MP_WITH_GUN].animIndex + 18 &&
(item->Animation.AnimNumber == Objects[ID_MP_WITH_GUN].animIndex + 19 && item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 6)) item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 17) ||
(item->Animation.AnimNumber == Objects[ID_MP_WITH_GUN].animIndex + 19 &&
item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 6))
{ {
if (!ShotLara(item, &AI, MPGunBite, torsoY, 32)) if (!ShotLara(item, &AI, MPGunBite, extraTorsoRot.y, 32))
item->Animation.RequiredState = MPGUN_STATE_WALK; item->Animation.RequiredState = MPGUN_STATE_WALK;
} }
else if (item->HitStatus && !(GetRandomControl() & 0x3) && cover) else if (item->HitStatus && TestProbability(0.25f) && cover)
{ {
item->Animation.RequiredState = MPGUN_STATE_CROUCH; item->Animation.RequiredState = MPGUN_STATE_CROUCH;
item->Animation.TargetState = MPGUN_STATE_WAIT; item->Animation.TargetState = MPGUN_STATE_WAIT;
@ -404,8 +406,8 @@ namespace TEN::Entities::TR3
case MPGUN_STATE_SHOOT_4B: case MPGUN_STATE_SHOOT_4B:
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (item->Animation.RequiredState == MPGUN_STATE_WALK) if (item->Animation.RequiredState == MPGUN_STATE_WALK)
@ -413,7 +415,7 @@ namespace TEN::Entities::TR3
if (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 16) if (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase + 16)
{ {
if (!ShotLara(item, &AI, MPGunBite, torsoY, 32)) if (!ShotLara(item, &AI, MPGunBite, extraTorsoRot.y, 32))
item->Animation.TargetState = MPGUN_STATE_WALK; item->Animation.TargetState = MPGUN_STATE_WALK;
} }
@ -430,7 +432,7 @@ namespace TEN::Entities::TR3
if (Targetable(item, &AI)) if (Targetable(item, &AI))
item->Animation.TargetState = MPGUN_STATE_CROUCH_AIM; item->Animation.TargetState = MPGUN_STATE_CROUCH_AIM;
else if (item->HitStatus || !cover || (AI.ahead && !(GetRandomControl() & 0x1F))) else if (item->HitStatus || !cover || (AI.ahead && TestProbability(1.0f / 30)))
item->Animation.TargetState = MPGUN_STATE_STAND; item->Animation.TargetState = MPGUN_STATE_STAND;
else else
item->Animation.TargetState = MPGUN_STATE_CROUCH_WALK; item->Animation.TargetState = MPGUN_STATE_CROUCH_WALK;
@ -441,7 +443,7 @@ namespace TEN::Entities::TR3
creature->MaxTurn = ANGLE(1.0f); creature->MaxTurn = ANGLE(1.0f);
if (AI.ahead) if (AI.ahead)
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
if (Targetable(item, &AI)) if (Targetable(item, &AI))
item->Animation.TargetState = MPGUN_STATE_CROUCH_SHOT; item->Animation.TargetState = MPGUN_STATE_CROUCH_SHOT;
@ -452,11 +454,11 @@ namespace TEN::Entities::TR3
case MPGUN_STATE_CROUCH_SHOT: case MPGUN_STATE_CROUCH_SHOT:
if (AI.ahead) if (AI.ahead)
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
if (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase) if (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase)
{ {
if (!ShotLara(item, &AI, MPGunBite, torsoY, 32) || !(GetRandomControl() & 0x7)) if (!ShotLara(item, &AI, MPGunBite, extraTorsoRot.y, 32) || TestProbability(0.125f))
item->Animation.TargetState = MPGUN_STATE_CROUCHED; item->Animation.TargetState = MPGUN_STATE_CROUCHED;
} }
@ -468,7 +470,7 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
head = AI.angle; head = AI.angle;
if (Targetable(item, &AI) || item->HitStatus || !cover || (AI.ahead && !(GetRandomControl() & 0x1F))) if (Targetable(item, &AI) || item->HitStatus || !cover || (AI.ahead && TestProbability(1.0f / 30)))
item->Animation.TargetState = MPGUN_STATE_CROUCHED; item->Animation.TargetState = MPGUN_STATE_CROUCHED;
break; break;
@ -476,8 +478,8 @@ namespace TEN::Entities::TR3
} }
CreatureTilt(item, tilt); CreatureTilt(item, tilt);
CreatureJoint(item, 0, torsoY); CreatureJoint(item, 0, extraTorsoRot.y);
CreatureJoint(item, 1, torsoX); CreatureJoint(item, 1, extraTorsoRot.x);
CreatureJoint(item, 2, head); CreatureJoint(item, 2, head);
CreatureAnimation(itemNumber, angle, tilt); CreatureAnimation(itemNumber, angle, tilt);
} }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
void MPGunControl(short itemNumber); void MPGunControl(short itemNumber);
} }

View file

@ -13,18 +13,18 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
const auto MPStickBite1 = BiteInfo(Vector3(247.0f, 10.0f, 11.0f), 13); const auto MPStickBite1 = BiteInfo(Vector3(247.0f, 10.0f, 11.0f), 13);
const auto MPStickBite2 = BiteInfo(Vector3(0.0f, 0.0f, 100.0f), 6); const auto MPStickBite2 = BiteInfo(Vector3(0.0f, 0.0f, 100.0f), 6);
const vector<int> MPStickPunchAttackJoints = { 10, 13 }; const vector<int> MPStickPunchAttackJoints = { 10, 13 };
const vector<int> MPStickKickAttackJoints = { 5, 6 }; const vector<int> MPStickKickAttackJoints = { 5, 6 };
enum MPStickState enum MPStickState
{ {
MPSTICK_STATE_NONE,
MPSTICK_STATE_STOP, MPSTICK_STATE_STOP,
MPSTICK_STATE_WALK, MPSTICK_STATE_WALK,
MPSTICK_STATE_PUNCH2, MPSTICK_STATE_PUNCH2,
@ -54,10 +54,7 @@ namespace TEN::Entities::TR3
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
ClearItem(itemNumber); ClearItem(itemNumber);
SetAnimation(item, 6);
item->Animation.AnimNumber = Objects[ID_MP_WITH_STICK].animIndex + 6;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = item->Animation.TargetState = MPSTICK_STATE_STOP;
} }
void MPStickControl(short itemNumber) void MPStickControl(short itemNumber)
@ -68,11 +65,10 @@ namespace TEN::Entities::TR3
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0; short angle = 0;
short tilt = 0; short tilt = 0;
short torsoX = 0; short head = 0;
short torsoY = 0; auto extraTorsoRot = EulerAngles::Zero;
if (item->BoxNumber != NO_BOX && (g_Level.Boxes[item->BoxNumber].flags & BLOCKED)) if (item->BoxNumber != NO_BOX && (g_Level.Boxes[item->BoxNumber].flags & BLOCKED))
{ {
@ -85,9 +81,7 @@ namespace TEN::Entities::TR3
{ {
if (item->Animation.ActiveState != MPSTICK_STATE_DEATH) if (item->Animation.ActiveState != MPSTICK_STATE_DEATH)
{ {
item->Animation.AnimNumber = Objects[ID_MP_WITH_STICK].animIndex + 26; SetAnimation(item, 26);
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = MPSTICK_STATE_DEATH;
creature->LOT.Step = 256; creature->LOT.Step = 256;
} }
} }
@ -103,7 +97,7 @@ namespace TEN::Entities::TR3
int dz = LaraItem->Pose.Position.z - item->Pose.Position.z; int dz = LaraItem->Pose.Position.z - item->Pose.Position.z;
laraAI.distance = pow(dx, 2) + pow(dx, 2); laraAI.distance = pow(dx, 2) + pow(dx, 2);
int bestDistance = 0x7fffffff; int bestDistance = INT_MAX;
for (int slot = 0; slot < ActiveCreatures.size(); slot++) for (int slot = 0; slot < ActiveCreatures.size(); slot++)
{ {
auto* currentCreature = ActiveCreatures[slot]; auto* currentCreature = ActiveCreatures[slot];
@ -184,7 +178,7 @@ namespace TEN::Entities::TR3
if (item->AIBits & GUARD) if (item->AIBits & GUARD)
{ {
head = AIGuard(creature); head = AIGuard(creature);
if (!(GetRandomControl() & 0xFF)) if (TestProbability(1.0f / 256))
{ {
if (item->Animation.ActiveState == MPSTICK_STATE_STOP) if (item->Animation.ActiveState == MPSTICK_STATE_STOP)
item->Animation.TargetState = MPSTICK_STATE_WAIT; item->Animation.TargetState = MPSTICK_STATE_WAIT;
@ -194,7 +188,6 @@ namespace TEN::Entities::TR3
break; break;
} }
else if (item->AIBits & PATROL1) else if (item->AIBits & PATROL1)
item->Animation.TargetState = MPSTICK_STATE_WALK; item->Animation.TargetState = MPSTICK_STATE_WALK;
@ -240,7 +233,7 @@ namespace TEN::Entities::TR3
item->Animation.TargetState = MPSTICK_STATE_RUN; item->Animation.TargetState = MPSTICK_STATE_RUN;
else if (creature->Mood == MoodType::Bored) else if (creature->Mood == MoodType::Bored)
{ {
if (GetRandomControl() < 0x100) if (TestProbability(1.0f / 128))
{ {
item->Animation.RequiredState = MPSTICK_STATE_WAIT; item->Animation.RequiredState = MPSTICK_STATE_WAIT;
item->Animation.TargetState = MPSTICK_STATE_STOP; item->Animation.TargetState = MPSTICK_STATE_STOP;
@ -288,8 +281,8 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (AI.bite && AI.distance < pow(SECTOR(0.5f), 2)) if (AI.bite && AI.distance < pow(SECTOR(0.5f), 2))
@ -305,8 +298,8 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (AI.ahead && AI.distance < pow(SECTOR(1), 2)) if (AI.ahead && AI.distance < pow(SECTOR(1), 2))
@ -322,8 +315,8 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (AI.bite && AI.distance < pow(SECTOR(1.25f), 2)) if (AI.bite && AI.distance < pow(SECTOR(1.25f), 2))
@ -338,32 +331,30 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (enemy->IsLara()) if (creature->Enemy->IsLara())
{ {
if (!creature->Flags && item->TestBits(JointBitType::Touch, MPStickPunchAttackJoints)) if (!creature->Flags && item->TestBits(JointBitType::Touch, MPStickPunchAttackJoints))
{ {
CreatureEffect(item, MPStickBite1, DoBloodSplat);
DoDamage(enemy, 80); DoDamage(enemy, 80);
CreatureEffect(item, MPStickBite1, DoBloodSplat);
SoundEffect(SFX_TR4_LARA_THUD, &item->Pose); SoundEffect(SFX_TR4_LARA_THUD, &item->Pose);
creature->Flags = 1; creature->Flags = 1;
} }
} }
else else
{ {
if (!creature->Flags && enemy) if (!creature->Flags && enemy != nullptr)
{ {
if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < SECTOR(0.25f) && if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= SECTOR(0.25f))
abs(enemy->Pose.Position.y - item->Pose.Position.y) <= SECTOR(0.25f) &&
abs(enemy->Pose.Position.z - item->Pose.Position.z) < SECTOR(0.25f))
{ {
creature->Flags = 1;
CreatureEffect(item, MPStickBite1, DoBloodSplat);
DoDamage(enemy, 5); DoDamage(enemy, 5);
CreatureEffect(item, MPStickBite1, DoBloodSplat);
SoundEffect(SFX_TR4_LARA_THUD, &item->Pose); SoundEffect(SFX_TR4_LARA_THUD, &item->Pose);
creature->Flags = 1;
} }
} }
} }
@ -375,32 +366,30 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (enemy->IsLara()) if (creature->Enemy->IsLara())
{ {
if (!creature->Flags && item->TestBits(JointBitType::Touch, MPStickPunchAttackJoints)) if (!creature->Flags && item->TestBits(JointBitType::Touch, MPStickPunchAttackJoints))
{ {
DoDamage(creature->Enemy, 80);
CreatureEffect(item, MPStickBite1, DoBloodSplat); CreatureEffect(item, MPStickBite1, DoBloodSplat);
DoDamage(enemy, 80);
SoundEffect(SFX_TR4_LARA_THUD, &item->Pose); SoundEffect(SFX_TR4_LARA_THUD, &item->Pose);
creature->Flags = 1; creature->Flags = 1;
} }
} }
else else
{ {
if (!creature->Flags && enemy) if (!creature->Flags && creature->Enemy != nullptr)
{ {
if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < SECTOR(0.25f) && if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= SECTOR(0.25f))
abs(enemy->Pose.Position.y - item->Pose.Position.y) <= SECTOR(0.25f) &&
abs(enemy->Pose.Position.z - item->Pose.Position.z) < SECTOR(0.25f))
{ {
creature->Flags = 1; DoDamage(creature->Enemy, 5);
CreatureEffect(item, MPStickBite1, DoBloodSplat); CreatureEffect(item, MPStickBite1, DoBloodSplat);
DoDamage(enemy, 5);
SoundEffect(SFX_TR4_LARA_THUD, &item->Pose); SoundEffect(SFX_TR4_LARA_THUD, &item->Pose);
creature->Flags = 1;
} }
} }
} }
@ -415,32 +404,30 @@ namespace TEN::Entities::TR3
if (AI.ahead) if (AI.ahead)
{ {
torsoX = AI.xAngle; extraTorsoRot.x = AI.xAngle;
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
} }
if (enemy->IsLara()) if (creature->Enemy->IsLara())
{ {
if (creature->Flags != 2 && item->TestBits(JointBitType::Touch, MPStickPunchAttackJoints)) if (creature->Flags != 2 && item->TestBits(JointBitType::Touch, MPStickPunchAttackJoints))
{ {
DoDamage(creature->Enemy, 100);
CreatureEffect(item, MPStickBite1, DoBloodSplat); CreatureEffect(item, MPStickBite1, DoBloodSplat);
DoDamage(enemy, 100);
creature->Flags = 2;
SoundEffect(70, &item->Pose); SoundEffect(70, &item->Pose);
creature->Flags = 2;
} }
} }
else else
{ {
if (creature->Flags != 2 && enemy) if (creature->Flags != 2 && creature->Enemy)
{ {
if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < SECTOR(0.25f) && if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= SECTOR(0.25f))
abs(enemy->Pose.Position.y - item->Pose.Position.y) <= SECTOR(0.25f) &&
abs(enemy->Pose.Position.z - item->Pose.Position.z) < SECTOR(0.25f))
{ {
creature->Flags = 2; DoDamage(creature->Enemy, 6);
CreatureEffect(item, MPStickBite1, DoBloodSplat); CreatureEffect(item, MPStickBite1, DoBloodSplat);
DoDamage(enemy, 6);
SoundEffect(SFX_TR4_LARA_THUD, &item->Pose); SoundEffect(SFX_TR4_LARA_THUD, &item->Pose);
creature->Flags = 2;
} }
} }
} }
@ -451,32 +438,30 @@ namespace TEN::Entities::TR3
creature->MaxTurn = ANGLE(6.0f); creature->MaxTurn = ANGLE(6.0f);
if (AI.ahead) if (AI.ahead)
torsoY = AI.angle; extraTorsoRot.y = AI.angle;
if (enemy->IsLara()) if (creature->Enemy->IsLara())
{ {
if (creature->Flags != 1 && item->TestBits(JointBitType::Touch, MPStickKickAttackJoints) && if (creature->Flags != 1 && item->TestBits(JointBitType::Touch, MPStickKickAttackJoints) &&
item->Animation.FrameNumber > g_Level.Anims[item->Animation.AnimNumber].frameBase + 8) item->Animation.FrameNumber > g_Level.Anims[item->Animation.AnimNumber].frameBase + 8)
{ {
DoDamage(creature->Enemy, 150);
CreatureEffect(item, MPStickBite2, DoBloodSplat); CreatureEffect(item, MPStickBite2, DoBloodSplat);
DoDamage(enemy, 150);
SoundEffect(SFX_TR4_LARA_THUD, &item->Pose); SoundEffect(SFX_TR4_LARA_THUD, &item->Pose);
creature->Flags = 1; creature->Flags = 1;
} }
} }
else else
{ {
if (!creature->Flags != 1 && enemy && if (!creature->Flags != 1 && creature->Enemy &&
item->Animation.FrameNumber > g_Level.Anims[item->Animation.AnimNumber].frameBase + 8) item->Animation.FrameNumber > g_Level.Anims[item->Animation.AnimNumber].frameBase + 8)
{ {
if (abs(enemy->Pose.Position.x - item->Pose.Position.x) < SECTOR(0.25f) && if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= SECTOR(0.25f))
abs(enemy->Pose.Position.y - item->Pose.Position.y) <= SECTOR(0.25f) &&
abs(enemy->Pose.Position.z - item->Pose.Position.z) < SECTOR(0.25f))
{ {
creature->Flags = 1; DoDamage(creature->Enemy, 9);
CreatureEffect(item, MPStickBite2, DoBloodSplat); CreatureEffect(item, MPStickBite2, DoBloodSplat);
DoDamage(enemy, 9);
SoundEffect(SFX_TR4_LARA_THUD, &item->Pose); SoundEffect(SFX_TR4_LARA_THUD, &item->Pose);
creature->Flags = 1;
} }
} }
} }
@ -486,8 +471,8 @@ namespace TEN::Entities::TR3
} }
CreatureTilt(item, tilt); CreatureTilt(item, tilt);
CreatureJoint(item, 0, torsoY); CreatureJoint(item, 0, extraTorsoRot.y);
CreatureJoint(item, 1, torsoX); CreatureJoint(item, 1, extraTorsoRot.x);
CreatureJoint(item, 2, head); CreatureJoint(item, 2, head);
if (item->Animation.ActiveState < MPSTICK_STATE_DEATH) if (item->Animation.ActiveState < MPSTICK_STATE_DEATH)

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
void InitialiseMPStick(short itemNumber); void InitialiseMPStick(short itemNumber);
void MPStickControl(short itemNumber); void MPStickControl(short itemNumber);

View file

@ -16,10 +16,17 @@
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
constexpr auto RAPTOR_ATTACK_DAMAGE = 100; constexpr auto RAPTOR_ATTACK_DAMAGE = 100;
constexpr auto RAPTOR_BITE_ATTACK_RANGE = SQUARE(585);
constexpr auto RAPTOR_JUMP_ATTACK_RANGE = SQUARE(SECTOR(1.5f));
constexpr auto RAPTOR_RUN_ATTACK_RANGE = SQUARE(SECTOR(1.5f));
constexpr auto RAPTOR_ROAR_CHANCE = 1.0f / 256;
constexpr auto RAPTOR_SWITCH_TARGET_CHANCE = 1.0f / 128;
#define RAPTOR_WALK_TURN_RATE_MAX ANGLE(2.0f) #define RAPTOR_WALK_TURN_RATE_MAX ANGLE(2.0f)
#define RAPTOR_RUN_TURN_RATE_MAX ANGLE(2.0f) #define RAPTOR_RUN_TURN_RATE_MAX ANGLE(2.0f)
#define RAPTOR_ATTACK_TURN_RATE_MAX ANGLE(2.0f) #define RAPTOR_ATTACK_TURN_RATE_MAX ANGLE(2.0f)
@ -74,10 +81,10 @@ namespace TEN::Entities::TR3
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short head = 0;
short neck = 0;
short angle = 0; short angle = 0;
short tilt = 0; short tilt = 0;
short head = 0;
short neck = 0;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
@ -86,14 +93,13 @@ namespace TEN::Entities::TR3
} }
else else
{ {
if (creature->Enemy == nullptr || !(GetRandomControl() & 0x7F)) // TODO: Probability is 0.004f or 0.996f? if (creature->Enemy == nullptr || TestProbability(RAPTOR_SWITCH_TARGET_CHANCE))
{ {
ItemInfo* nearestItem = nullptr; ItemInfo* nearestItem = nullptr;
int minDistance = INT_MAX; int minDistance = INT_MAX;
for (int i = 0; i < ActiveCreatures.size(); i++) for (auto* currentCreature : ActiveCreatures)
{ {
auto* currentCreature = ActiveCreatures[i];
if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber) if (currentCreature->ItemNumber == NO_ITEM || currentCreature->ItemNumber == itemNumber)
{ {
currentCreature++; currentCreature++;
@ -106,7 +112,7 @@ namespace TEN::Entities::TR3
int y = (targetItem->Pose.Position.y - item->Pose.Position.y) / 64; int y = (targetItem->Pose.Position.y - item->Pose.Position.y) / 64;
int z = (targetItem->Pose.Position.z - item->Pose.Position.z) / 64; int z = (targetItem->Pose.Position.z - item->Pose.Position.z) / 64;
int distance = pow(x, 2) + pow(y, 2) + pow(z, 2); int distance = SQUARE(x) + SQUARE(y) + SQUARE(z);
if (distance < minDistance && item->HitPoints > 0) if (distance < minDistance && item->HitPoints > 0)
{ {
nearestItem = targetItem; nearestItem = targetItem;
@ -118,7 +124,7 @@ namespace TEN::Entities::TR3
if (nearestItem != nullptr && if (nearestItem != nullptr &&
(nearestItem->ObjectNumber != ID_RAPTOR || (nearestItem->ObjectNumber != ID_RAPTOR ||
(TestProbability(0.03f) && minDistance < pow(SECTOR(2), 2)))) (TestProbability(1.0f / 30) && minDistance < SQUARE(SECTOR(2)))))
{ {
creature->Enemy = nearestItem; creature->Enemy = nearestItem;
} }
@ -127,7 +133,7 @@ namespace TEN::Entities::TR3
int y = (LaraItem->Pose.Position.y - item->Pose.Position.y) / 64; int y = (LaraItem->Pose.Position.y - item->Pose.Position.y) / 64;
int z = (LaraItem->Pose.Position.z - item->Pose.Position.z) / 64; int z = (LaraItem->Pose.Position.z - item->Pose.Position.z) / 64;
int distance = pow(x, 2) + pow(y, 2) + pow(z, 2); int distance = SQUARE(x) + SQUARE(y) + SQUARE(z);
if (distance <= minDistance) if (distance <= minDistance)
creature->Enemy = LaraItem; creature->Enemy = LaraItem;
} }
@ -164,11 +170,11 @@ namespace TEN::Entities::TR3
item->Animation.TargetState = RAPTOR_STATE_ROAR; item->Animation.TargetState = RAPTOR_STATE_ROAR;
} }
else if (item->TestBits(JointBitType::Touch, RaptorAttackJoints) || else if (item->TestBits(JointBitType::Touch, RaptorAttackJoints) ||
(AI.distance < pow(585, 2) && AI.bite)) (AI.distance < RAPTOR_BITE_ATTACK_RANGE && AI.bite))
{ {
item->Animation.TargetState = RAPTOR_STATE_BITE_ATTACK; item->Animation.TargetState = RAPTOR_STATE_BITE_ATTACK;
} }
else if (AI.bite && AI.distance < pow(SECTOR(1.5f), 2)) else if (AI.bite && AI.distance < RAPTOR_JUMP_ATTACK_RANGE)
item->Animation.TargetState = RAPTOR_STATE_JUMP_ATTACK; item->Animation.TargetState = RAPTOR_STATE_JUMP_ATTACK;
else if (creature->Mood == MoodType::Escape && else if (creature->Mood == MoodType::Escape &&
Lara.TargetEntity != item && AI.ahead && !item->HitStatus) Lara.TargetEntity != item && AI.ahead && !item->HitStatus)
@ -188,7 +194,7 @@ namespace TEN::Entities::TR3
if (creature->Mood != MoodType::Bored) if (creature->Mood != MoodType::Bored)
item->Animation.TargetState = RAPTOR_STATE_IDLE; item->Animation.TargetState = RAPTOR_STATE_IDLE;
else if (AI.ahead && TestProbability(0.004f)) else if (AI.ahead && TestProbability(RAPTOR_ROAR_CHANCE))
{ {
item->Animation.TargetState = RAPTOR_STATE_IDLE; item->Animation.TargetState = RAPTOR_STATE_IDLE;
item->Animation.RequiredState = RAPTOR_STATE_ROAR; item->Animation.RequiredState = RAPTOR_STATE_ROAR;
@ -210,7 +216,7 @@ namespace TEN::Entities::TR3
item->Animation.RequiredState = RAPTOR_STATE_ROAR; item->Animation.RequiredState = RAPTOR_STATE_ROAR;
creature->Flags &= ~2; creature->Flags &= ~2;
} }
else if (AI.bite && AI.distance < pow(SECTOR(1.5f), 2)) else if (AI.bite && AI.distance < RAPTOR_RUN_ATTACK_RANGE)
{ {
if (item->Animation.TargetState == RAPTOR_STATE_RUN_FORWARD) if (item->Animation.TargetState == RAPTOR_STATE_RUN_FORWARD)
{ {
@ -221,7 +227,7 @@ namespace TEN::Entities::TR3
} }
} }
else if (AI.ahead && creature->Mood != MoodType::Escape && else if (AI.ahead && creature->Mood != MoodType::Escape &&
TestProbability(0.004f)) TestProbability(RAPTOR_ROAR_CHANCE))
{ {
item->Animation.TargetState = RAPTOR_STATE_IDLE; item->Animation.TargetState = RAPTOR_STATE_IDLE;
item->Animation.RequiredState = RAPTOR_STATE_ROAR; item->Animation.RequiredState = RAPTOR_STATE_ROAR;
@ -257,10 +263,7 @@ namespace TEN::Entities::TR3
{ {
if (!(creature->Flags & 1) && creature->Enemy != nullptr) if (!(creature->Flags & 1) && creature->Enemy != nullptr)
{ {
auto direction = creature->Enemy->Pose.Position - item->Pose.Position; if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= SECTOR(0.5f))
if (abs(direction.x) < SECTOR(0.5f) &&
abs(direction.y) < SECTOR(0.5f) &&
abs(direction.z) < SECTOR(0.5f))
{ {
if (creature->Enemy->HitPoints <= 0) if (creature->Enemy->HitPoints <= 0)
creature->Flags |= 2; creature->Flags |= 2;
@ -297,10 +300,7 @@ namespace TEN::Entities::TR3
{ {
if (!(creature->Flags & 1) && creature->Enemy != nullptr) if (!(creature->Flags & 1) && creature->Enemy != nullptr)
{ {
auto direction = creature->Enemy->Pose.Position - item->Pose.Position; if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= SECTOR(0.5f))
if (abs(direction.x) < SECTOR(0.5f) &&
abs(direction.y) < SECTOR(0.5f) &&
abs(direction.z) < SECTOR(0.5f))
{ {
if (creature->Enemy->HitPoints <= 0) if (creature->Enemy->HitPoints <= 0)
creature->Flags |= 2; creature->Flags |= 2;
@ -336,10 +336,7 @@ namespace TEN::Entities::TR3
{ {
if (!(creature->Flags & 1) && creature->Enemy != nullptr) if (!(creature->Flags & 1) && creature->Enemy != nullptr)
{ {
auto direction = creature->Enemy->Pose.Position - item->Pose.Position; if (Vector3i::Distance(item->Pose.Position, creature->Enemy->Pose.Position) <= SECTOR(0.5f))
if (abs(direction.x) < SECTOR(0.5f) &&
abs(direction.y) < SECTOR(0.5f) &&
abs(direction.z) < SECTOR(0.5f))
{ {
if (creature->Enemy->HitPoints <= 0) if (creature->Enemy->HitPoints <= 0)
creature->Flags |= 2; creature->Flags |= 2;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
void RaptorControl(short itemNumber); void RaptorControl(short itemNumber);
} }

View file

@ -13,7 +13,7 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
constexpr auto SCUBA_DIVER_ATTACK_DAMAGE = 50; constexpr auto SCUBA_DIVER_ATTACK_DAMAGE = 50;
@ -23,7 +23,6 @@ namespace TEN::Entities::TR3
enum ScubaDiverState enum ScubaDiverState
{ {
SDIVER_STATE_NONE = 0,
SDIVER_STATE_SWIM = 1, SDIVER_STATE_SWIM = 1,
SDIVER_STATE_TREAD_WATER_IDLE = 2, SDIVER_STATE_TREAD_WATER_IDLE = 2,
SDIVER_STATE_SWIM_SHOOT = 3, SDIVER_STATE_SWIM_SHOOT = 3,
@ -60,23 +59,23 @@ namespace TEN::Entities::TR3
static void ShootHarpoon(ItemInfo* item, Vector3i pos, short velocity, short yRot, short roomNumber) static void ShootHarpoon(ItemInfo* item, Vector3i pos, short velocity, short yRot, short roomNumber)
{ {
short harpoonItemNumber = CreateItem(); short harpoonItemNumber = CreateItem();
if (harpoonItemNumber != NO_ITEM) if (harpoonItemNumber == NO_ITEM)
{ return;
auto* harpoonItem = &g_Level.Items[harpoonItemNumber];
harpoonItem->ObjectNumber = ID_SCUBA_HARPOON; auto* harpoonItem = &g_Level.Items[harpoonItemNumber];
harpoonItem->RoomNumber = item->RoomNumber;
harpoonItem->Pose.Position = pos;
InitialiseItem(harpoonItemNumber); harpoonItem->ObjectNumber = ID_SCUBA_HARPOON;
harpoonItem->RoomNumber = item->RoomNumber;
harpoonItem->Pose.Position = pos;
harpoonItem->Animation.Velocity.z = 150; InitialiseItem(harpoonItemNumber);
harpoonItem->Pose.Orientation.x = 0;
harpoonItem->Pose.Orientation.y = yRot;
AddActiveItem(harpoonItemNumber); harpoonItem->Animation.Velocity.z = 150.0f;
harpoonItem->Status = ITEM_ACTIVE; harpoonItem->Pose.Orientation.x = 0;
} harpoonItem->Pose.Orientation.y = yRot;
AddActiveItem(harpoonItemNumber);
harpoonItem->Status = ITEM_ACTIVE;
} }
void ScubaHarpoonControl(short itemNumber) void ScubaHarpoonControl(short itemNumber)
@ -91,14 +90,7 @@ namespace TEN::Entities::TR3
} }
else else
{ {
int ox = item->Pose.Position.x; TranslateItem(item, item->Pose.Orientation, item->Animation.Velocity.z);
int oz = item->Pose.Position.z;
int velocity = item->Animation.Velocity.z * phd_cos(item->Pose.Orientation.x);
item->Pose.Position.z += velocity * phd_cos(item->Pose.Orientation.y);
item->Pose.Position.x += velocity * phd_sin(item->Pose.Orientation.y);
item->Pose.Position.y += -item->Animation.Velocity.z * phd_sin(item->Pose.Orientation.x);
auto probe = GetCollision(item); auto probe = GetCollision(item);
@ -123,7 +115,8 @@ namespace TEN::Entities::TR3
short head = 0; short head = 0;
short neck = 0; short neck = 0;
int waterHeight; int waterHeight = 0;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
if (item->Animation.ActiveState != SDIVER_STATE_DEATH) if (item->Animation.ActiveState != SDIVER_STATE_DEATH)
@ -140,22 +133,23 @@ namespace TEN::Entities::TR3
GetCreatureMood(item, &AI, false); GetCreatureMood(item, &AI, false);
CreatureMood(item, &AI, false); CreatureMood(item, &AI, false);
GameVector origin;
GameVector target;
bool shoot = false; bool shoot = false;
if (Lara.Control.WaterStatus == WaterStatus::Dry) if (Lara.Control.WaterStatus == WaterStatus::Dry)
{ {
origin.x = item->Pose.Position.x; auto origin = GameVector(
origin.y = item->Pose.Position.y - CLICK(1); item->Pose.Position.x,
origin.z = item->Pose.Position.z; item->Pose.Position.y - CLICK(1),
origin.roomNumber = item->RoomNumber; item->Pose.Position.z,
item->RoomNumber
target.x = LaraItem->Pose.Position.x; );
target.y = LaraItem->Pose.Position.y - (LARA_HEIGHT - 150); auto target = GameVector(
target.z = LaraItem->Pose.Position.z; LaraItem->Pose.Position.x,
LaraItem->Pose.Position.y - (LARA_HEIGHT - 150),
LaraItem->Pose.Position.z
);
shoot = LOS(&origin, &target); shoot = LOS(&origin, &target);
if (shoot) if (shoot)
creature->Target = LaraItem->Pose.Position; creature->Target = LaraItem->Pose.Position;
@ -164,19 +158,11 @@ namespace TEN::Entities::TR3
} }
else if (AI.angle > -ANGLE(45.0f) && AI.angle < ANGLE(45.0f)) else if (AI.angle > -ANGLE(45.0f) && AI.angle < ANGLE(45.0f))
{ {
origin.x = item->Pose.Position.x; auto origin = GameVector(item->Pose.Position, item->RoomNumber);
origin.y = item->Pose.Position.y; auto target = GameVector(LaraItem->Pose.Position);
origin.z = item->Pose.Position.z;
origin.roomNumber = item->RoomNumber;
target.x = LaraItem->Pose.Position.x;
target.y = LaraItem->Pose.Position.y;
target.z = LaraItem->Pose.Position.z;
shoot = LOS(&origin, &target); shoot = LOS(&origin, &target);
} }
else
shoot = false;
angle = CreatureTurn(item, creature->MaxTurn); angle = CreatureTurn(item, creature->MaxTurn);
waterHeight = GetWaterSurface(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber) + SECTOR(0.5f); waterHeight = GetWaterSurface(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber) + SECTOR(0.5f);
@ -202,7 +188,7 @@ namespace TEN::Entities::TR3
break; break;
case SDIVER_STATE_SWIM_AIM: case SDIVER_STATE_SWIM_AIM:
creature->Flags = NULL; creature->Flags = 0;
if (shoot) if (shoot)
neck = -AI.angle; neck = -AI.angle;
@ -270,7 +256,6 @@ namespace TEN::Entities::TR3
} }
break; break;
} }
} }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
void ScubaHarpoonControl(short itemNumber); void ScubaHarpoonControl(short itemNumber);
void ScubaControl(short itemNumber); void ScubaControl(short itemNumber);

View file

@ -19,7 +19,7 @@
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
constexpr auto SHIVA_GRAB_ATTACK_DAMAGE = 150; constexpr auto SHIVA_GRAB_ATTACK_DAMAGE = 150;
constexpr auto SHIVA_DOWNWARD_ATTACK_DAMAGE = 180; constexpr auto SHIVA_DOWNWARD_ATTACK_DAMAGE = 180;
@ -29,10 +29,10 @@ namespace TEN::Entities::TR3
#define SHIVA_WALK_TURN_RATE_MAX ANGLE(4.0f) #define SHIVA_WALK_TURN_RATE_MAX ANGLE(4.0f)
#define SHIVA_ATTACK_TURN_RATE_MAX ANGLE(4.0f) #define SHIVA_ATTACK_TURN_RATE_MAX ANGLE(4.0f)
const vector<int> ShivaAttackLeftJoints = { 10, 13 };
const vector<int> ShivaAttackRightJoints = { 22, 25 };
const auto ShivaBiteLeft = BiteInfo(Vector3(0.0f, 0.0f, 920.0f), 13); const auto ShivaBiteLeft = BiteInfo(Vector3(0.0f, 0.0f, 920.0f), 13);
const auto ShivaBiteRight = BiteInfo(Vector3(0.0f, 0.0f, 920.0f), 22); const auto ShivaBiteRight = BiteInfo(Vector3(0.0f, 0.0f, 920.0f), 22);
const vector<int> ShivaAttackLeftJoints = { 10, 13 };
const vector<int> ShivaAttackRightJoints = { 22, 25 };
enum ShivaState enum ShivaState
{ {
@ -81,7 +81,7 @@ namespace TEN::Entities::TR3
}; };
static void TriggerShivaSmoke(long x, long y, long z, long uw) void TriggerShivaSmoke(long x, long y, long z, long uw)
{ {
long dx = LaraItem->Pose.Position.x - x; long dx = LaraItem->Pose.Position.x - x;
long dz = LaraItem->Pose.Position.z - z; long dz = LaraItem->Pose.Position.z - z;
@ -169,7 +169,7 @@ namespace TEN::Entities::TR3
sptr->dSize = size; sptr->dSize = size;
} }
static void ShivaDamage(ItemInfo* item, CreatureInfo* creature, int damage) void ShivaDamage(ItemInfo* item, CreatureInfo* creature, int damage)
{ {
if (!(creature->Flags) && item->TestBits(JointBitType::Touch, ShivaAttackRightJoints)) if (!(creature->Flags) && item->TestBits(JointBitType::Touch, ShivaAttackRightJoints))
{ {
@ -208,14 +208,15 @@ namespace TEN::Entities::TR3
return; return;
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* shiva = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
bool laraAlive = LaraItem->HitPoints > 0; auto pos = Vector3i(0, 0, CLICK(1));
bool isLaraAlive = LaraItem->HitPoints > 0;
EulerAngles extraHeadRot;
EulerAngles extraTorsoRot;
short angle = 0; short angle = 0;
short tilt = 0; short tilt = 0;
EulerAngles extraHeadRot = EulerAngles::Zero;
EulerAngles extraTorsoRot = EulerAngles::Zero;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
@ -234,13 +235,13 @@ namespace TEN::Entities::TR3
GetCreatureMood(item, &AI, true); GetCreatureMood(item, &AI, true);
CreatureMood(item, &AI, true); CreatureMood(item, &AI, true);
if (shiva->Mood == MoodType::Escape) if (creature->Mood == MoodType::Escape)
{ {
shiva->Target.x = LaraItem->Pose.Position.x; creature->Target.x = LaraItem->Pose.Position.x;
shiva->Target.z = LaraItem->Pose.Position.z; creature->Target.z = LaraItem->Pose.Position.z;
} }
angle = CreatureTurn(item, shiva->MaxTurn); angle = CreatureTurn(item, creature->MaxTurn);
if (item->Animation.ActiveState != SHIVA_STATE_INACTIVE) if (item->Animation.ActiveState != SHIVA_STATE_INACTIVE)
item->MeshBits = ALL_JOINT_BITS; item->MeshBits = ALL_JOINT_BITS;
@ -251,15 +252,15 @@ namespace TEN::Entities::TR3
switch (item->Animation.ActiveState) switch (item->Animation.ActiveState)
{ {
case SHIVA_STATE_INACTIVE: case SHIVA_STATE_INACTIVE:
shiva->MaxTurn = 0; creature->MaxTurn = 0;
if (!shiva->Flags) if (!creature->Flags)
{ {
if (!item->MeshBits) if (!item->MeshBits)
effectMesh = 0; effectMesh = 0;
item->MeshBits = (item->MeshBits * 2) + 1; item->MeshBits = (item->MeshBits * 2) + 1;
shiva->Flags = 1; creature->Flags = 1;
pos = GetJointPosition(item, effectMesh++, pos); pos = GetJointPosition(item, effectMesh++, pos);
TriggerExplosionSparks(pos.x, pos.y, pos.z, 2, 0, 0, item->RoomNumber); TriggerExplosionSparks(pos.x, pos.y, pos.z, 2, 0, 0, item->RoomNumber);
@ -267,45 +268,45 @@ namespace TEN::Entities::TR3
} }
else else
shiva->Flags--; creature->Flags--;
if (item->MeshBits == 0x7FFFFFFF) if (item->MeshBits == 0x7FFFFFFF)
{ {
item->Animation.TargetState = SHIVA_STATE_IDLE; item->Animation.TargetState = SHIVA_STATE_IDLE;
shiva->Flags = -45; creature->Flags = -45;
effectMesh = 0; effectMesh = 0;
} }
break; break;
case SHIVA_STATE_IDLE: case SHIVA_STATE_IDLE:
shiva->MaxTurn = 0; creature->MaxTurn = 0;
if (AI.ahead) if (AI.ahead)
extraHeadRot.y = AI.angle; extraHeadRot.y = AI.angle;
if (shiva->Flags < 0) if (creature->Flags < 0)
{ {
shiva->Flags++; creature->Flags++;
TriggerShivaSmoke(item->Pose.Position.x + (GetRandomControl() & 0x5FF) - 0x300, pos.y - (GetRandomControl() & 0x5FF), item->Pose.Position.z + (GetRandomControl() & 0x5FF) - 0x300, 1); TriggerShivaSmoke(item->Pose.Position.x + (GetRandomControl() & 0x5FF) - 0x300, pos.y - (GetRandomControl() & 0x5FF), item->Pose.Position.z + (GetRandomControl() & 0x5FF) - 0x300, 1);
break; break;
} }
if (shiva->Flags == 1) if (creature->Flags == 1)
shiva->Flags = 0; creature->Flags = 0;
if (shiva->Mood == MoodType::Escape) if (creature->Mood == MoodType::Escape)
{ {
int x = item->Pose.Position.x + SECTOR(1) * phd_sin(item->Pose.Orientation.y + ANGLE(180.0f)); int x = item->Pose.Position.x + SECTOR(1) * phd_sin(item->Pose.Orientation.y + ANGLE(180.0f));
int z = item->Pose.Position.z + SECTOR(1) * phd_cos(item->Pose.Orientation.y + ANGLE(180.0f)); int z = item->Pose.Position.z + SECTOR(1) * phd_cos(item->Pose.Orientation.y + ANGLE(180.0f));
auto box = GetCollision(x, item->Pose.Position.y, z, item->RoomNumber).BottomBlock->Box; auto box = GetCollision(x, item->Pose.Position.y, z, item->RoomNumber).BottomBlock->Box;
if (box != NO_BOX && !(g_Level.Boxes[box].flags & BLOCKABLE) && !shiva->Flags) if (box != NO_BOX && !(g_Level.Boxes[box].flags & BLOCKABLE) && !creature->Flags)
item->Animation.TargetState = SHIVA_STATE_WALK_BACK; item->Animation.TargetState = SHIVA_STATE_WALK_BACK;
else else
item->Animation.TargetState = SHIVA_STATE_GUARD_IDLE; item->Animation.TargetState = SHIVA_STATE_GUARD_IDLE;
} }
else if (shiva->Mood == MoodType::Bored) else if (creature->Mood == MoodType::Bored)
{ {
if (TestProbability(0.0325f)) if (TestProbability(0.0325f))
item->Animation.TargetState = SHIVA_STATE_WALK_FORWARD; item->Animation.TargetState = SHIVA_STATE_WALK_FORWARD;
@ -313,17 +314,17 @@ namespace TEN::Entities::TR3
else if (AI.bite && AI.distance < pow(SECTOR(1.25f), 2)) else if (AI.bite && AI.distance < pow(SECTOR(1.25f), 2))
{ {
item->Animation.TargetState = SHIVA_STATE_GRAB_ATTACK; item->Animation.TargetState = SHIVA_STATE_GRAB_ATTACK;
shiva->Flags = 0; creature->Flags = 0;
} }
else if (AI.bite && AI.distance < pow(SECTOR(4) / 3, 2)) else if (AI.bite && AI.distance < pow(SECTOR(4) / 3, 2))
{ {
item->Animation.TargetState = SHIVA_STATE_DOWNWARD_ATTACK; item->Animation.TargetState = SHIVA_STATE_DOWNWARD_ATTACK;
shiva->Flags = 0; creature->Flags = 0;
} }
else if (item->HitStatus && AI.ahead) else if (item->HitStatus && AI.ahead)
{ {
item->Animation.TargetState = SHIVA_STATE_GUARD_IDLE; item->Animation.TargetState = SHIVA_STATE_GUARD_IDLE;
shiva->Flags = 4; creature->Flags = 4;
} }
else else
item->Animation.TargetState = SHIVA_STATE_WALK_FORWARD; item->Animation.TargetState = SHIVA_STATE_WALK_FORWARD;
@ -331,103 +332,103 @@ namespace TEN::Entities::TR3
break; break;
case SHIVA_STATE_GUARD_IDLE: case SHIVA_STATE_GUARD_IDLE:
shiva->MaxTurn = 0; creature->MaxTurn = 0;
if (AI.ahead) if (AI.ahead)
extraHeadRot.y = AI.angle; extraHeadRot.y = AI.angle;
if (item->HitStatus || shiva->Mood == MoodType::Escape) if (item->HitStatus || creature->Mood == MoodType::Escape)
shiva->Flags = 4; creature->Flags = 4;
if (AI.bite && AI.distance < pow(SECTOR(4) / 3, 2) || if (AI.bite && AI.distance < pow(SECTOR(4) / 3, 2) ||
(item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase && (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase &&
!shiva->Flags) || !creature->Flags) ||
!AI.ahead) !AI.ahead)
{ {
item->Animation.TargetState = SHIVA_STATE_IDLE; item->Animation.TargetState = SHIVA_STATE_IDLE;
shiva->Flags = 0; creature->Flags = 0;
} }
else if (shiva->Flags) else if (creature->Flags)
item->Animation.TargetState = SHIVA_STATE_GUARD_IDLE; item->Animation.TargetState = SHIVA_STATE_GUARD_IDLE;
if (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase && if (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase &&
shiva->Flags > 1) creature->Flags > 1)
{ {
shiva->Flags -= 2; creature->Flags -= 2;
} }
break; break;
case SHIVA_STATE_WALK_FORWARD: case SHIVA_STATE_WALK_FORWARD:
shiva->MaxTurn = SHIVA_WALK_TURN_RATE_MAX; creature->MaxTurn = SHIVA_WALK_TURN_RATE_MAX;
if (AI.ahead) if (AI.ahead)
extraHeadRot.y = AI.angle; extraHeadRot.y = AI.angle;
if (shiva->Mood == MoodType::Escape) if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = SHIVA_STATE_IDLE; item->Animation.TargetState = SHIVA_STATE_IDLE;
else if (shiva->Mood == MoodType::Bored) else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = SHIVA_STATE_IDLE; item->Animation.TargetState = SHIVA_STATE_IDLE;
else if (AI.bite && AI.distance < pow(SECTOR(4) / 3, 2)) else if (AI.bite && AI.distance < pow(SECTOR(4) / 3, 2))
{ {
item->Animation.TargetState = SHIVA_STATE_IDLE; item->Animation.TargetState = SHIVA_STATE_IDLE;
shiva->Flags = 0; creature->Flags = 0;
} }
else if (item->HitStatus) else if (item->HitStatus)
{ {
item->Animation.TargetState = SHIVA_STATE_WALK_FORWARD_GUARDING; item->Animation.TargetState = SHIVA_STATE_WALK_FORWARD_GUARDING;
shiva->Flags = 4; creature->Flags = 4;
} }
break; break;
case SHIVA_STATE_WALK_FORWARD_GUARDING: case SHIVA_STATE_WALK_FORWARD_GUARDING:
shiva->MaxTurn = SHIVA_WALK_TURN_RATE_MAX; creature->MaxTurn = SHIVA_WALK_TURN_RATE_MAX;
if (AI.ahead) if (AI.ahead)
extraHeadRot.y = AI.angle; extraHeadRot.y = AI.angle;
if (item->HitStatus) if (item->HitStatus)
shiva->Flags = 4; creature->Flags = 4;
if (AI.bite && AI.distance < pow(SECTOR(1.25f), 2) || if (AI.bite && AI.distance < pow(SECTOR(1.25f), 2) ||
(item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase && (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase &&
!shiva->Flags)) !creature->Flags))
{ {
item->Animation.TargetState = SHIVA_STATE_WALK_FORWARD; item->Animation.TargetState = SHIVA_STATE_WALK_FORWARD;
shiva->Flags = 0; creature->Flags = 0;
} }
else if (shiva->Flags) else if (creature->Flags)
item->Animation.TargetState = SHIVA_STATE_WALK_FORWARD_GUARDING; item->Animation.TargetState = SHIVA_STATE_WALK_FORWARD_GUARDING;
if (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase) if (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase)
shiva->Flags = 0; creature->Flags = 0;
break; break;
case SHIVA_STATE_WALK_BACK: case SHIVA_STATE_WALK_BACK:
shiva->MaxTurn = SHIVA_WALK_TURN_RATE_MAX; creature->MaxTurn = SHIVA_WALK_TURN_RATE_MAX;
if (AI.ahead) if (AI.ahead)
extraHeadRot.y = AI.angle; extraHeadRot.y = AI.angle;
if (AI.ahead && AI.distance < pow(SECTOR(4) / 3, 2) || if (AI.ahead && AI.distance < pow(SECTOR(4) / 3, 2) ||
(item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase && (item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameBase &&
!shiva->Flags)) !creature->Flags))
{ {
item->Animation.TargetState = SHIVA_STATE_IDLE; item->Animation.TargetState = SHIVA_STATE_IDLE;
} }
else if (item->HitStatus) else if (item->HitStatus)
{ {
item->Animation.TargetState = SHIVA_STATE_IDLE; item->Animation.TargetState = SHIVA_STATE_IDLE;
shiva->Flags = 4; creature->Flags = 4;
} }
break; break;
case SHIVA_STATE_GRAB_ATTACK: case SHIVA_STATE_GRAB_ATTACK:
shiva->MaxTurn = SHIVA_ATTACK_TURN_RATE_MAX; creature->MaxTurn = SHIVA_ATTACK_TURN_RATE_MAX;
if (AI.ahead) if (AI.ahead)
{ {
@ -435,22 +436,22 @@ namespace TEN::Entities::TR3
extraTorsoRot = EulerAngles(AI.xAngle, AI.angle, 0); extraTorsoRot = EulerAngles(AI.xAngle, AI.angle, 0);
} }
ShivaDamage(item, shiva, SHIVA_GRAB_ATTACK_DAMAGE); ShivaDamage(item, creature, SHIVA_GRAB_ATTACK_DAMAGE);
break; break;
case SHIVA_STATE_DOWNWARD_ATTACK: case SHIVA_STATE_DOWNWARD_ATTACK:
shiva->MaxTurn = SHIVA_ATTACK_TURN_RATE_MAX; creature->MaxTurn = SHIVA_ATTACK_TURN_RATE_MAX;
extraHeadRot.y = AI.angle; extraHeadRot.y = AI.angle;
extraTorsoRot.y = AI.angle; extraTorsoRot.y = AI.angle;
if (AI.xAngle > 0) if (AI.xAngle > 0)
extraTorsoRot.x = AI.xAngle; extraTorsoRot.x = AI.xAngle;
ShivaDamage(item, shiva, SHIVA_DOWNWARD_ATTACK_DAMAGE); ShivaDamage(item, creature, SHIVA_DOWNWARD_ATTACK_DAMAGE);
break; break;
case SHIVA_STATE_KILL: case SHIVA_STATE_KILL:
shiva->MaxTurn = 0; creature->MaxTurn = 0;
extraHeadRot = EulerAngles::Zero; extraHeadRot = EulerAngles::Zero;
extraTorsoRot = EulerAngles::Zero; extraTorsoRot = EulerAngles::Zero;
@ -466,23 +467,21 @@ namespace TEN::Entities::TR3
} }
} }
// Dispatch kill animation // Dispatch kill animation.
if (laraAlive && LaraItem->HitPoints <= 0) if (isLaraAlive && LaraItem->HitPoints <= 0)
{ {
item->Animation.TargetState = SHIVA_STATE_KILL; item->Animation.TargetState = SHIVA_STATE_KILL;
if (LaraItem->RoomNumber != item->RoomNumber) if (LaraItem->RoomNumber != item->RoomNumber)
ItemNewRoom(Lara.ItemNumber, item->RoomNumber); ItemNewRoom(Lara.ItemNumber, item->RoomNumber);
LaraItem->Pose.Position = item->Pose.Position;
LaraItem->Pose.Orientation = EulerAngles(0, item->Pose.Orientation.y, 0);
LaraItem->Animation.IsAirborne = false;
LaraItem->Animation.AnimNumber = Objects[ID_LARA_EXTRA_ANIMS].animIndex + LARA_ANIM_SHIVA_DEATH; LaraItem->Animation.AnimNumber = Objects[ID_LARA_EXTRA_ANIMS].animIndex + LARA_ANIM_SHIVA_DEATH;
LaraItem->Animation.FrameNumber = g_Level.Anims[LaraItem->Animation.AnimNumber].frameBase; LaraItem->Animation.FrameNumber = g_Level.Anims[LaraItem->Animation.AnimNumber].frameBase;
LaraItem->Animation.ActiveState = LS_DEATH; LaraItem->Animation.ActiveState = LS_DEATH;
LaraItem->Animation.TargetState = LS_DEATH; LaraItem->Animation.TargetState = LS_DEATH;
LaraItem->Animation.IsAirborne = false;
LaraItem->Pose.Position = item->Pose.Position;
LaraItem->Pose.Orientation = EulerAngles(0, item->Pose.Orientation.y, 0);
LaraItem->HitPoints = NOT_TARGETABLE; LaraItem->HitPoints = NOT_TARGETABLE;
Lara.Air = -1; Lara.Air = -1;
Lara.Control.HandStatus = HandStatus::Special; Lara.Control.HandStatus = HandStatus::Special;

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
void InitialiseShiva(short itemNumber); void InitialiseShiva(short itemNumber);
void ShivaControl(short itemNumber); void ShivaControl(short itemNumber);

View file

@ -9,7 +9,7 @@
#include "Sound/sound.h" #include "Sound/sound.h"
#include "Specific/level.h" #include "Specific/level.h"
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
static BOSS_STRUCT BossData; static BOSS_STRUCT BossData;

View file

@ -1,7 +1,7 @@
#pragma once #pragma once
#include "Game/items.h" #include "Game/items.h"
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
void ControlLaserBolts(short itemNumber); void ControlLaserBolts(short itemNumber);
void ControlLondBossPlasmaBall(short fxNumber); void ControlLondBossPlasmaBall(short fxNumber);

View file

@ -15,23 +15,52 @@
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
using std::vector; using std::vector;
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
constexpr auto TIGER_ATTACK_DAMAGE = 90; constexpr auto TIGER_ATTACK_DAMAGE = 90;
constexpr auto TIGER_BITE_ATTACK_RANGE = SQUARE(SECTOR(0.33f));
constexpr auto TIGER_POUNCE_ATTACK_RANGE = SQUARE(SECTOR(1));
constexpr auto TIGER_RUN_ATTACK_RANGE = SQUARE(SECTOR(1.5f));
constexpr auto TIGER_WALK_CHANCE = 0.035f;
constexpr auto TIGER_ROAR_CHANCE = 1.0f / 340;
const auto TigerBite = BiteInfo(Vector3(19.0f, -13.0f, 3.0f), 26); const auto TigerBite = BiteInfo(Vector3(19.0f, -13.0f, 3.0f), 26);
const vector<int> TigerAttackJoints = { 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26 }; const vector<int> TigerAttackJoints = { 14, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26 };
// TODO #define TIGER_WALK_TURN_RATE_MAX ANGLE(3.0f)
#define TIGER_RUN_TURN_RATE_MAX ANGLE(6.0f)
#define TIGER_POUNCE_ATTACK_TURN_RATE_MAX ANGLE(3.0f)
enum TigerState enum TigerState
{ {
TIGER_STATE_DEATH = 0,
TIGER_STATE_IDLE = 1,
TIGER_STATE_WALK_FORWARD = 2,
TIGER_STATE_RUN_FORWARD = 3,
// No state 4.
TIGER_STATE_ROAR = 5,
TIGER_STATE_BITE_ATTACK = 6,
TIGER_STATE_RUN_SWIPE_ATTACK = 7,
TIGER_STATE_POUNCE_ATTACK = 8
}; };
// TODO
enum TigerAnim enum TigerAnim
{ {
TIGER_ANIM_IDLE_TO_RUN_FORWARD = 0,
TIGER_ANIM_BITE_ATTACK = 1,
TIGER_ANIM_RUN_SWIPE_ATTACK = 2,
TIGER_ANIM_POUNCE_ATTACK_START = 3,
TIGER_ANIM_ROAR = 4,
TIGER_ANIM_RUN_FORWARD = 5,
TIGER_ANIM_RUN_FORWARD_TO_IDLE = 6,
TIGER_ANIM_IDLE = 7,
TIGER_ANIM_WALK_FORWARD = 8,
TIGER_ANIM_IDLE_TO_WALK_FORWARD = 9,
TIGER_ANIM_WALK_FORWARD_TO_IDLE = 10,
TIGER_ANIM_DEATH = 11,
TIGER_ANIM_POUNCE_ATTACK_END = 12
}; };
void TigerControl(short itemNumber) void TigerControl(short itemNumber)
@ -40,20 +69,16 @@ namespace TEN::Entities::TR3
return; return;
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
auto* info = GetCreatureInfo(item); auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0; short angle = 0;
short tilt = 0; short tilt = 0;
auto extraHeadRot = EulerAngles::Zero;
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
if (item->Animation.ActiveState != 9) if (item->Animation.ActiveState != TIGER_STATE_DEATH)
{ SetAnimation(item, TIGER_ANIM_DEATH);
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + 11;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 9;
}
} }
else else
{ {
@ -61,99 +86,105 @@ namespace TEN::Entities::TR3
CreatureAIInfo(item, &AI); CreatureAIInfo(item, &AI);
if (AI.ahead) if (AI.ahead)
head = AI.angle; extraHeadRot.y = AI.angle;
GetCreatureMood(item, &AI, 1); GetCreatureMood(item, &AI, true);
if (info->Alerted && AI.zoneNumber != AI.enemyZone) if (creature->Alerted && AI.zoneNumber != AI.enemyZone)
info->Mood = MoodType::Escape; creature->Mood = MoodType::Escape;
CreatureMood(item, &AI, 1); CreatureMood(item, &AI, true);
angle = CreatureTurn(item, info->MaxTurn); angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState) switch (item->Animation.ActiveState)
{ {
case 1: case TIGER_STATE_IDLE:
info->MaxTurn = 0; creature->MaxTurn = 0;
info->Flags = 0; creature->Flags = 0;
if (info->Mood == MoodType::Escape) if (creature->Mood == MoodType::Escape)
{ {
if (Lara.TargetEntity != item && AI.ahead) if (Lara.TargetEntity != item && AI.ahead)
item->Animation.TargetState = 1; item->Animation.TargetState = TIGER_STATE_IDLE;
else else
item->Animation.TargetState = 3; item->Animation.TargetState = TIGER_STATE_RUN_FORWARD;
} }
else if (info->Mood == MoodType::Bored) else if (creature->Mood == MoodType::Bored)
{ {
if (TestProbability(0.003f)) if (TestProbability(TIGER_ROAR_CHANCE))
item->Animation.TargetState = 5; item->Animation.TargetState = TIGER_STATE_ROAR;
else if (TestProbability(0.035f)) else if (TestProbability(TIGER_WALK_CHANCE))
item->Animation.TargetState = 2; item->Animation.TargetState = TIGER_STATE_WALK_FORWARD;
} }
else if (AI.bite && AI.distance < pow(340, 2)) else if (AI.bite && AI.distance < TIGER_BITE_ATTACK_RANGE)
item->Animation.TargetState = 6; item->Animation.TargetState = TIGER_STATE_BITE_ATTACK;
else if (AI.bite && AI.distance < pow(SECTOR(1), 2)) else if (AI.bite && AI.distance < TIGER_POUNCE_ATTACK_RANGE)
{ {
info->MaxTurn = ANGLE(3.0f); item->Animation.TargetState = TIGER_STATE_POUNCE_ATTACK;
item->Animation.TargetState = 8; creature->MaxTurn = TIGER_POUNCE_ATTACK_TURN_RATE_MAX;
} }
else if (item->Animation.RequiredState) else if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState; item->Animation.TargetState = item->Animation.RequiredState;
else if (info->Mood != MoodType::Attack && TestProbability(0.003f)) else if (creature->Mood != MoodType::Attack && TestProbability(TIGER_ROAR_CHANCE))
item->Animation.TargetState = 5; item->Animation.TargetState = TIGER_STATE_ROAR;
else else
item->Animation.TargetState = 3; item->Animation.TargetState = TIGER_STATE_RUN_FORWARD;
break; break;
case 2: case TIGER_STATE_WALK_FORWARD:
info->MaxTurn = ANGLE(3.0f); creature->MaxTurn = TIGER_WALK_TURN_RATE_MAX;
if (info->Mood == MoodType::Escape || info->Mood == MoodType::Attack) if (creature->Mood == MoodType::Escape ||
item->Animation.TargetState = 3; creature->Mood == MoodType::Attack)
else if (TestProbability(0.003f))
{ {
item->Animation.TargetState = 1; item->Animation.TargetState = TIGER_STATE_RUN_FORWARD;
item->Animation.RequiredState = 5; }
else if (TestProbability(TIGER_ROAR_CHANCE))
{
item->Animation.TargetState = TIGER_STATE_IDLE;
item->Animation.RequiredState = TIGER_STATE_ROAR;
} }
break; break;
case 3: case TIGER_STATE_RUN_FORWARD:
info->MaxTurn = ANGLE(6.0f); creature->MaxTurn = TIGER_RUN_TURN_RATE_MAX;
if (info->Mood == MoodType::Bored) if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = 1; item->Animation.TargetState = TIGER_STATE_IDLE;
else if (info->Flags && AI.ahead) else if (creature->Flags && AI.ahead)
item->Animation.TargetState = 1; item->Animation.TargetState = TIGER_STATE_IDLE;
else if (AI.bite && AI.distance < pow(SECTOR(1.5f), 2)) else if (AI.bite && AI.distance < TIGER_RUN_ATTACK_RANGE)
{ {
if (LaraItem->Animation.Velocity.z == 0) if (LaraItem->Animation.Velocity.z == 0.0f)
item->Animation.TargetState = 1; item->Animation.TargetState = TIGER_STATE_IDLE;
else else
item->Animation.TargetState = 7; item->Animation.TargetState = TIGER_STATE_RUN_SWIPE_ATTACK;
} }
else if (info->Mood != MoodType::Attack && TestProbability(0.003f)) else if (creature->Mood != MoodType::Attack && TestProbability(TIGER_ROAR_CHANCE))
{ {
item->Animation.TargetState = 1; item->Animation.TargetState = TIGER_STATE_IDLE;
item->Animation.RequiredState = 5; item->Animation.RequiredState = TIGER_STATE_ROAR;
}
else if (creature->Mood == MoodType::Escape &&
Lara.TargetEntity != item && AI.ahead)
{
item->Animation.TargetState = TIGER_STATE_IDLE;
} }
else if (info->Mood == MoodType::Escape && Lara.TargetEntity != item && AI.ahead)
item->Animation.TargetState = 1;
info->Flags = 0; creature->Flags = 0;
break; break;
case 6: case TIGER_STATE_BITE_ATTACK:
case 7: case TIGER_STATE_RUN_SWIPE_ATTACK:
case 8: case TIGER_STATE_POUNCE_ATTACK:
if (!info->Flags && item->TestBits(JointBitType::Touch, TigerAttackJoints)) if (!creature->Flags && item->TestBits(JointBitType::Touch, TigerAttackJoints))
{ {
DoDamage(info->Enemy, TIGER_ATTACK_DAMAGE); DoDamage(creature->Enemy, TIGER_ATTACK_DAMAGE);
CreatureEffect(item, TigerBite, DoBloodSplat); CreatureEffect(item, TigerBite, DoBloodSplat);
info->Flags = 1; creature->Flags = 1; // 1 = is attacking.
} }
break; break;
@ -161,7 +192,7 @@ namespace TEN::Entities::TR3
} }
CreatureTilt(item, tilt); CreatureTilt(item, tilt);
CreatureJoint(item, 0, head); CreatureJoint(item, 0, extraHeadRot.y);
CreatureAnimation(itemNumber, angle, tilt); CreatureAnimation(itemNumber, angle, tilt);
} }
} }

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
void TigerControl(short itemNumber); void TigerControl(short itemNumber);
} }

View file

@ -20,7 +20,7 @@
using namespace TEN::Effects::Lara; using namespace TEN::Effects::Lara;
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
static BOSS_STRUCT BossData; static BOSS_STRUCT BossData;
@ -452,7 +452,7 @@ namespace TEN::Entities::TR3
if (!Lara.Burn) if (!Lara.Burn)
{ {
if (ItemNearLara(&fx->pos, 200)) if (ItemNearLara(&fx->pos.Position, 200))
{ {
LaraItem->HitStatus = true; LaraItem->HitStatus = true;
KillEffect(fxNumber); KillEffect(fxNumber);

View file

@ -1,7 +1,7 @@
#pragma once #pragma once
#include "Game/items.h" #include "Game/items.h"
namespace TEN::Entities::TR3 namespace TEN::Entities::Creatures::TR3
{ {
void InitialiseTony(short itemNumber); void InitialiseTony(short itemNumber);
void TonyControl(short itemNumber); void TonyControl(short itemNumber);

Some files were not shown because too many files have changed in this diff Show more