Merge branch 'ten_beta' into states_tier_3

This commit is contained in:
Sezz 2022-06-12 23:02:07 +10:00
commit 57e5ddf9cc
231 changed files with 6973 additions and 15537 deletions

2
.gitignore vendored
View file

@ -6,6 +6,7 @@ TombEngine/Debug/
TombEngine/Release
TombEngine/Legacy Engine Objects
x64/
packages/
.vs/
*.dll
*.dmp
@ -23,4 +24,3 @@ x64/
*.wav
*.trc
*.str
packages/

View file

@ -1,108 +0,0 @@
-- Inventory constants
INV_OBJECT_UZIS = 0
INV_OBJECT_PISTOLS = 1
INV_OBJECT_SHOTGUN = 2
INV_OBJECT_REVOLVER = 3
INV_OBJECT_REVOLVER_LASER = 4
INV_OBJECT_CROSSBOW = 5
INV_OBJECT_CROSSBOW_LASER = 6
INV_OBJECT_HK = 7
INV_OBJECT_HK_LASER = 8
INV_OBJECT_SHOTGUN_AMMO1 = 9
INV_OBJECT_SHOTGUN_AMMO2 = 10
INV_OBJECT_HK_AMMO1 = 13
INV_OBJECT_CROSSBOW_AMMO1 = 15
INV_OBJECT_CROSSBOW_AMMO2 = 16
INV_OBJECT_REVOLVER_AMMO = 17
INV_OBJECT_UZI_AMMO = 18
INV_OBJECT_PISTOLS_AMMO = 19
INV_OBJECT_LASERSIGHT = 20
INV_OBJECT_SILENCER = 21
INV_OBJECT_LARGE_MEDIPACK = 22
INV_OBJECT_SMALL_MEDIPACK = 23
INV_OBJECT_BINOCULARS = 24
INV_OBJECT_FLARES = 25
INV_OBJECT_TIMEX = 26
INV_OBJECT_PUZZLE1 = 29
INV_OBJECT_PUZZLE2 = 30
INV_OBJECT_PUZZLE3 = 31
INV_OBJECT_PUZZLE4 = 32
INV_OBJECT_PUZZLE5 = 33
INV_OBJECT_PUZZLE6 = 34
INV_OBJECT_PUZZLE7 = 35
INV_OBJECT_PUZZLE8 = 36
INV_OBJECT_PUZZLE1_COMBO1 = 37
INV_OBJECT_PUZZLE1_COMBO2 = 38
INV_OBJECT_PUZZLE2_COMBO1 = 39
INV_OBJECT_PUZZLE2_COMBO2 = 40
INV_OBJECT_PUZZLE3_COMBO1 = 41
INV_OBJECT_PUZZLE3_COMBO2 = 42
INV_OBJECT_PUZZLE4_COMBO1 = 43
INV_OBJECT_PUZZLE4_COMBO2 = 44
INV_OBJECT_PUZZLE5_COMBO1 = 45
INV_OBJECT_PUZZLE5_COMBO2 = 46
INV_OBJECT_PUZZLE6_COMBO1 = 47
INV_OBJECT_PUZZLE6_COMBO2 = 48
INV_OBJECT_PUZZLE7_COMBO1 = 49
INV_OBJECT_PUZZLE7_COMBO2 = 50
INV_OBJECT_PUZZLE8_COMBO1 = 51
INV_OBJECT_PUZZLE8_COMBO2 = 52
INV_OBJECT_KEY1 = 53
INV_OBJECT_KEY2 = 54
INV_OBJECT_KEY3 = 55
INV_OBJECT_KEY4 = 56
INV_OBJECT_KEY5 = 57
INV_OBJECT_KEY6 = 58
INV_OBJECT_KEY7 = 59
INV_OBJECT_KEY8 = 60
INV_OBJECT_KEY1_COMBO1 = 61
INV_OBJECT_KEY1_COMBO2 = 62
INV_OBJECT_KEY2_COMBO1 = 63
INV_OBJECT_KEY2_COMBO2 = 64
INV_OBJECT_KEY3_COMBO1 = 65
INV_OBJECT_KEY3_COMBO2 = 66
INV_OBJECT_KEY4_COMBO1 = 67
INV_OBJECT_KEY4_COMBO2 = 68
INV_OBJECT_KEY5_COMBO1 = 69
INV_OBJECT_KEY5_COMBO2 = 70
INV_OBJECT_KEY6_COMBO1 = 71
INV_OBJECT_KEY6_COMBO2 = 72
INV_OBJECT_KEY7_COMBO1 = 73
INV_OBJECT_KEY7_COMBO2 = 74
INV_OBJECT_KEY8_COMBO1 = 75
INV_OBJECT_KEY8_COMBO2 = 76
INV_OBJECT_PICKUP1 = 77
INV_OBJECT_PICKUP2 = 78
INV_OBJECT_PICKUP3 = 79
INV_OBJECT_PICKUP4 = 80
INV_OBJECT_PICKUP1_COMBO1 = 81
INV_OBJECT_PICKUP1_COMBO2 = 82
INV_OBJECT_PICKUP2_COMBO1 = 83
INV_OBJECT_PICKUP2_COMBO2 = 84
INV_OBJECT_PICKUP3_COMBO1 = 85
INV_OBJECT_PICKUP3_COMBO2 = 86
INV_OBJECT_PICKUP4_COMBO1 = 87
INV_OBJECT_PICKUP4_COMBO2 = 88
INV_OBJECT_BRUNING_TORCH = 89
INV_OBJECT_CROWBAR = 90
INV_OBJECT_EXAMINE1 = 91
INV_OBJECT_EXAMINE2 = 92
INV_OBJECT_EXAMINE3 = 93
INV_OBJECT_WETCLOTH1 = 93
INV_OBJECT_GRAPPLING_GUN = 95
INV_OBJECT_GRAPPLING_AMMO = 96
INV_OBJECT_WETCLOTH2 = 97
INV_OBJECT_BOTTLE = 98
-- Item constants
ITEM_currentAnimState = 0
ITEM_goalAnimState = 1
ITEM_REQUIRED_ANIM_STATE = 2
ITEM_frameNumber = 3
ITEM_animNumber = 4
ITEM_hitPoints = 5
ITEM_HIT_STATUS = 6
ITEM_GRAVITY_STATUS = 7
ITEM_COLLIDABLE = 8
ITEM_POISONED = 9
ITEM_roomNumber = 10

View file

@ -490,7 +490,7 @@ local strings = {
""
},
grenade_launcher_ammo1 = {
"Grenadegun (Normal Ammo)",
"Grenade Gun Normal Ammo",
"",
"",
"",
@ -500,7 +500,7 @@ local strings = {
""
},
grenade_launcher_ammo2 = {
"Grenadegun (Super Ammo)",
"Grenade Gun Super Ammo",
"",
"",
"",
@ -510,7 +510,7 @@ local strings = {
""
},
grenade_launcher_ammo3 = {
"Grenadegun (Flash Ammo)",
"Grenade Gun Flash Ammo",
"",
"",
"",
@ -519,8 +519,8 @@ local strings = {
"",
""
},
harpoon_item = {
"Harpoon Launcher",
harpoon_gun = {
"Harpoon Gun",
"",
"",
"",
@ -530,7 +530,7 @@ local strings = {
""
},
harpoon_ammo = {
"Harpoon Launcher Ammo",
"Harpoon Gun Ammo",
"",
"",
"",
@ -780,7 +780,7 @@ local strings = {
""
},
savegame_timestamp = {
"%d Days %d:%d:%d",
"%02d Days %02d:%02d:%02d",
"",
"",
"",
@ -889,6 +889,16 @@ local strings = {
"",
""
},
options = {
"Options",
"",
"",
"",
"",
"",
"",
""
},
sound = {
"Sound settings",
"",

View file

@ -751,9 +751,9 @@ void LaraAboveWater(ItemInfo* item, CollisionInfo* coll)
coll->Setup.EnableSpasm = true;
coll->Setup.OldPosition = item->Pose.Position;
coll->Setup.OldState = item->Animation.ActiveState;
coll->Setup.OldAnimNumber = item->Animation.AnimNumber;
coll->Setup.OldFrameNumber = item->Animation.FrameNumber;
coll->Setup.OldState = item->Animation.ActiveState;
if (TrInput & IN_LOOK && lara->Control.CanLook &&
lara->ExtraAnim == NO_ITEM)

View file

@ -1,4 +1,5 @@
#pragma once
#include "Game/control/control.h"
#include "Game/Lara/lara_struct.h"
struct ItemInfo;
@ -67,14 +68,14 @@ constexpr auto LARA_DEATH_VELOCITY = 155;
constexpr auto LARA_DIVE_DEATH_VELOCITY = 134;
constexpr auto LARA_TERMINAL_VELOCITY = CLICK(10);
constexpr auto LARA_POSITION_ADJUST_MAX_TIME = 30 * 3; // 30 frames * 3 = 3 seconds allowed for position adjustment.
constexpr auto LARA_POSE_TIME = 30 * 30; // 30 frames * 30 = 30 seconds to AFK pose.
constexpr auto LARA_POSITION_ADJUST_MAX_TIME = FPS * 3; // 30 frames * 3 = 3 seconds allowed for position adjustment.
constexpr auto LARA_POSE_TIME = FPS * 30; // 30 frames * 30 = 30 seconds to AFK pose.
constexpr auto LARA_RUN_JUMP_TIME = 22; // Frames to count before a running jump is possible.
constexpr auto LARA_HEALTH_MAX = 1000.0f;
constexpr auto LARA_AIR_MAX = 1800.0f;
constexpr auto LARA_SPRINT_ENERGY_MAX = 120.0f;
constexpr auto LARA_POISON_POTENCY_MAX = 16.0f;
constexpr auto LARA_POISON_POTENCY_MAX = 32.0f;
extern LaraInfo Lara;
extern ItemInfo* LaraItem;

View file

@ -1419,7 +1419,7 @@ void lara_as_death(ItemInfo* item, CollisionInfo* coll)
BinocularRange = 0;
LaserSight = false;
AlterFOV(ANGLE(80.0f));
item->MeshBits = -1;
item->MeshBits = ALL_JOINT_BITS;
lara->Inventory.IsBusy = false;
}
}

View file

@ -408,7 +408,7 @@ void lara_as_climb_stepoff_right(ItemInfo* item, CollisionInfo* coll)
short GetClimbFlags(int x, int y, int z, short roomNumber)
{
return GetClimbFlags(GetFloor(x, y, z, &roomNumber));
return GetClimbFlags(GetCollision(x, y, z, roomNumber).BottomBlock);
}
short GetClimbFlags(FloorInfo* floor)

View file

@ -64,7 +64,7 @@ bool LaraDeflectEdgeJump(ItemInfo* item, CollisionInfo* coll)
SetAnimation(item, LA_LAND);
LaraSnapToHeight(item, coll);
}
else if (abs(item->Animation.Velocity) > CLICK(0.5f))
else if (abs(item->Animation.Velocity) > 47) // TODO: Demagic. This is Lara's running velocity. Jumps have a minimum of 50.
SetAnimation(item, LA_JUMP_WALL_SMASH_START, 1);
item->Animation.Velocity /= 4;
@ -670,10 +670,10 @@ bool TestLaraHitCeiling(CollisionInfo* coll)
void SetLaraHitCeiling(ItemInfo* item, CollisionInfo* coll)
{
item->Pose.Position = coll->Setup.OldPosition;
item->Animation.Airborne = false;
item->Animation.Velocity = 0;
item->Animation.VerticalVelocity = 0;
item->Pose.Position = coll->Setup.OldPosition;
}
bool TestLaraObjectCollision(ItemInfo* item, short angle, int distance, int height, int side)

View file

@ -53,6 +53,10 @@ void lara_as_crouch_idle(ItemInfo* item, CollisionInfo* coll)
if (TrInput & IN_LOOK)
LookUpDown(item);
// HACK.
if (BinocularOn)
return;
if (TrInput & IN_LEFT)
lara->Control.TurnRate.y = -LARA_CRAWL_TURN_MAX;
else if (TrInput & IN_RIGHT)
@ -397,6 +401,10 @@ void lara_as_crawl_idle(ItemInfo* item, CollisionInfo* coll)
if (TrInput & IN_LOOK)
LookUpDown(item);
// HACK.
if (BinocularOn)
return;
if (TrInput & IN_LEFT)
lara->Control.TurnRate.y = -LARA_CRAWL_TURN_MAX;
else if (TrInput & IN_RIGHT)

View file

@ -66,7 +66,7 @@ WeaponInfo Weapons[(int)LaraWeaponType::NumWeapons] =
9,
3,
0,
SFX_TR4_LARA_FIRE,
SFX_TR4_PISTOL_FIRE,
0
},
@ -83,7 +83,7 @@ WeaponInfo Weapons[(int)LaraWeaponType::NumWeapons] =
16,
3,
0,
SFX_TR4_DESSERT_EAGLE_FIRE,
SFX_TR4_REVOLVER_FIRE,
0
},
@ -100,7 +100,7 @@ WeaponInfo Weapons[(int)LaraWeaponType::NumWeapons] =
3,
3,
0,
SFX_TR4_LARA_UZI_FIRE,
SFX_TR4_UZI_FIRE,
0
},
@ -117,7 +117,7 @@ WeaponInfo Weapons[(int)LaraWeaponType::NumWeapons] =
9,
3,
9,
SFX_TR4_LARA_SHOTGUN,
SFX_TR4_SHOTGUN_FIRE,
0
},
@ -151,7 +151,7 @@ WeaponInfo Weapons[(int)LaraWeaponType::NumWeapons] =
0,
2,
9,
SFX_TR4_LARA_CROSSBOW,
SFX_TR4_CROSSBOW_FIRE,
20
},
@ -185,7 +185,7 @@ WeaponInfo Weapons[(int)LaraWeaponType::NumWeapons] =
0,
2,
0,
SFX_TR4_LARA_UZI_FIRE,
SFX_TR4_UZI_FIRE,
0
},
@ -253,7 +253,7 @@ WeaponInfo Weapons[(int)LaraWeaponType::NumWeapons] =
0,
0,
0,
SFX_TR4_LARA_UZI_FIRE,
SFX_TR4_UZI_FIRE,
0
}
};
@ -589,7 +589,6 @@ void LaraGun(ItemInfo* laraItem)
case LaraWeaponType::Pistol:
case LaraWeaponType::Uzi:
PistolHandler(laraItem, lara->Control.Weapon.GunType);
break;
case LaraWeaponType::Shotgun:
@ -722,7 +721,7 @@ GAME_OBJECT_ID WeaponObjectMesh(ItemInfo* laraItem, LaraWeaponType weaponType)
switch (weaponType)
{
case LaraWeaponType::Revolver:
return (lara->Weapons[(int)LaraWeaponType::Revolver].HasLasersight == true ? ID_LARA_REVOLVER_LASER : ID_REVOLVER_ANIM);
return (lara->Weapons[(int)LaraWeaponType::Revolver].HasLasersight ? ID_LARA_REVOLVER_LASER : ID_REVOLVER_ANIM);
case LaraWeaponType::Uzi:
return ID_UZI_ANIM;
@ -734,7 +733,7 @@ GAME_OBJECT_ID WeaponObjectMesh(ItemInfo* laraItem, LaraWeaponType weaponType)
return ID_HK_ANIM;
case LaraWeaponType::Crossbow:
return (lara->Weapons[(int)LaraWeaponType::Crossbow].HasLasersight == true ? ID_LARA_CROSSBOW_LASER : ID_CROSSBOW_ANIM);
return (lara->Weapons[(int)LaraWeaponType::Crossbow].HasLasersight ? ID_LARA_CROSSBOW_LASER : ID_CROSSBOW_ANIM);
case LaraWeaponType::GrenadeLauncher:
return ID_GRENADE_ANIM;
@ -775,7 +774,7 @@ void HitTarget(ItemInfo* laraItem, ItemInfo* target, GameVector* hitPos, int dam
lara->Control.Weapon.GunType == LaraWeaponType::Uzi))
{
// Baddy2 gun hitting sword
SoundEffect(SFX_TR4_BAD_SWORD_RICO, &target->Pose);
SoundEffect(SFX_TR4_BADDY_SWORD_RICOCHET, &target->Pose);
TriggerRicochetSpark(hitPos, laraItem->Pose.Orientation.y, 3, 0);
return;
}
@ -794,7 +793,7 @@ void HitTarget(ItemInfo* laraItem, ItemInfo* target, GameVector* hitPos, int dam
if (target->ObjectNumber == ID_ROMAN_GOD1 ||
target->ObjectNumber == ID_ROMAN_GOD2)
{
SoundEffect(SFX_TR5_SWORD_GOD_HITMETAL, &target->Pose);
SoundEffect(SFX_TR5_SWORD_GOD_HIT_METAL, &target->Pose);
}
break;
@ -815,6 +814,7 @@ void HitTarget(ItemInfo* laraItem, ItemInfo* target, GameVector* hitPos, int dam
target->HitPoints = 0;
}
}
if (!target->LuaCallbackOnHitName.empty())
{
short index = g_GameScriptEntities->GetIndexByName(target->LuaName);
@ -897,7 +897,7 @@ FireWeaponType FireWeapon(LaraWeaponType weaponType, ItemInfo* target, ItemInfo*
// TODO: enable it when the slot is created !
/*
if (target->objectNumber == ID_TRIBEBOSS)
if (target->ObjectNumber == ID_TRIBEBOSS)
{
long dx, dy, dz;
@ -906,27 +906,27 @@ FireWeaponType FireWeapon(LaraWeaponType weaponType, ItemInfo* target, ItemInfo*
dz = (vDest.z - vSrc.z) >> 5;
FindClosestShieldPoint(vDest.x - dx, vDest.y - dy, vDest.z - dz, target);
}
else if (target->objectNumber == ID_ARMY_WINSTON || target->objectNumber == ID_LONDONBOSS) //Don't want blood on Winston - never get the stains out
else if (target->ObjectNumber == ID_ARMY_WINSTON || target->ObjectNumber == ID_LONDONBOSS) //Don't want blood on Winston - never get the stains out
{
short ricochet_angle;
target->hitStatus = true; //need to do this to maintain defence state
target->HitStatus = true; //need to do this to maintain defence state
target->HitPoints--;
ricochet_angle = (mGetAngle(lara->pos.Position.z, lara->pos.Position.x, target->pos.Position.z, target->pos.Position.x) >> 4) & 4095;
ricochet_angle = (mGetAngle(lara->Pose.Position.z, lara->Pose.Position.x, target->Pose.Position.z, target->Pose.Position.x) >> 4) & 4095;
TriggerRicochetSparks(&vDest, ricochet_angle, 16, 0);
SoundEffect(SFX_TR4_LARA_RICOCHET, &target->pos); // play RICOCHET Sample
SoundEffect(SFX_TR4_WEAPON_RICOCHET, &target->Pose); // play RICOCHET Sample
}
else if (target->objectNumber == ID_SHIVA) //So must be Shiva
else if (target->ObjectNumber == ID_SHIVA) //So must be Shiva
{
z = target->pos.Position.z - lara_item->pos.Position.z;
x = target->pos.Position.x - lara_item->pos.Position.x;
angle = 0x8000 + phd_atan(z, x) - target->pos.Orientation.y;
z = target->Pose.Position.z - lara_item->Pose.Position.z;
x = target->Pose.Position.x - lara_item->Pose.Position.x;
angle = 0x8000 + phd_atan(z, x) - target->Pose.Orientation.y;
if ((target->ActiveState > 1 && target->ActiveState < 5) && angle < 0x4000 && angle > -0x4000)
{
target->hitStatus = true; //need to do this to maintain defence state
ricochet_angle = (mGetAngle(lara->pos.Position.z, lara->pos.Position.x, target->pos.Position.z, target->pos.Position.x) >> 4) & 4095;
target->HitStatus = true; //need to do this to maintain defence state
ricochet_angle = (mGetAngle(lara->Pose.Position.z, lara->Pose.Position.x, target->Pose.Position.z, target->Pose.Position.x) >> 4) & 4095;
TriggerRicochetSparks(&vDest, ricochet_angle, 16, 0);
SoundEffect(SFX_TR4_LARA_RICOCHET, &target->pos); // play RICOCHET Sample
SoundEffect(SFX_TR4_WEAPON_RICOCHET, &target->Pose); // play RICOCHET Sample
}
else //Shiva's not in defence mode or has its back to Lara
HitTarget(target, &vDest, weapon->damage, 0);
@ -980,7 +980,8 @@ void LaraTargetInfo(ItemInfo* laraItem, WeaponInfo* weaponInfo)
laraItem->Pose.Position.x,
muzzleOffset.y,
laraItem->Pose.Position.z,
laraItem->RoomNumber);
laraItem->RoomNumber
);
auto targetPoint = GameVector();
FindTargetPoint(lara->TargetEntity, &targetPoint);
@ -1050,7 +1051,8 @@ void LaraGetNewTarget(ItemInfo* laraItem, WeaponInfo* weaponInfo)
laraItem->Pose.Position.x,
muzzleOffset.y,
laraItem->Pose.Position.z,
laraItem->RoomNumber);
laraItem->RoomNumber
);
ItemInfo* bestItem = nullptr;
short bestYrot = MAXSHORT;

View file

@ -22,12 +22,12 @@ struct WeaponInfo
short ShotAccuracy;
int GunHeight;
short TargetDist;
byte Damage;
byte RecoilFrame;
byte FlashTime;
byte DrawFrame;
int Damage;
int RecoilFrame;
int FlashTime;
int DrawFrame;
short SampleNum;
byte ExplosiveDamage;
int ExplosiveDamage;
};
enum WeaponState

View file

@ -31,7 +31,7 @@ void FlareControl(short itemNumber)
if (flareItem->Animation.VerticalVelocity)
{
flareItem->Pose.Orientation.x += ANGLE(3.0f);
flareItem->Pose.Orientation.x -= ANGLE(5.0f);
flareItem->Pose.Orientation.z += ANGLE(5.0f);
}
else
@ -40,13 +40,14 @@ void FlareControl(short itemNumber)
flareItem->Pose.Orientation.z = 0;
}
auto velocity = Vector3Int(
flareItem->Animation.Velocity * phd_sin(flareItem->Pose.Orientation.y),
flareItem->Animation.VerticalVelocity,
flareItem->Animation.Velocity * phd_cos(flareItem->Pose.Orientation.y)
);
auto oldPos = flareItem->Pose.Position;
int xVel = flareItem->Animation.Velocity * phd_sin(flareItem->Pose.Orientation.y);
int zVel = flareItem->Animation.Velocity * phd_cos(flareItem->Pose.Orientation.y);
flareItem->Pose.Position.x += xVel;
flareItem->Pose.Position.z += zVel;
flareItem->Pose.Position += Vector3Int(velocity.x, 0, velocity.z);
if (TestEnvironment(ENV_FLAG_WATER, flareItem) ||
TestEnvironment(ENV_FLAG_SWAMP, flareItem))
@ -58,8 +59,7 @@ void FlareControl(short itemNumber)
flareItem->Animation.VerticalVelocity += 6;
flareItem->Pose.Position.y += flareItem->Animation.VerticalVelocity;
DoProjectileDynamics(itemNumber, oldPos.x, oldPos.y, oldPos.z, xVel, flareItem->Animation.VerticalVelocity, zVel);
DoProjectileDynamics(itemNumber, oldPos.x, oldPos.y, oldPos.z, velocity.x, velocity.y, velocity.z);
int& life = flareItem->Data;
life &= 0x7FFF;
@ -244,7 +244,7 @@ void DrawFlare(ItemInfo* laraItem)
{
if (armFrame == 72)
{
SoundEffect(SFX_TR4_OBJ_GEM_SMASH, &laraItem->Pose, (SoundEnvironment)TestEnvironment(ENV_FLAG_WATER, laraItem));
SoundEffect(SFX_TR4_FLARE_IGNITE_DRY, &laraItem->Pose, TestEnvironment(ENV_FLAG_WATER, laraItem) ? SoundEnvironment::Water : SoundEnvironment::Land);
lara->Flare.Life = 1;
}
@ -291,7 +291,6 @@ void CreateFlare(ItemInfo* laraItem, GAME_OBJECT_ID objectNumber, bool thrown)
if (itemNumber != NO_ITEM)
{
auto* flareItem = &g_Level.Items[itemNumber];
bool flag = false;
flareItem->ObjectNumber = objectNumber;
flareItem->RoomNumber = laraItem->RoomNumber;
@ -303,12 +302,13 @@ void CreateFlare(ItemInfo* laraItem, GAME_OBJECT_ID objectNumber, bool thrown)
int floorHeight = GetCollision(pos.x, pos.y, pos.z, laraItem->RoomNumber).Position.Floor;
auto collided = GetCollidedObjects(flareItem, 0, true, CollidedItems, CollidedMeshes, true);
bool landed = false;
if (floorHeight < pos.y || collided)
{
flag = true;
flareItem->Pose.Orientation.y = laraItem->Pose.Orientation.y + ANGLE(180.0f);
landed = true;
flareItem->Pose.Position.x = laraItem->Pose.Position.x + 320 * phd_sin(flareItem->Pose.Orientation.y);
flareItem->Pose.Position.z = laraItem->Pose.Position.z + 320 * phd_cos(flareItem->Pose.Orientation.y);
flareItem->Pose.Orientation.y = laraItem->Pose.Orientation.y + ANGLE(180.0f);
flareItem->RoomNumber = laraItem->RoomNumber;
}
else
@ -338,7 +338,7 @@ void CreateFlare(ItemInfo* laraItem, GAME_OBJECT_ID objectNumber, bool thrown)
flareItem->Animation.VerticalVelocity = laraItem->Animation.VerticalVelocity + 50;
}
if (flag)
if (landed)
flareItem->Animation.Velocity /= 2;
if (objectNumber == ID_FLARE_ITEM)

View file

@ -1,11 +1,12 @@
#pragma once
#include "Game/control/control.h"
struct ItemInfo;
struct CollisionInfo;
struct Vector3Int;
enum GAME_OBJECT_ID : short;
constexpr auto FLARE_LIFE_MAX = 60 * 30; // 60 * 30 frames = 60 seconds.
constexpr auto FLARE_LIFE_MAX = 60 * FPS; // 60 * 30 frames = 60 seconds.
void FlareControl(short itemNumber);
void ReadyFlare(ItemInfo* laraItem);

View file

@ -499,7 +499,7 @@ void UpdateLaraSubsuitAngles(ItemInfo* item)
auto mul1 = (float)abs(lara->Control.Subsuit.Velocity[0]) / SECTOR(8);
auto mul2 = (float)abs(lara->Control.Subsuit.Velocity[1]) / SECTOR(8);
auto vol = ((mul1 + mul2) * 5.0f) + 0.5f;
SoundEffect(SFX_TR5_DIVE_SUIT_ENGINE, &item->Pose, SoundEnvironment::Water, 1.0f + (mul1 + mul2), vol);
SoundEffect(SFX_TR5_VEHICLE_DIVESUIT_ENGINE, &item->Pose, SoundEnvironment::Water, 1.0f + (mul1 + mul2), vol);
}
}
@ -664,9 +664,9 @@ void SetContextWaterClimbOut(ItemInfo* item, CollisionInfo* coll, WaterClimbOutT
void SetLaraLand(ItemInfo* item, CollisionInfo* coll)
{
//item->Airborne = false; // TODO: Removing this avoids an unusual landing bug Core had worked around in an obscure way. I hope to find a proper solution. @Sezz 2022.02.18
item->Animation.Velocity = 0;
item->Animation.VerticalVelocity = 0;
//item->Airborne = false; // TODO: Removing this avoids an unusual landing bug Core had worked around in an obscure way. I hope to find a proper solution. @Sezz 2022.02.18
LaraSnapToHeight(item, coll);
}
@ -674,15 +674,15 @@ void SetLaraLand(ItemInfo* item, CollisionInfo* coll)
void SetLaraFallAnimation(ItemInfo* item)
{
SetAnimation(item, LA_FALL_START);
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = true;
item->Animation.VerticalVelocity = 0;
}
void SetLaraFallBackAnimation(ItemInfo* item)
{
SetAnimation(item, LA_FALL_BACK);
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = true;
item->Animation.VerticalVelocity = 0;
}
void SetLaraMonkeyFallAnimation(ItemInfo* item)
@ -750,7 +750,9 @@ void SetLaraSlideAnimation(ItemInfo* item, CollisionInfo* coll)
item->Pose.Orientation.y = angle;
}
LaraSnapToHeight(item, coll);
lara->Control.MoveAngle = angle;
lara->Control.TurnRate.y = 0;
oldAngle = angle;
}
@ -828,11 +830,11 @@ void SetLaraCornerAnimation(ItemInfo* item, CollisionInfo* coll, bool flip)
if (item->HitPoints <= 0)
{
SetAnimation(item, LA_FALL_START);
item->Pose.Position.y += CLICK(1);
item->Pose.Orientation.y += lara->NextCornerPos.Orientation.y / 2;
item->Animation.Airborne = true;
item->Animation.Velocity = 2;
item->Animation.VerticalVelocity = 1;
item->Pose.Position.y += CLICK(1);
item->Pose.Orientation.y += lara->NextCornerPos.Orientation.y / 2;
lara->Control.HandStatus = HandStatus::Free;
return;
}

View file

@ -809,7 +809,7 @@ void lara_as_swan_dive(ItemInfo* item, CollisionInfo* coll)
g_GameFlow->HasCrawlspaceSwandive())
{
item->Animation.TargetState = LS_CROUCH_IDLE;
TranslateItem(item, coll->Setup.ForwardAngle, CLICK(0.5f), 0, 0); // HACK: Move forward to avoid standing up or falling out on an edge.
TranslateItem(item, coll->Setup.ForwardAngle, CLICK(0.5f)); // HACK: Move forward to avoid standing up or falling out on an edge.
}
else USE_FEATURE_IF_CPP20([[likely]])
item->Animation.TargetState = LS_IDLE;

View file

@ -397,9 +397,9 @@ void lara_as_tightrope_fall(ItemInfo* item, CollisionInfo* coll)
{
// HACK: Set position command can't move Lara laterally?
if (item->Animation.AnimNumber == LA_TIGHTROPE_FALL_LEFT)
TranslateItem(item, coll->Setup.ForwardAngle - ANGLE(90.0f), CLICK(1), 0, 0);
TranslateItem(item, coll->Setup.ForwardAngle - ANGLE(90.0f), CLICK(1));
else if (item->Animation.AnimNumber == LA_TIGHTROPE_FALL_RIGHT)
TranslateItem(item, coll->Setup.ForwardAngle + ANGLE(90.0f), CLICK(1), 0, 0);
TranslateItem(item, coll->Setup.ForwardAngle + ANGLE(90.0f), CLICK(1));
item->Animation.VerticalVelocity = 10;
}
@ -895,7 +895,7 @@ void lara_as_pole_down(ItemInfo* item, CollisionInfo* coll)
}
// TODO: In WAD.
SoundEffect(SFX_TR4_LARA_POLE_LOOP, &item->Pose);
SoundEffect(SFX_TR4_LARA_POLE_SLIDE_LOOP, &item->Pose);
if (TrInput & IN_ACTION)
{

View file

@ -158,11 +158,11 @@ void AnimateShotgun(ItemInfo* laraItem, LaraWeaponType weaponType)
//HKFlag = 1;
if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer)
SoundEffect(SFX_TR4_LARA_HK_SILENCED, nullptr);
SoundEffect(SFX_TR4_HK_SILENCED, nullptr);
else
{
SoundEffect(SFX_TR4_EXPLOSION1, &laraItem->Pose, SoundEnvironment::Land, 1.0f, 0.4f);
SoundEffect(SFX_TR4_LARA_HK_FIRE, &laraItem->Pose);
SoundEffect(SFX_TR4_HK_FIRE, &laraItem->Pose);
}
}
else
@ -174,15 +174,12 @@ void AnimateShotgun(ItemInfo* laraItem, LaraWeaponType weaponType)
item->Animation.TargetState = WEAPON_STATE_AIM;
}
if (weaponType == LaraWeaponType::HarpoonGun && reloadHarpoonGun)
item->Animation.TargetState = WEAPON_STATE_UNAIM;
if (item->Animation.TargetState != WEAPON_STATE_RECOIL &&
//HKFlag &&
!(lara->Weapons[(int)LaraWeaponType::HK].HasSilencer))
{
StopSoundEffect(SFX_TR4_LARA_HK_FIRE);
SoundEffect(SFX_TR4_LARA_HK_STOP, &laraItem->Pose);
StopSoundEffect(SFX_TR4_HK_FIRE);
SoundEffect(SFX_TR4_HK_STOP, &laraItem->Pose);
//HKFlag = 0;
}
}
@ -234,11 +231,11 @@ void AnimateShotgun(ItemInfo* laraItem, LaraWeaponType weaponType)
item->Animation.TargetState = WEAPON_STATE_UNDERWATER_RECOIL;
if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer)
SoundEffect(SFX_TR4_LARA_HK_SILENCED, nullptr);
SoundEffect(SFX_TR4_HK_SILENCED, nullptr);
else
{
SoundEffect(SFX_TR4_EXPLOSION1, &laraItem->Pose, SoundEnvironment::Land, 1.0f, 0.4f);
SoundEffect(SFX_TR4_LARA_HK_FIRE, &laraItem->Pose);
SoundEffect(SFX_TR4_HK_FIRE, &laraItem->Pose);
}
}
else
@ -253,8 +250,8 @@ void AnimateShotgun(ItemInfo* laraItem, LaraWeaponType weaponType)
//HKFlag &&
!(lara->Weapons[(int)LaraWeaponType::HK].HasSilencer))
{
StopSoundEffect(SFX_TR4_LARA_HK_FIRE);
SoundEffect(SFX_TR4_LARA_HK_STOP, &laraItem->Pose);
StopSoundEffect(SFX_TR4_HK_FIRE);
SoundEffect(SFX_TR4_HK_STOP, &laraItem->Pose);
//HKFlag = 0;
}
/*else if (HKFlag)
@ -352,7 +349,7 @@ void FireShotgun(ItemInfo* laraItem)
lara->RightArm.FlashGun = Weapons[(int)LaraWeaponType::Shotgun].FlashTime;
SoundEffect(SFX_TR4_EXPLOSION1, &laraItem->Pose, (SoundEnvironment)TestEnvironment(ENV_FLAG_WATER, laraItem));
SoundEffect(SFX_TR4_EXPLOSION1, &laraItem->Pose, TestEnvironment(ENV_FLAG_WATER, laraItem) ? SoundEnvironment::Water : SoundEnvironment::Land);
SoundEffect(Weapons[(int)LaraWeaponType::Shotgun].SampleNum, &laraItem->Pose);
Statistics.Game.AmmoUsed++;
@ -558,7 +555,6 @@ void HarpoonBoltControl(short itemNumber)
aboveWater = false;
}
// Update bolt's position
TranslateItem(item, item->Pose.Orientation, item->Animation.Velocity);
}
else
@ -830,10 +826,6 @@ void GrenadeControl(short itemNumber)
// Store old position for later
auto oldPos = item->Pose.Position;
int xv;
int yv;
int zv;
item->Shade = 0xC210;
// Check if above water and update velocity and vertical velocity
@ -890,7 +882,12 @@ void GrenadeControl(short itemNumber)
}
// Update grenade position
TranslateItem(item, item->Animation.TargetState/*???*/, item->Animation.Velocity, item->Animation.VerticalVelocity);
auto velocity = Vector3Int(
item->Animation.Velocity * phd_sin(item->Animation.TargetState),
item->Animation.VerticalVelocity,
item->Animation.Velocity * phd_cos(item->Animation.TargetState)
);
item->Pose.Position += velocity;
// Grenades that originate from first grenade when special ammo is selected
if (item->ItemFlags[0] == (int)GrenadeType::Ultra)
@ -908,10 +905,7 @@ void GrenadeControl(short itemNumber)
short sYrot = item->Pose.Orientation.y;
item->Pose.Orientation.y = item->Animation.TargetState; // ???
int xv = item->Animation.Velocity * phd_sin(item->Animation.TargetState);
int yv = item->Animation.VerticalVelocity;
int zv = item->Animation.Velocity * phd_cos(item->Animation.TargetState);
DoProjectileDynamics(itemNumber, oldPos.x, oldPos.y, oldPos.z, xv, yv, zv);
DoProjectileDynamics(itemNumber, oldPos.x, oldPos.y, oldPos.z, velocity.x, velocity.y, velocity.z);
item->Animation.TargetState = item->Pose.Orientation.y;
item->Pose.Orientation.y = sYrot;
@ -1131,7 +1125,7 @@ void FireRocket(ItemInfo* laraItem)
{
auto* lara = GetLaraInfo(laraItem);
Ammo& ammos = GetAmmo(laraItem, LaraWeaponType::RocketLauncher);
auto& ammos = GetAmmo(laraItem, LaraWeaponType::RocketLauncher);
if (!ammos)
return;
@ -1327,8 +1321,8 @@ void RocketControl(short itemNumber)
{
// Smash objects are legacy objects from TRC, let's make them explode in the legacy way
TriggerExplosionSparks(currentItem->Pose.Position.x, currentItem->Pose.Position.y, currentItem->Pose.Position.z, 3, -2, 0, currentItem->RoomNumber);
auto pos = PHD_3DPOS(currentItem->Pose.Position.x, currentItem->Pose.Position.y - 128, currentItem->Pose.Position.z);
TriggerShockwave(&pos, 48, 304, 96, 0, 96, 128, 24, 0, 0);
auto pose = PHD_3DPOS(currentItem->Pose.Position.x, currentItem->Pose.Position.y - 128, currentItem->Pose.Position.z);
TriggerShockwave(&pose, 48, 304, 96, 0, 96, 128, 24, 0, 0);
ExplodeItemNode(currentItem, 0, 0, 128);
short currentItemNumber = (currentItem - CollidedItems[0]);
SmashObject(currentItemNumber);
@ -1364,8 +1358,8 @@ void RocketControl(short itemNumber)
if (currentMesh->HitPoints <= 0)
{
TriggerExplosionSparks(currentMesh->pos.Position.x, currentMesh->pos.Position.y, currentMesh->pos.Position.z, 3, -2, 0, item->RoomNumber);
auto pos = PHD_3DPOS(currentMesh->pos.Position.x, currentMesh->pos.Position.y - 128, currentMesh->pos.Position.z, 0, currentMesh->pos.Orientation.y, 0);
TriggerShockwave(&pos, 40, 176, 64, 0, 96, 128, 16, 0, 0);
auto pose = PHD_3DPOS(currentMesh->pos.Position.x, currentMesh->pos.Position.y - 128, currentMesh->pos.Position.z, 0, currentMesh->pos.Orientation.y, 0);
TriggerShockwave(&pose, 40, 176, 64, 0, 96, 128, 16, 0, 0);
ShatterObject(nullptr, currentMesh, -128, item->RoomNumber, 0);
SmashedMeshRoom[SmashedMeshCount] = item->RoomNumber;
SmashedMesh[SmashedMeshCount] = currentMesh;
@ -1412,7 +1406,7 @@ void FireCrossbow(ItemInfo* laraItem, PHD_3DPOS* pos)
{
auto* lara = GetLaraInfo(laraItem);
Ammo& ammos = GetAmmo(laraItem, LaraWeaponType::Crossbow);
auto& ammos = GetAmmo(laraItem, LaraWeaponType::Crossbow);
if (!ammos)
return;
@ -1472,7 +1466,7 @@ void FireCrossbow(ItemInfo* laraItem, PHD_3DPOS* pos)
item->ItemFlags[0] = (int)lara->Weapons[(int)LaraWeaponType::Crossbow].SelectedAmmo;
SoundEffect(SFX_TR4_LARA_CROSSBOW, &laraItem->Pose);
SoundEffect(SFX_TR4_CROSSBOW_FIRE, &laraItem->Pose);
Statistics.Level.AmmoUsed++;
Statistics.Game.AmmoUsed++;
@ -1506,7 +1500,7 @@ void CrossbowBoltControl(short itemNumber)
// Update speed and check if above water
if (TestEnvironment(ENV_FLAG_WATER, item))
{
Vector3Int bubblePos = { item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z };
auto bubblePos = item->Pose.Position;
if (item->Animation.Velocity > 64)
item->Animation.Velocity -= (item->Animation.Velocity >> 4);
@ -1519,7 +1513,6 @@ void CrossbowBoltControl(short itemNumber)
else
aboveWater = true;
// Update bolt's position
TranslateItem(item, item->Pose.Orientation, item->Animation.Velocity);
auto probe = GetCollision(item);
@ -1790,7 +1783,7 @@ void RifleHandler(ItemInfo* laraItem, LaraWeaponType weaponType)
{
if (weaponType == LaraWeaponType::Shotgun || weaponType == LaraWeaponType::HK)
{
Vector3Int pos = {};
auto pos = Vector3Int();
pos.y = -64;
GetLaraJointPosition(&pos, LM_RHAND);
TriggerDynamicLight(
@ -1894,7 +1887,7 @@ void SomeSparkEffect(int x, int y, int z, int count)
{
for (int i = 0; i < count; i++)
{
auto * spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = 1;
spark->sR = 112;
@ -1907,7 +1900,7 @@ void SomeSparkEffect(int x, int y, int z, int count)
spark->dG = spark->sG >> 1;
spark->dB = spark->sB >> 1;
spark->sLife = 24;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->friction = 5;
int random = GetRandomControl() & 0xFFF;
spark->xVel = -128 * phd_sin(random << 4);

View file

@ -1,4 +1,5 @@
#pragma once
#include "Game/control/control.h"
#include "Specific/trmath.h"
enum class LaraWeaponType;
@ -9,10 +10,10 @@ struct ItemInfo;
constexpr auto HARPOON_DRAW_ANIM = 1;
constexpr auto ROCKET_DRAW_ANIM = 0;
constexpr auto HARPOON_VELOCITY = CLICK(1);
constexpr auto HARPOON_TIME = 300;
constexpr auto GRENADE_TIME = 120;
constexpr auto ROCKET_TIME = 140;
constexpr auto EXPLOSION_TRIGGER_TIME = 118;
constexpr auto HARPOON_TIME = 10 * FPS;
constexpr auto GRENADE_TIME = 4 * FPS;
constexpr auto ROCKET_TIME = 4.5f * FPS;
constexpr auto EXPLOSION_TRIGGER_TIME = 4 * FPS - 2;
constexpr auto ROCKET_VELOCITY = CLICK(2);
constexpr auto GRENADE_VELOCITY = CLICK(0.5f);
constexpr auto MAX_GRENADE_VERTICAL_VELOCITY = CLICK(0.5f);

View file

@ -928,7 +928,7 @@ enum class HandStatus
Special
};
enum class TorchState : int
enum class TorchState
{
Holding,
Throwing,
@ -1079,7 +1079,7 @@ struct ArmInfo
Vector3Shrt Orientation;
bool Locked;
short FlashGun;
int FlashGun;
};
struct FlareData
@ -1236,6 +1236,12 @@ struct LaraControlData
WaterStatus WaterStatus;
LaraCountData Count;
WeaponControlData Weapon;
RopeControlData Rope;
TightropeControlData Tightrope;
SubsuitControlData Subsuit;
MinecartControlData Minecart;
bool CanLook;
bool IsMoving;
bool KeepLow;
@ -1245,12 +1251,6 @@ struct LaraControlData
bool CanMonkeySwing;
bool RunJumpQueued;
bool Locked;
WeaponControlData Weapon;
RopeControlData Rope;
TightropeControlData Tightrope;
SubsuitControlData Subsuit;
MinecartControlData Minecart;
};
struct LaraInfo

View file

@ -123,7 +123,7 @@ bool TestLaraHang(ItemInfo* item, CollisionInfo* coll)
if (LaraCeilingFront(item, lara->Control.MoveAngle, coll->Setup.Radius * 1.5f, 0) > -950)
stopped = true;
// Backup item pos to restore it after coll tests
// Restore backup pos after coll tests
item->Pose = oldPose;
// Setup coll lara
@ -337,12 +337,20 @@ bool TestLaraClimbIdle(ItemInfo* item, CollisionInfo* coll)
return true;
}
bool TestLaraNearClimbableWall(ItemInfo* item, FloorInfo* floor)
{
if (floor == nullptr)
floor = GetCollision(item).BottomBlock;
return ((1 << (GetQuadrant(item->Pose.Orientation.y) + 8)) & GetClimbFlags(floor));
}
bool TestLaraHangOnClimbableWall(ItemInfo* item, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(item);
int shift, result;
if (!lara->Control.CanClimbLadder)
if (!TestLaraNearClimbableWall(item))
return false;
if (item->Animation.VerticalVelocity < 0)
@ -1174,7 +1182,7 @@ bool TestLaraMonkeyStep(ItemInfo* item, CollisionInfo* coll)
return false;
}
// TODO: This function and its clone TestLaraCrawlMoveTolerance() should become obsolete with more accurate and accessible collision detection in the future.
// TODO: This function should become obsolete with more accurate and accessible collision detection in the future.
// For now, it supersedes old probes and is used alongside COLL_INFO. @Sezz 2021.10.24
bool TestLaraMoveTolerance(ItemInfo* item, CollisionInfo* coll, MoveTestSetup testSetup, bool useCrawlSetup)
{

View file

@ -19,6 +19,7 @@ bool DoLaraLedgeHang(ItemInfo* item, CollisionInfo* coll);
bool TestLaraClimbIdle(ItemInfo* item, CollisionInfo* coll);
bool TestLaraHangOnClimbableWall(ItemInfo* item, CollisionInfo* coll);
bool TestLaraNearClimbableWall(ItemInfo* item, FloorInfo* floor = nullptr);
bool TestLaraValidHangPosition(ItemInfo* item, CollisionInfo* coll);
CornerType TestLaraHangCorner(ItemInfo* item, CollisionInfo* coll, float testAngle);

View file

@ -124,7 +124,7 @@ void AnimatePistols(ItemInfo* laraItem, LaraWeaponType weaponType)
lara->RightArm.FlashGun = weapon->FlashTime;
SoundEffect(SFX_TR4_EXPLOSION1, &laraItem->Pose, SoundEnvironment::Land, 0.9f, 0.5f);
SoundEffect(SFX_TR4_EXPLOSION1, &laraItem->Pose, SoundEnvironment::Land, 0.9f, 0.3f);
SoundEffect(weapon->SampleNum, &laraItem->Pose);
soundPlayed = true;
@ -217,7 +217,7 @@ void AnimatePistols(ItemInfo* laraItem, LaraWeaponType weaponType)
if (!soundPlayed)
{
SoundEffect(SFX_TR4_EXPLOSION1, &laraItem->Pose, SoundEnvironment::Land, 0.9f, 0.5f);
SoundEffect(SFX_TR4_EXPLOSION1, &laraItem->Pose, SoundEnvironment::Land, 0.9f, 0.3f);
SoundEffect(weapon->SampleNum, &laraItem->Pose);
}

View file

@ -87,8 +87,6 @@ void AnimateLara(ItemInfo* item)
if (anim->numberCommands > 0)
{
short* cmd = &g_Level.Commands[anim->commandIndex];
int flags;
int effectID = 0;
for (int i = anim->numberCommands; i > 0; i--)
{
@ -110,14 +108,20 @@ void AnimateLara(ItemInfo* item)
}
else
{
flags = cmd[1] & 0xC000;
auto condition = flags ? (flags == (1 << 14) ? SoundEnvironment::Land : SoundEnvironment::Water) : SoundEnvironment::Always;
bool inWater = (cmd[1] & 0x8000) != 0;
bool inDry = (cmd[1] & 0x4000) != 0;
bool always = (inWater && inDry) || (!inWater && !inDry);
SoundEnvironment condition = SoundEnvironment::Always;
if (inWater) condition = SoundEnvironment::Water;
if (inDry) condition = SoundEnvironment::Land;
if (always) condition = SoundEnvironment::Always;
if (condition == SoundEnvironment::Always ||
(condition == SoundEnvironment::Land && (lara->WaterSurfaceDist >= 0 || lara->WaterSurfaceDist == NO_HEIGHT)) ||
(condition == SoundEnvironment::Water && lara->WaterSurfaceDist < -CLICK(0.5f) && lara->WaterSurfaceDist != NO_HEIGHT && !TestEnvironment(ENV_FLAG_SWAMP, item)))
(condition == SoundEnvironment::Land && (lara->WaterSurfaceDist >= -SHALLOW_WATER_START_LEVEL || lara->WaterSurfaceDist == NO_HEIGHT)) ||
(condition == SoundEnvironment::Water && lara->WaterSurfaceDist < -SHALLOW_WATER_START_LEVEL && lara->WaterSurfaceDist != NO_HEIGHT && !TestEnvironment(ENV_FLAG_SWAMP, item)))
{
SoundEffect(cmd[1] & 0x3FFF, &item->Pose, SoundEnvironment::Always);
SoundEffect(cmd[1] & 0x3FFF, &item->Pose, condition);
}
}
@ -131,8 +135,7 @@ void AnimateLara(ItemInfo* item)
break;
}
effectID = cmd[1] & 0x3FFF;
DoFlipEffect(effectID, item);
DoFlipEffect((cmd[1] & 0x3FFF), item);
cmd += 2;
break;
@ -210,7 +213,7 @@ void AnimateLara(ItemInfo* item)
void AnimateItem(ItemInfo* item)
{
item->TouchBits = 0;
item->TouchBits = NO_JOINT_BITS;
item->HitStatus = false;
item->Animation.FrameNumber++;
@ -280,8 +283,6 @@ void AnimateItem(ItemInfo* item)
if (anim->numberCommands > 0)
{
short* cmd = &g_Level.Commands[anim->commandIndex];
int flags;
int effectID = 0;
for (int i = anim->numberCommands; i > 0; i--)
{
@ -302,10 +303,12 @@ void AnimateItem(ItemInfo* item)
break;
}
flags = cmd[1] & 0xC000;
if (!Objects[item->ObjectNumber].waterCreature)
{
bool inWater = (cmd[1] & 0x8000) != 0;
bool inDry = (cmd[1] & 0x4000) != 0;
bool always = (inWater && inDry) || (!inWater && !inDry);
if (item->RoomNumber == NO_ROOM)
{
item->Pose.Position.x = LaraItem->Pose.Position.x;
@ -316,15 +319,15 @@ void AnimateItem(ItemInfo* item)
}
else if (TestEnvironment(ENV_FLAG_WATER, item))
{
if (!flags || flags == (int)SoundEnvironment::Water && (TestEnvironment(ENV_FLAG_WATER, Camera.pos.roomNumber) || Objects[item->ObjectNumber].intelligent))
if (always || (inWater && TestEnvironment(ENV_FLAG_WATER, Camera.pos.roomNumber)))
SoundEffect(cmd[1] & 0x3FFF, &item->Pose, SoundEnvironment::Always);
}
else if (!flags || flags == (int)SoundEnvironment::Land && !TestEnvironment(ENV_FLAG_WATER, Camera.pos.roomNumber))
else if (always || (inDry && !TestEnvironment(ENV_FLAG_WATER, Camera.pos.roomNumber) && !TestEnvironment(ENV_FLAG_SWAMP, Camera.pos.roomNumber)))
SoundEffect(cmd[1] & 0x3FFF, &item->Pose, SoundEnvironment::Always);
}
else
{
SoundEffect(cmd[1] & 0x3FFF, &item->Pose, (SoundEnvironment)TestEnvironment(ENV_FLAG_WATER, item));
SoundEffect(cmd[1] & 0x3FFF, &item->Pose, TestEnvironment(ENV_FLAG_WATER, item) ? SoundEnvironment::Water : SoundEnvironment::Land);
}
break;
@ -336,8 +339,7 @@ void AnimateItem(ItemInfo* item)
break;
}
effectID = cmd[1] & 0x3FFF;
DoFlipEffect(effectID, item);
DoFlipEffect((cmd[1] & 0x3FFF), item);
cmd += 2;
break;
@ -418,9 +420,9 @@ bool TestLastFrame(ItemInfo* item, int animNumber)
return (item->Animation.FrameNumber >= anim->frameEnd);
}
void TranslateItem(ItemInfo* item, short orient, float forward, float vertical, float lateral)
void TranslateItem(ItemInfo* item, short angle, float forward, float vertical, float lateral)
{
item->Pose.Position = TranslateVector(item->Pose.Position, orient, forward, vertical, lateral);
item->Pose.Position = TranslateVector(item->Pose.Position, angle, forward, vertical, lateral);
}
void TranslateItem(ItemInfo* item, Vector3Shrt orient, float distance)
@ -542,7 +544,8 @@ int GetCurrentRelativeFrameNumber(ItemInfo* item)
int GetFrameNumber(ItemInfo* item, int frameToStart)
{
return GetFrameNumber(item->ObjectNumber, item->Animation.AnimNumber, frameToStart);
int animNumber = item->Animation.AnimNumber - Objects[item->ObjectNumber].animIndex;
return GetFrameNumber(item->ObjectNumber, animNumber, frameToStart);
}
int GetFrameNumber(int objectID, int animNumber, int frameToStart)

View file

@ -74,7 +74,7 @@ void AnimateItem(ItemInfo* item);
bool HasStateDispatch(ItemInfo* item, int targetState = -1);
bool TestLastFrame(ItemInfo* item, int animNumber = -1);
void TranslateItem(ItemInfo* item, short orient, float forward, float vertical = 0.0f, float lateral = 0.0f);
void TranslateItem(ItemInfo* item, short angle, float forward, float vertical = 0.0f, float lateral = 0.0f);
void TranslateItem(ItemInfo* item, Vector3Shrt orient, float distance);
void SetAnimation(ItemInfo* item, int animIndex, int frameToStart = 0);

View file

@ -91,7 +91,7 @@ void LookAt(CAMERA_INFO* cam, short roll)
Vector3 target = Vector3(cam->target.x, cam->target.y, cam->target.z);
Vector3 up = Vector3(0.0f, -1.0f, 0.0f);
float fov = TO_RAD(CurrentFOV / 1.333333f);
float r = 0; TO_RAD(roll);
float r = TO_RAD(roll);
g_Renderer.UpdateCameraMatrices(cam, r, fov);
}
@ -1077,7 +1077,7 @@ void BinocularCamera(ItemInfo* item)
// TODO: Some of these inputs should ideally be blocked. @Sezz 2022.05.19
if (InputBusy & (IN_DESELECT | IN_LOOK | IN_DRAW | IN_FLARE | IN_WALK | IN_JUMP))
{
item->MeshBits = -1;
item->MeshBits = ALL_JOINT_BITS;
lara->Inventory.IsBusy = false;
lara->ExtraHeadRot = Vector3Shrt();
lara->ExtraTorsoRot = Vector3Shrt();
@ -1089,7 +1089,7 @@ void BinocularCamera(ItemInfo* item)
}
}
item->MeshBits = 0;
item->MeshBits = NO_JOINT_BITS;
AlterFOV(7 * (ANGLE(11.5f) - BinocularRange));
short headXRot = lara->ExtraHeadRot.x * 2;
@ -1250,11 +1250,11 @@ void BinocularCamera(ItemInfo* item)
firing = true;
if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer)
SoundEffect(SFX_TR4_LARA_HK_SILENCED, nullptr);
SoundEffect(SFX_TR4_HK_SILENCED, nullptr);
else
{
SoundEffect(SFX_TR4_EXPLOSION1, nullptr, SoundEnvironment::Land, 1.0f, 0.4f);
//SoundEffect(SFX_TR4_LARA_HK_FIRE, nullptr);
//SoundEffect(SFX_TR4_HK_FIRE, nullptr);
}
}
else if (lara->Weapons[(int)LaraWeaponType::HK].SelectedAmmo == WeaponAmmoType::Ammo2)
@ -1271,11 +1271,11 @@ void BinocularCamera(ItemInfo* item)
firing = true;
if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer)
SoundEffect(SFX_TR4_LARA_HK_SILENCED, nullptr);
SoundEffect(SFX_TR4_HK_SILENCED, nullptr);
else
{
SoundEffect(SFX_TR4_EXPLOSION1, nullptr, SoundEnvironment::Land, 1.0f, 0.4f);
SoundEffect(SFX_TR4_LARA_HK_FIRE, nullptr);
SoundEffect(SFX_TR4_HK_FIRE, nullptr);
}
}
else
@ -1283,11 +1283,11 @@ void BinocularCamera(ItemInfo* item)
Camera.bounce = -16 - (GetRandomControl() & 0x1F);
if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer)
SoundEffect(SFX_TR4_LARA_HK_SILENCED, nullptr);
SoundEffect(SFX_TR4_HK_SILENCED, nullptr);
else
{
SoundEffect(SFX_TR4_EXPLOSION1, nullptr, SoundEnvironment::Land, 1.0f, 0.4f);
SoundEffect(SFX_TR4_LARA_HK_FIRE, nullptr);
SoundEffect(SFX_TR4_HK_FIRE, nullptr);
}
}
}
@ -1296,11 +1296,11 @@ void BinocularCamera(ItemInfo* item)
if (LSHKTimer)
{
if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer)
SoundEffect(SFX_TR4_LARA_HK_SILENCED, nullptr);
SoundEffect(SFX_TR4_HK_SILENCED, nullptr);
else
{
SoundEffect(SFX_TR4_EXPLOSION1, nullptr, SoundEnvironment::Land, 1.0f, 0.4f);
SoundEffect(SFX_TR4_LARA_HK_FIRE, nullptr);
SoundEffect(SFX_TR4_HK_FIRE, nullptr);
}
}
else
@ -1309,11 +1309,11 @@ void BinocularCamera(ItemInfo* item)
firing = true;
if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer)
SoundEffect(SFX_TR4_LARA_HK_SILENCED, nullptr);
SoundEffect(SFX_TR4_HK_SILENCED, nullptr);
else
{
SoundEffect(SFX_TR4_EXPLOSION1, nullptr, SoundEnvironment::Land, 1.0f, 0.4f);
SoundEffect(SFX_TR4_LARA_HK_FIRE, nullptr);
SoundEffect(SFX_TR4_HK_FIRE, nullptr);
}
}

View file

@ -94,7 +94,7 @@ void GenericSphereBoxCollision(short itemNumber, ItemInfo* laraItem, CollisionIn
}
}
bool GetCollidedObjects(ItemInfo* collidingItem, int radius, bool onlyVisible, ItemInfo** collidedItems, MESH_INFO** collidedMeshes, int ignoreLara)
bool GetCollidedObjects(ItemInfo* collidingItem, int radius, bool onlyVisible, ItemInfo** collidedItems, MESH_INFO** collidedMeshes, bool ignoreLara)
{
short numItems = 0;
short numMeshes = 0;
@ -157,7 +157,7 @@ bool GetCollidedObjects(ItemInfo* collidingItem, int radius, bool onlyVisible, I
if (item == collidingItem ||
item->ObjectNumber == ID_LARA && ignoreLara ||
item->Flags & 0x8000 ||
item->MeshBits == 0 ||
item->MeshBits == NO_JOINT_BITS ||
(Objects[item->ObjectNumber].drawRoutine == NULL && item->ObjectNumber != ID_LARA) ||
(Objects[item->ObjectNumber].collision == NULL && item->ObjectNumber != ID_LARA) ||
onlyVisible && item->Status == ITEM_INVISIBLE ||
@ -1744,7 +1744,7 @@ void AIPickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll
item->Status = ITEM_INVISIBLE;
}
void ObjectCollision(short const itemNumber, ItemInfo* laraItem, CollisionInfo * coll)
void ObjectCollision(short const itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
{
auto* item = &g_Level.Items[itemNumber];

View file

@ -26,7 +26,7 @@ struct OBJECT_COLLISION_BOUNDS
};
void GenericSphereBoxCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll);
bool GetCollidedObjects(ItemInfo* collidingItem, int radius, bool onlyVisible, ItemInfo** collidedItems, MESH_INFO** collidedMeshes, int flag2);
bool GetCollidedObjects(ItemInfo* collidingItem, int radius, bool onlyVisible, ItemInfo** collidedItems, MESH_INFO** collidedMeshes, bool ignoreLara);
bool TestWithGlobalCollisionBounds(ItemInfo* item, ItemInfo* laraItem, CollisionInfo* coll);
void TestForObjectOnLedge(ItemInfo* item, CollisionInfo* coll);

View file

@ -95,39 +95,25 @@ bool TestItemRoomCollisionAABB(ItemInfo* item)
auto test = [item](short x, short y, short z, bool floor)
{
CollisionPosition pos = GetCollision(x, y, z, item->RoomNumber).Position;
if (floor) return y > pos.Floor;
return y < pos.Ceiling;
auto collPos = GetCollision(x, y, z, item->RoomNumber).Position;
if (floor) return y > collPos.Floor;
return y < collPos.Ceiling;
};
bool collided =
test(box.X1, minY, box.Z1, true)
|| test(box.X2, minY, box.Z1, true)
|| test(box.X1, minY, box.Z2, true)
|| test(box.X2, minY, box.Z2, true)
|| test(box.X1, maxY, box.Z1, false)
|| test(box.X2, maxY, box.Z1, false)
|| test(box.X1, maxY, box.Z2, false)
|| test(box.X2, maxY, box.Z2, false);
test(box.X1, minY, box.Z1, true) ||
test(box.X2, minY, box.Z1, true) ||
test(box.X1, minY, box.Z2, true) ||
test(box.X2, minY, box.Z2, true) ||
test(box.X1, maxY, box.Z1, false) ||
test(box.X2, maxY, box.Z1, false) ||
test(box.X1, maxY, box.Z2, false) ||
test(box.X2, maxY, box.Z2, false) ;
return collided;
}
CollisionResult GetCollision(ItemInfo* item, short orient, int forward, int vertical, int lateral)
{
// NOTE: GetRoom() call is necessary to fetch the index of the room directly above in order to perform a correct L-shaped test.
// Probes MUST traverse through portals between rooms, otherwise they will not work correctly.
auto point = TranslateVector(item->Pose.Position, orient, forward, vertical, lateral);
return GetCollision(point.x, point.y, point.z, GetRoom(item->Location, item->Pose.Position.x, point.y, item->Pose.Position.z).roomNumber);
}
CollisionResult GetCollision(Vector3 pos, int roomIndex, short orient, int forward, int vertical, int lateral)
{
auto point = TranslateVector(pos, orient, forward, vertical, lateral);
return GetCollision(point.x, point.y, point.z, roomIndex);
}
// Overload used to quickly get point/room collision parameters at a given item's position.
CollisionResult GetCollision(ItemInfo* item)
{
auto room = item->RoomNumber;
@ -138,6 +124,30 @@ CollisionResult GetCollision(ItemInfo* item)
return result;
}
// Overload used to probe point/room collision parameters from a given item's position.
CollisionResult GetCollision(ItemInfo* item, short angle, float forward, float vertical, float lateral)
{
auto point = TranslateVector(item->Pose.Position, angle, forward, vertical, lateral);
int adjacentRoomNumber = GetRoom(item->Location, item->Pose.Position.x, point.y, item->Pose.Position.z).roomNumber;
return GetCollision(point.x, point.y, point.z, adjacentRoomNumber);
}
// Overload used to probe point/room collision parameters from a given position.
CollisionResult GetCollision(Vector3Int pos, int roomNumber, short angle, float forward, float vertical, float lateral)
{
short tempRoomNumber = roomNumber;
auto location = ROOM_VECTOR{ GetFloor(pos.x, pos.y, pos.z, &tempRoomNumber)->Room, pos.y };
auto point = TranslateVector(pos, angle, forward, vertical, lateral);
int adjacentRoomNumber = GetRoom(location, pos.x, point.y, pos.z).roomNumber;
return GetCollision(point.x, point.y, point.z, adjacentRoomNumber);
}
// Overload used as a universal wrapper across collisional code to replace
// triads of roomNumber-GetFloor()-GetFloorHeight() operations.
// The advantage is that it does NOT modify the incoming roomNumber argument,
// instead storing one modified by GetFloor() within the returned CollisionResult struct.
// This way, no external variables are modified as output arguments.
CollisionResult GetCollision(int x, int y, int z, short roomNumber)
{
auto room = roomNumber;
@ -148,14 +158,16 @@ CollisionResult GetCollision(int x, int y, int z, short roomNumber)
return result;
}
// A reworked legacy GetFloorHeight() function which writes data
// into a special CollisionResult struct instead of global variables.
// It writes for both floor and ceiling heights at the same coordinates, meaning it should be used
// in place of successive GetFloorHeight() and GetCeilingHeight() calls to increase readability.
CollisionResult GetCollision(FloorInfo* floor, int x, int y, int z)
{
CollisionResult result = {};
// Record coordinates.
result.Coordinates.x = x;
result.Coordinates.y = y;
result.Coordinates.z = z;
result.Coordinates = Vector3(x, y, z);
// Return provided block into result as itself.
result.Block = floor;
@ -701,7 +713,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
if (coll->TriangleAtLeft() && !coll->MiddleLeft.FloorSlope)
{
// HACK: Force slight push-out to the left side to avoid stucking
TranslateItem(item, coll->Setup.ForwardAngle + ANGLE(8.0f), item->Animation.Velocity, 0, 0);
TranslateItem(item, coll->Setup.ForwardAngle + ANGLE(8.0f), item->Animation.Velocity);
coll->Shift.x = coll->Setup.OldPosition.x - xPos;
coll->Shift.z = coll->Setup.OldPosition.z - zPos;
@ -753,7 +765,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
if (coll->TriangleAtRight() && !coll->MiddleRight.FloorSlope)
{
// HACK: Force slight push-out to the right side to avoid stucking
TranslateItem(item, coll->Setup.ForwardAngle - ANGLE(8.0f), item->Animation.Velocity, 0, 0);
TranslateItem(item, coll->Setup.ForwardAngle - ANGLE(8.0f), item->Animation.Velocity);
coll->Shift.x = coll->Setup.OldPosition.x - xPos;
coll->Shift.z = coll->Setup.OldPosition.z - zPos;
@ -1265,12 +1277,13 @@ int GetWaterDepth(int x, int y, int z, short roomNumber)
while (floor->RoomAbove(x, y, z).value_or(NO_ROOM) != NO_ROOM)
{
room = &g_Level.Rooms[floor->RoomAbove(x, y, z).value_or(floor->Room)];
if (!TestEnvironment(ENV_FLAG_WATER, room) &&
!TestEnvironment(ENV_FLAG_SWAMP, room))
{
int waterHeight = floor->CeilingHeight(x, z);
floor = GetFloor(x, y, z, &roomNumber);
return (GetFloorHeight(floor, x, y, z) - waterHeight);
int floorHeight = GetCollision(floor, x, y, z).BottomBlock->FloorHeight(x, z);
return (floorHeight - waterHeight);
}
floor = GetSector(room, x - room->x, z - room->z);

View file

@ -81,16 +81,16 @@ struct CollisionSetup
bool BlockFloorSlopeDown; // Treat steep slopes as pits
bool BlockCeilingSlope; // Treat steep slopes on ceilings as walls
bool BlockDeathFloorDown; // Treat death sectors as pits
bool BlockMonkeySwingEdge; // Treat non-monkey sectors as walls
bool BlockMonkeySwingEdge; // Treat non-monkey sectors as walls
bool EnableObjectPush; // Can be pushed by objects
bool EnableSpasm; // Convulse when pushed
// Preserve old parameters to restore later
Vector3Int OldPosition;
int OldState;
int OldAnimNumber;
int OldFrameNumber;
int OldState;
};
struct CollisionInfo
@ -121,11 +121,12 @@ struct CollisionInfo
};
[[nodiscard]] bool TestItemRoomCollisionAABB(ItemInfo* item);
CollisionResult GetCollision(ItemInfo* item, short orient, int forward, int vertical = 0, int lateral = 0);
CollisionResult GetCollision(Vector3 pos, int roomIndex, short orient, int forward, int vertical = 0, int lateral = 0);
CollisionResult GetCollision(FloorInfo* floor, int x, int y, int z);
CollisionResult GetCollision(int x, int y, int z, short roomNumber);
CollisionResult GetCollision(ItemInfo* item);
CollisionResult GetCollision(ItemInfo* item, short angle, float forward, float vertical = 0, float lateral = 0);
CollisionResult GetCollision(Vector3Int pos, int roomNumber, short angle, float forward, float vertical = 0, float lateral = 0);
CollisionResult GetCollision(int x, int y, int z, short roomNumber);
CollisionResult GetCollision(FloorInfo* floor, int x, int y, int z);
void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bool resetRoom = false);
void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, bool resetRoom = false);

View file

@ -35,25 +35,25 @@ int GetSpheres(ItemInfo* item, SPHERE* ptr, int worldSpace, Matrix local)
return num;
}
int TestCollision(ItemInfo* item, ItemInfo* l)
int TestCollision(ItemInfo* item, ItemInfo* laraItem)
{
int flags = 0;
int creatureSphereCount = GetSpheres(item, CreatureSpheres, SPHERES_SPACE_WORLD, Matrix::Identity);
int laraSphereCount = GetSpheres(l, LaraSpheres, SPHERES_SPACE_WORLD, Matrix::Identity);
int laraSphereCount = GetSpheres(laraItem, LaraSpheres, SPHERES_SPACE_WORLD, Matrix::Identity);
l->TouchBits = 0;
laraItem->TouchBits = NO_JOINT_BITS;
if (creatureSphereCount <= 0)
{
item->TouchBits = 0;
item->TouchBits = NO_JOINT_BITS;
return 0;
}
else
{
for (int i = 0; i < creatureSphereCount; i++)
{
SPHERE* ptr1 = &CreatureSpheres[i];
auto* ptr1 = &CreatureSpheres[i];
int x1 = item->Pose.Position.x + ptr1->x;
int y1 = item->Pose.Position.y + ptr1->y;
@ -64,7 +64,7 @@ int TestCollision(ItemInfo* item, ItemInfo* l)
{
for (int j = 0; j < laraSphereCount; j++)
{
SPHERE* ptr2 = &LaraSpheres[j];
auto* ptr2 = &LaraSpheres[j];
int x2 = item->Pose.Position.x + ptr2->x;
int y2 = item->Pose.Position.y + ptr2->y;
@ -78,10 +78,11 @@ int TestCollision(ItemInfo* item, ItemInfo* l)
int dz = z1 - z2;
int r = r1 + r2;
if (SQUARE(dx) + SQUARE(dy) + SQUARE(dz) < SQUARE(r))
if ((pow(dx, 2) + pow(dy, 2) + pow(dz, 2)) < pow(r, 2))
{
l->TouchBits |= (1 << j);
flags |= (1 << i);
item->SetBits(JointBitType::Touch, i);
laraItem->SetBits(JointBitType::Touch, j);
flags |= 1 << i;
break;
}
}
@ -89,7 +90,6 @@ int TestCollision(ItemInfo* item, ItemInfo* l)
}
}
item->TouchBits = flags;
return flags;
}
}
@ -100,7 +100,7 @@ void GetJointAbsPosition(ItemInfo* item, Vector3Int* vec, int joint)
short itemNumber = item - g_Level.Items.data();
// Use matrices done in the renderer and transform the input vector
Vector3 p = Vector3(vec->x, vec->y, vec->z);
auto p = vec->ToVector3();
g_Renderer.GetItemAbsBonePosition(itemNumber, &p, joint);
// Store the result

View file

@ -747,9 +747,9 @@ void CreatureDie(short itemNumber, int explode)
if (explode)
{
if (Objects[item->ObjectNumber].hitEffect)
ExplodingDeath(itemNumber, ALL_MESHBITS, EXPLODE_HIT_EFFECT);
ExplodingDeath(itemNumber, ALL_JOINT_BITS, EXPLODE_HIT_EFFECT);
else
ExplodingDeath(itemNumber, ALL_MESHBITS, EXPLODE_NORMAL);
ExplodingDeath(itemNumber, ALL_JOINT_BITS, EXPLODE_NORMAL);
KillItem(itemNumber);
}

View file

@ -241,7 +241,7 @@ GameStatus ControlPhase(int numFrames, int demoMode)
Camera.bounce = 0;
AlterFOV(ANGLE(80.0f));
LaraItem->MeshBits = 0xFFFFFFFF;
LaraItem->MeshBits = ALL_JOINT_BITS;
Lara.Inventory.IsBusy = false;
ResetLaraFlex(LaraItem);
@ -411,7 +411,7 @@ GameStatus ControlPhase(int numFrames, int demoMode)
RumbleScreen();
PlaySoundSources();
DoFlipEffect(FlipEffect);
DoFlipEffect(FlipEffect, LaraItem);
UpdateFadeScreenAndCinematicBars();
@ -490,7 +490,7 @@ GameStatus DoTitle(int index, std::string const& ambient)
g_GameScript->InitCallbacks();
g_GameStringsHandler->SetCallbackDrawString([](std::string const key, D3DCOLOR col, int x, int y, int flags)
{
g_Renderer.DrawString(float(x)/float(g_Configuration.Width) * ASSUMED_WIDTH_FOR_TEXT_DRAWING, float(y)/float(g_Configuration.Height) * ASSUMED_HEIGHT_FOR_TEXT_DRAWING, key.c_str(), col, flags);
g_Renderer.DrawString(float(x)/float(g_Configuration.Width) * REFERENCE_RES_WIDTH, float(y)/float(g_Configuration.Height) * REFERENCE_RES_HEIGHT, key.c_str(), col, flags);
});
}
@ -576,16 +576,6 @@ GameStatus DoTitle(int index, std::string const& ambient)
GameStatus DoLevel(int index, std::string const& ambient, bool loadFromSavegame)
{
// If not loading a savegame, then clear all the infos
if (!loadFromSavegame)
{
Statistics.Level.Timer = 0;
Statistics.Level.Distance = 0;
Statistics.Level.AmmoUsed = 0;
Statistics.Level.AmmoHits = 0;
Statistics.Level.Kills = 0;
}
// Reset all the globals for the game which needs this
CleanUp();
@ -609,7 +599,7 @@ GameStatus DoLevel(int index, std::string const& ambient, bool loadFromSavegame)
g_GameScript->InitCallbacks();
g_GameStringsHandler->SetCallbackDrawString([](std::string const key, D3DCOLOR col, int x, int y, int flags)
{
g_Renderer.DrawString(float(x)/float(g_Configuration.Width) * ASSUMED_WIDTH_FOR_TEXT_DRAWING, float(y)/float(g_Configuration.Height) * ASSUMED_HEIGHT_FOR_TEXT_DRAWING, key.c_str(), col, flags);
g_Renderer.DrawString(float(x)/float(g_Configuration.Width) * REFERENCE_RES_WIDTH, float(y)/float(g_Configuration.Height) * REFERENCE_RES_HEIGHT, key.c_str(), col, flags);
});
}
@ -637,15 +627,17 @@ GameStatus DoLevel(int index, std::string const& ambient, bool loadFromSavegame)
}
else
{
// If not loading a savegame, then clear all the infos
Statistics.Level = {};
RequiredStartPos = false;
if (InitialiseGame)
{
// Clear all game infos as well
Statistics.Game = {};
GameTimer = 0;
RequiredStartPos = false;
InitialiseGame = false;
}
Statistics.Level.Timer = 0;
}
g_Gui.SetInventoryItemChosen(NO_ITEM);
@ -677,8 +669,6 @@ GameStatus DoLevel(int index, std::string const& ambient, bool loadFromSavegame)
while (DoTheGame)
{
result = ControlPhase(nFrames, 0);
nFrames = DrawPhase();
Sound_UpdateScene();
if (result == GameStatus::ExitToTitle ||
result == GameStatus::LoadGame ||
@ -694,6 +684,9 @@ GameStatus DoLevel(int index, std::string const& ambient, bool loadFromSavegame)
return result;
}
nFrames = DrawPhase();
Sound_UpdateScene();
}
g_GameScript->ResetScripts(true);

View file

@ -45,6 +45,8 @@ constexpr int MAX_ROOMS = 1024;
constexpr int WIBBLE_SPEED = 4;
constexpr int WIBBLE_MAX = UCHAR_MAX - WIBBLE_SPEED + 1;
constexpr auto FPS = 30;
extern int GameTimer;
extern int RumbleTimer;
extern int GlobalCounter;

View file

@ -41,4 +41,4 @@ void MeshSwapToPour(ItemInfo* item);
void MeshSwapFromPour(ItemInfo* item);
void FlashOrange(ItemInfo* item);
void DoFlipEffect(int number, ItemInfo* item = NULL);
void DoFlipEffect(int number, ItemInfo* item = nullptr);

View file

@ -94,7 +94,7 @@ bool GetTargetOnLOS(GameVector* src, GameVector* dest, bool drawTarget, bool fir
Lara.Control.Weapon.Fired = true;
if (Lara.Control.Weapon.GunType == LaraWeaponType::Revolver)
SoundEffect(SFX_TR4_DESSERT_EAGLE_FIRE, nullptr);
SoundEffect(SFX_TR4_REVOLVER_FIRE, nullptr);
}
bool hit = false;

View file

@ -125,7 +125,7 @@ int SwitchTrigger(short itemNumber, short timer)
item->Timer = timer;
item->Status = ITEM_ACTIVE;
if (timer != 1)
item->Timer = 30 * timer;
item->Timer = FPS * timer;
return 1;
}
if (item->TriggerFlags != 6 || item->Animation.ActiveState)
@ -456,7 +456,7 @@ void TestTriggers(FloorInfo* floor, int x, int y, int z, bool heavy, int heavyFl
item->Timer = timer;
if (timer != 1)
item->Timer = 30 * timer;
item->Timer = FPS * timer;
if (triggerType == TRIGGER_TYPES::SWITCH ||
triggerType == TRIGGER_TYPES::HEAVYSWITCH)
@ -523,7 +523,7 @@ void TestTriggers(FloorInfo* floor, int x, int y, int z, bool heavy, int heavyFl
{
if (item->Status == ITEM_INVISIBLE)
{
item->TouchBits = 0;
item->TouchBits = NO_JOINT_BITS;
if (EnableBaddyAI(value, 0))
{
item->Status = ITEM_ACTIVE;
@ -538,7 +538,7 @@ void TestTriggers(FloorInfo* floor, int x, int y, int z, bool heavy, int heavyFl
}
else
{
item->TouchBits = 0;
item->TouchBits = NO_JOINT_BITS;
item->Status = ITEM_ACTIVE;
AddActiveItem(value);
EnableBaddyAI(value, 1);
@ -546,7 +546,7 @@ void TestTriggers(FloorInfo* floor, int x, int y, int z, bool heavy, int heavyFl
}
else
{
item->TouchBits = 0;
item->TouchBits = NO_JOINT_BITS;
AddActiveItem(value);
item->Status = ITEM_ACTIVE;
}
@ -576,7 +576,7 @@ void TestTriggers(FloorInfo* floor, int x, int y, int z, bool heavy, int heavyFl
if (Camera.number != Camera.last || triggerType == TRIGGER_TYPES::SWITCH)
{
Camera.timer = (trigger & 0xFF) * 30;
Camera.timer = (trigger & 0xFF) * FPS;
Camera.type = heavy ? CameraType::Heavy : CameraType::Fixed;
if (trigger & ONESHOT)
g_Level.Cameras[Camera.number].flags |= ONESHOT;
@ -746,7 +746,7 @@ void ProcessSectorFlags(FloorInfo* floor)
}
// Set climb status
if ((1 << (GetQuadrant(LaraItem->Pose.Orientation.y) + 8)) & GetClimbFlags(floor))
if (TestLaraNearClimbableWall(LaraItem, floor))
Lara.Control.CanClimbLadder = true;
else
Lara.Control.CanClimbLadder = false;

View file

@ -11,6 +11,8 @@
#include "Game/Lara/lara.h"
#include "Specific/level.h"
#include "Specific/prng.h"
#include "Specific/setup.h"
#include "Renderer/Renderer11Enums.h"
#define MAX_TRIGGER_RANGE 0x4000
using namespace TEN::Math::Random;
@ -41,7 +43,7 @@ void TriggerChaffEffects(int flareAge)
TriggerChaffEffects(LaraItem, &pos, &vel, LaraItem->Animation.Velocity, (bool)(g_Level.Rooms[LaraItem->RoomNumber].flags & ENV_FLAG_WATER), flareAge);
}
void TriggerChaffEffects(ItemInfo* Item,int age)
void TriggerChaffEffects(ItemInfo* Item, int age)
{
Matrix world
= Matrix::CreateTranslation(-6, 6, 32)
@ -62,10 +64,10 @@ void TriggerChaffEffects(ItemInfo* Item,int age)
vel.y = world.Translation().y;
vel.z = world.Translation().z;
TriggerChaffEffects(Item, &pos, &vel, Item->Animation.Velocity, (bool)(g_Level.Rooms[Item->RoomNumber].flags & ENV_FLAG_WATER),age);
TriggerChaffEffects(Item, &pos, &vel, Item->Animation.Velocity, (bool)(g_Level.Rooms[Item->RoomNumber].flags & ENV_FLAG_WATER), age);
}
void TriggerChaffEffects(ItemInfo* item, Vector3Int* pos, Vector3Int* vel, int speed, bool isUnderwater,int age)
void TriggerChaffEffects(ItemInfo* item, Vector3Int* pos, Vector3Int* vel, int speed, bool isUnderwater, int age)
{
int numSparks = (int)GenerateFloat(2, 5);
for (int i = 0; i < numSparks; i++)
@ -83,7 +85,7 @@ void TriggerChaffEffects(ItemInfo* item, Vector3Int* pos, Vector3Int* vel, int s
color.g = (GetRandomDraw() & 127) + 64;
color.b = 192 - color.g;
TriggerChaffSparkles(pos, vel, &color,age,item);
TriggerChaffSparkles(pos, vel, &color, age, item);
if (isUnderwater)
{
TriggerChaffBubbles(pos, item->RoomNumber);
@ -99,7 +101,7 @@ void TriggerChaffEffects(ItemInfo* item, Vector3Int* pos, Vector3Int* vel, int s
}
void TriggerChaffSparkles (Vector3Int* pos, Vector3Int* vel, CVECTOR* color,int age,ItemInfo* item)
void TriggerChaffSparkles(Vector3Int* pos, Vector3Int* vel, CVECTOR* color, int age, ItemInfo* item)
{
/*
SPARKS* sparkle;
@ -119,7 +121,7 @@ void TriggerChaffSparkles (Vector3Int* pos, Vector3Int* vel, CVECTOR* color,int
sparkle->colFadeSpeed = 3;
sparkle->fadeToBlack = 5;
sparkle->sLife = sparkle->life = 10;
sparkle->transType = TransTypeEnum::COLADD;
sparkle->transType = BLEND_MODES::BLENDMODE_ADDITIVE;
sparkle->dynamic = true;
sparkle->x = pos->x + (GetRandomDraw() & 7) - 3;
@ -174,7 +176,7 @@ void TriggerChaffSmoke(Vector3Int* pos, Vector3Int* vel, int speed, bool moving,
smoke->sLife = rnd;
}
smoke->transType = TransTypeEnum::COLADD;
smoke->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
smoke->x = pos->x + (GetRandomControl() & 7) - 3;
smoke->y = pos->y + (GetRandomControl() & 7) - 3;

View file

@ -29,15 +29,16 @@ using namespace TEN::Effects::Spark;
using namespace TEN::Effects::Environment;
using namespace TEN::Math::Random;
// New particle class
Particle Particles[MAX_PARTICLES];
ParticleDynamic ParticleDynamics[MAX_PARTICLE_DYNAMICS];
FX_INFO EffectList[NUM_EFFECTS];
int NextSpark;
int DeadlyBounds[6];
SPLASH_SETUP SplashSetup;
SPLASH_STRUCT Splashes[MAX_SPLASHES];
RIPPLE_STRUCT Ripples[MAX_RIPPLES];
SPARKS Sparks[MAX_SPARKS];
SP_DYNAMIC SparkDynamics[MAX_SPARKS_DYNAMICS];
LaraWeaponType SmokeWeapon;
byte SmokeCountL;
byte SmokeCountR;
@ -73,9 +74,9 @@ NODEOFFSET_INFO NodeOffsets[MAX_NODE] =
void DetatchSpark(int number, SpriteEnumFlag type)
{
auto* sptr = &Sparks[0];
auto* sptr = &Particles[0];
for (int lp = 0; lp < MAX_SPARKS; lp++, sptr++)
for (int lp = 0; lp < MAX_PARTICLES; lp++, sptr++)
{
if (sptr->on && (sptr->flags & type) && sptr->fxObj == number)
{
@ -115,53 +116,48 @@ void DetatchSpark(int number, SpriteEnumFlag type)
}
}
int GetFreeSpark()
Particle* GetFreeParticle()
{
short sparkNumber = NextSpark;
int result = -1;
for (int i = 0; i < MAX_SPARKS; i++)
// Get first free available spark
for (int i = 0; i < MAX_PARTICLES; i++)
{
auto* spark = &Sparks[sparkNumber];
auto* particle = &Particles[i];
if (!spark->on)
if (!particle->on)
{
NextSpark = (sparkNumber + 1) & 0x3FF;
spark->extras = 0;
spark->dynamic = -1;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex;
return sparkNumber;
}
else if (sparkNumber == 1023)
sparkNumber = 0;
else
{
spark++;
sparkNumber++;
result = i;
break;
}
}
int life = 4095;
for (int i = 0; i < MAX_SPARKS; i++)
{
auto* spark = &Sparks[i];
// No free sparks left, hijack existing one with less possible life
if (spark->life < life &&
spark->dynamic == -1 &&
!(spark->flags & SP_EXPLOSION))
int life = INT_MAX;
if (result == -1)
{
for (int i = 0; i < MAX_PARTICLES; i++)
{
sparkNumber = i;
life = spark->life;
auto* particle = &Particles[i];
if (particle->life < life && particle->dynamic == -1 && !(particle->flags & SP_EXPLOSION))
{
result = i;
life = particle->life;
}
}
}
NextSpark = (sparkNumber + 1) & 0x3FF;
auto* spark = &Particles[result];
auto* spark = &Sparks[sparkNumber];
spark->extras = 0;
spark->dynamic = -1;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex;
return sparkNumber;
spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
return spark;
}
void UpdateSparks()
@ -174,17 +170,18 @@ void UpdateSparks()
DeadlyBounds[4] = LaraItem->Pose.Position.z + bounds->Z1;
DeadlyBounds[5] = LaraItem->Pose.Position.z + bounds->Z2;
for (int i = 0; i < MAX_SPARKS; i++)
for (int i = 0; i < MAX_PARTICLES; i++)
{
auto* spark = &Sparks[i];
auto* spark = &Particles[i];
if (spark->on)
{
spark->life--;
if (!spark->life)
{
if (spark->dynamic != -1)
SparkDynamics[spark->dynamic].On = false;
ParticleDynamics[spark->dynamic].On = false;
spark->on = false;
continue;
@ -292,7 +289,9 @@ void UpdateSparks()
float alpha = (spark->sLife - spark->life) / (float)spark->sLife;
spark->size = lerp(spark->sSize, spark->dSize, alpha);
if (spark->flags & SP_FIRE && !Lara.Burn || spark->flags & SP_DAMAGE)
if ((spark->flags & SP_FIRE && !Lara.Burn) ||
(spark->flags & SP_DAMAGE) ||
(spark->flags & SP_POISON))
{
ds = spark->size * (spark->scalar / 2.0);
@ -304,8 +303,12 @@ void UpdateSparks()
{
if (spark->flags & SP_FIRE)
LaraBurn(LaraItem);
else
if (spark->flags & SP_DAMAGE)
LaraItem->HitPoints -= 2;
if (spark->flags & SP_POISON)
Lara.PoisonPotency += 5;
}
}
}
@ -313,13 +316,13 @@ void UpdateSparks()
}
}
for (int i = 0; i < MAX_SPARKS; i++)
for (int i = 0; i < MAX_PARTICLES; i++)
{
auto* spark = &Sparks[i];
auto* spark = &Particles[i];
if (spark->on && spark->dynamic != -1)
{
auto* dynsp = &SparkDynamics[spark->dynamic];
auto* dynsp = &ParticleDynamics[spark->dynamic];
if (dynsp->Flags & 3)
{
int random = GetRandomControl();
@ -401,7 +404,7 @@ void TriggerCyborgSpark(int x, int y, int z, short xv, short yv, short zv)
if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384)
{
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
int random = rand();
@ -416,7 +419,7 @@ void TriggerCyborgSpark(int x, int y, int z, short xv, short yv, short zv)
spark->dB = -64 - ((random & 0x7F) + 64);
spark->life = 10;
spark->sLife = 10;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->friction = 34;
spark->scalar = 1;
spark->x = (random & 7) + x - 3;
@ -445,7 +448,7 @@ void TriggerExplosionBubbles(int x, int y, int z, short roomNumber)
if (dx >= -ANGLE(90.0f) && dx <= ANGLE(90.0f) && dz >= -ANGLE(90.0f) && dz <= ANGLE(90.0f))
{
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->sR = -128;
spark->dR = -128;
@ -458,7 +461,7 @@ void TriggerExplosionBubbles(int x, int y, int z, short roomNumber)
spark->sB = 0;
spark->colFadeSpeed = 8;
spark->fadeToBlack = 12;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->x = x;
spark->y = y;
spark->z = z;
@ -467,7 +470,7 @@ void TriggerExplosionBubbles(int x, int y, int z, short roomNumber)
spark->zVel = 0;
spark->friction = 0;
spark->flags = SP_UNDERWEXP | SP_DEF | SP_SCALE;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex + 13;
spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + 13;
spark->scalar = 3;
spark->gravity = 0;
spark->maxYvel = 0;
@ -490,7 +493,7 @@ void TriggerExplosionBubbles(int x, int y, int z, short roomNumber)
void TriggerExplosionSmokeEnd(int x, int y, int z, int uw)
{
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = true;
@ -518,9 +521,9 @@ void TriggerExplosionSmokeEnd(int x, int y, int z, int uw)
spark->life = spark->sLife= (GetRandomControl() & 0x1F) + 96;
if (uw)
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
else
spark->transType = TransTypeEnum::COLSUB;
spark->blendMode = BLEND_MODES::BLENDMODE_SUBTRACTIVE;
spark->x = (GetRandomControl() & 0x1F) + x - 16;
spark->y = (GetRandomControl() & 0x1F) + y - 16;
@ -571,7 +574,7 @@ void TriggerExplosionSmoke(int x, int y, int z, int uw)
if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384)
{
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->sR = -112;
spark->sG = -112;
@ -582,7 +585,7 @@ void TriggerExplosionSmoke(int x, int y, int z, int uw)
spark->dB = 64;
spark->colFadeSpeed = 2;
spark->fadeToBlack = 8;
spark->transType = TransTypeEnum::COLSUB;
spark->blendMode = BLEND_MODES::BLENDMODE_SUBTRACTIVE;
spark->life = spark->sLife = (GetRandomControl() & 3) + 10;
spark->x = (GetRandomControl() & 0x1FF) + x - 256;
spark->y = (GetRandomControl() & 0x1FF) + y - 256;
@ -616,7 +619,7 @@ void TriggerExplosionSmoke(int x, int y, int z, int uw)
if (dx < -16384 || dx > 16384 || dz < -16384 || dz > 16384)
return;
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = true;
@ -698,7 +701,7 @@ void TriggerExplosionSmoke(int x, int y, int z, int uw)
spark->sLife = spark->life = (GetRandomControl() & 3) + 28;
}
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
if (fxObj == -1)
{
@ -811,7 +814,7 @@ void TriggerSuperJetFlame(ItemInfo* item, int yvel, int deadly)
if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384)
{
int size = (GetRandomControl() & 0x1FF) - yvel;
auto* sptr = &Sparks[GetFreeSpark()];
auto* sptr = GetFreeParticle();
if (size < 512)
size = 512;
@ -824,7 +827,7 @@ void TriggerSuperJetFlame(ItemInfo* item, int yvel, int deadly)
sptr->dB = 32;
sptr->colFadeSpeed = 8;
sptr->fadeToBlack = 8;
sptr->transType = TransTypeEnum::COLADD;
sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->life = sptr->sLife = (size >> 9) + (GetRandomControl() & 7) + 16;
sptr->x = (GetRandomControl() & 0x1F) + item->Pose.Position.x - 16;
sptr->y = (GetRandomControl() & 0x1F) + item->Pose.Position.y - 16;
@ -1087,7 +1090,7 @@ void Richochet(PHD_3DPOS* pos)
target.y = pos->Position.y;
target.z = pos->Position.z;
TriggerRicochetSpark(&target, angle / 16, 3, 0);
SoundEffect(SFX_TR4_LARA_RICOCHET, pos);
SoundEffect(SFX_TR4_WEAPON_RICOCHET, pos);
}
void ControlWaterfallMist(short itemNumber) // ControlWaterfallMist
@ -1103,7 +1106,6 @@ void ControlWaterfallMist(short itemNumber) // ControlWaterfallMist
void TriggerWaterfallMist(int x, int y, int z, int angle)
{
SPARKS* spark;
int dh = 0;
int ang1 = angle;
int ang2 = angle;
@ -1112,7 +1114,7 @@ void TriggerWaterfallMist(int x, int y, int z, int angle)
// CHECK THIS LOOP CONDITIONS
for (ang1 = angle; ; ang1 *= 2)
{
spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = 1;
spark->sR = 64;
spark->sG = 64;
@ -1121,7 +1123,7 @@ void TriggerWaterfallMist(int x, int y, int z, int angle)
spark->dG = 64;
spark->dB = 64;
spark->colFadeSpeed = 1;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 3) + 6;
spark->fadeToBlack = spark->life - 4;
dl = ((dh + (GlobalCounter << 6)) % 1536) + (GetRandomControl() & 0x3F) - 32;
@ -1146,7 +1148,7 @@ void TriggerWaterfallMist(int x, int y, int z, int angle)
break;
}
spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = 1;
spark->sR = 96;
spark->sG = 96;
@ -1155,7 +1157,7 @@ void TriggerWaterfallMist(int x, int y, int z, int angle)
spark->dG = 96;
spark->dB = 96;
spark->colFadeSpeed = 1;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 3) + 6;
spark->fadeToBlack = spark->life - 1;
dl = GetRandomControl() % 1408 + 64;
@ -1168,7 +1170,7 @@ void TriggerWaterfallMist(int x, int y, int z, int angle)
spark->flags = 10;
spark->yVel = GetRandomControl() & 0x100 + (GetRandomControl() & 0x7F) + 128;
spark->scalar = 2;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex + 17;
spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + 17;
spark->gravity = 0;
spark->maxYvel = 0;
spark->sSize = spark->size = (GetRandomControl() & 7) + 8;
@ -1280,7 +1282,7 @@ void Splash(ItemInfo* item)
void TriggerRocketFlame(int x, int y, int z, int xv, int yv, int zv, int itemNumber)
{
auto* sptr = &Sparks[GetFreeSpark()];
auto* sptr = GetFreeParticle();
sptr->on = true;
sptr->sR = 48 + (GetRandomControl() & 31);
@ -1294,7 +1296,7 @@ void TriggerRocketFlame(int x, int y, int z, int xv, int yv, int zv, int itemNum
sptr->colFadeSpeed = 12 + (GetRandomControl() & 3);
sptr->fadeToBlack = 12;
sptr->sLife = sptr->life = (GetRandomControl() & 3) + 28;
sptr->transType = TransTypeEnum::COLADD;
sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = 0;
sptr->dynamic = -1;
@ -1328,7 +1330,7 @@ void TriggerRocketFlame(int x, int y, int z, int xv, int yv, int zv, int itemNum
sptr->maxYvel = 0;
// TODO: right sprite
sptr->def = Objects[ID_DEFAULT_SPRITES].meshIndex;
sptr->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex;
sptr->scalar = 2;
int size = (GetRandomControl() & 7) + 32;
@ -1337,7 +1339,7 @@ void TriggerRocketFlame(int x, int y, int z, int xv, int yv, int zv, int itemNum
void TriggerRocketFire(int x, int y, int z)
{
auto* sptr = &Sparks[GetFreeSpark()];
auto* sptr = GetFreeParticle();
sptr->on = true;
@ -1350,7 +1352,7 @@ void TriggerRocketFire(int x, int y, int z)
sptr->colFadeSpeed = 4 + (GetRandomControl() & 3);
sptr->fadeToBlack = 12;
sptr->sLife = sptr->life = (GetRandomControl() & 3) + 20;
sptr->transType = TransTypeEnum::COLADD;
sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = 0;
sptr->dynamic = -1;
@ -1376,7 +1378,7 @@ void TriggerRocketFire(int x, int y, int z)
sptr->flags = SP_SCALE | SP_DEF | SP_EXPDEF;
// TODO: right sprite
sptr->def = Objects[ID_DEFAULT_SPRITES].meshIndex;
sptr->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex;
sptr->scalar = 1;
sptr->gravity = -(GetRandomControl() & 3) - 4;
sptr->maxYvel = -(GetRandomControl() & 3) - 4;
@ -1389,7 +1391,7 @@ void TriggerRocketFire(int x, int y, int z)
void TriggerRocketSmoke(int x, int y, int z, int bodyPart)
{
/*SPARKS* sptr = &Sparks[GetFreeSpark()];
/*auto* sptr = GetFreeParticle();
sptr->on = true;
sptr->sR = 0;
@ -1403,7 +1405,7 @@ void TriggerRocketSmoke(int x, int y, int z, int bodyPart)
sptr->colFadeSpeed = 4 + (GetRandomControl() & 3);
sptr->fadeToBlack = 12;
sptr->sLife = sptr->life = (GetRandomControl() & 3) + 20;
sptr->transType = TransTypeEnum::COLADD;
sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = 0;
sptr->dynamic = -1;
@ -1457,7 +1459,7 @@ void TriggerFlashSmoke(int x, int y, int z, short roomNumber)
spark->dShade = -128;
spark->colFadeSpeed = 4;
spark->fadeToBlack = 16;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 0xF) + 64;
spark->x = (GetRandomControl() & 0x1F) + x - 16;
spark->y = (GetRandomControl() & 0x1F) + y - 16;
@ -1502,7 +1504,7 @@ void TriggerFireFlame(int x, int y, int z, int fxObj, int type)
if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384)
{
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = true;
@ -1560,7 +1562,7 @@ void TriggerFireFlame(int x, int y, int z, int fxObj, int type)
spark->life = spark->sLife = (GetRandomControl() & 3) + 18;
}
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
if (fxObj != -1)
{
@ -1705,7 +1707,7 @@ void TriggerMetalSparks(int x, int y, int z, int xv, int yv, int zv, int additio
{
int r = rand();
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->dG = (r & 0x7F) + 64;
spark->dB = -64 - (r & 0x7F) + 64;
@ -1720,7 +1722,7 @@ void TriggerMetalSparks(int x, int y, int z, int xv, int yv, int zv, int additio
spark->colFadeSpeed = 3;
spark->fadeToBlack = 5;
spark->y = ((r >> 3) & 7) + y - 3;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->friction = 34;
spark->scalar = 1;
spark->z = ((r >> 6) & 7) + z - 3;
@ -1737,12 +1739,12 @@ void TriggerMetalSparks(int x, int y, int z, int xv, int yv, int zv, int additio
if (additional)
{
r = rand();
spark = &Sparks[GetFreeSpark()];
spark = GetFreeParticle();
spark->on = 1;
spark->sR = spark->dR >> 1;
spark->sG = spark->dG >> 1;
spark->fadeToBlack = 4;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->colFadeSpeed = (r & 3) + 8;
spark->sB = spark->dB >> 1;
spark->dR = 32;

View file

@ -1,5 +1,6 @@
#pragma once
#include "Specific/phd_global.h"
#include "Renderer/Renderer11Enums.h"
enum class LaraWeaponType;
struct ItemInfo;
@ -33,23 +34,8 @@ enum SpriteEnumFlag
SP_DAMAGE = 0x0400,
SP_UNDERWEXP = 0x0800,
SP_NODEATTACH = 0x1000,
SP_PLASMAEXP = 0x2000
};
enum class TransTypeEnum
{
NOTRANS,
SEMITRANS,
COLADD,
COLSUB,
WEIRD
};
enum FireSizeEnum
{
SP_NORMALFIRE,
SP_SMALLFIRE,
SP_BIGFIRE
SP_PLASMAEXP = 0x2000,
SP_POISON = 0x4000
};
struct FX_INFO
@ -98,7 +84,7 @@ struct RIPPLE_STRUCT
unsigned char init;
};
struct SPARKS
struct Particle
{
int x;
int y;
@ -114,24 +100,24 @@ struct SPARKS
float size;
unsigned char friction;
unsigned char scalar;
unsigned char def;
unsigned char spriteIndex;
signed char rotAdd;
signed char maxYvel;
bool on;
byte sR;
byte sG;
byte sB;
byte dR;
byte dG;
byte dB;
byte r;
byte g;
byte b;
unsigned char sR;
unsigned char sG;
unsigned char sB;
unsigned char dR;
unsigned char dG;
unsigned char dB;
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char colFadeSpeed;
unsigned char fadeToBlack;
unsigned char sLife;
unsigned char life;
TransTypeEnum transType;
int sLife;
int life;
BLEND_MODES blendMode;
unsigned char extras;
signed char dynamic;
unsigned char fxObj;
@ -160,7 +146,7 @@ struct SPLASH_STRUCT
bool isActive;
};
struct SP_DYNAMIC
struct ParticleDynamic
{
byte On;
byte Falloff;
@ -176,29 +162,37 @@ constexpr auto SD_UWEXPLOSION = 2;
#define MAX_NODE 23
#define MAX_DYNAMICS 64
#define MAX_SPARKS 1024
#define MAX_RIPPLES 256
#define MAX_SPLASHES 8
#define MAX_SPARKS_DYNAMICS 8
#define NUM_EFFECTS 256
extern int NextSpark;
extern int DeadlyBounds[6];
// New particle class
constexpr auto MAX_PARTICLES = 1024;
constexpr auto MAX_PARTICLE_DYNAMICS = 8;
extern Particle Particles[MAX_PARTICLES];
extern ParticleDynamic ParticleDynamics[MAX_PARTICLE_DYNAMICS];
extern SPLASH_SETUP SplashSetup;
extern SPLASH_STRUCT Splashes[MAX_SPLASHES];
extern RIPPLE_STRUCT Ripples[MAX_RIPPLES];
extern SPARKS Sparks[MAX_SPARKS];
extern SP_DYNAMIC SparkDynamics[MAX_SPARKS_DYNAMICS];
extern LaraWeaponType SmokeWeapon;
extern byte SmokeCountL;
extern byte SmokeCountR;
extern int SplashCount;
extern Vector3Int NodeVectors[MAX_NODE];
extern NODEOFFSET_INFO NodeOffsets[MAX_NODE];
extern FX_INFO EffectList[NUM_EFFECTS];
Particle* GetFreeParticle();
void DetatchSpark(int num, SpriteEnumFlag type);
int GetFreeSpark();
void UpdateSparks();
void TriggerRicochetSpark(GameVector* pos, short angle, int num, int unk);
void TriggerCyborgSpark(int x, int y, int z, short xv, short yv, short zv);

View file

@ -58,12 +58,12 @@ namespace Footprints {
if (result.Position.Bridge >= 0)
return;
auto fx = SOUND_EFFECTS::SFX_TR4_LARA_FEET;
auto fx = SOUND_EFFECTS::SFX_TR4_LARA_FOOTSTEPS;
// Choose material for footstep sound
switch (floor->Material)
{
case FLOOR_MATERIAL::Concrete:
fx = SOUND_EFFECTS::SFX_TR4_LARA_FEET;
fx = SOUND_EFFECTS::SFX_TR4_LARA_FOOTSTEPS;
break;
case FLOOR_MATERIAL::Grass:
@ -107,7 +107,7 @@ namespace Footprints {
break;
case FLOOR_MATERIAL::Stone:
fx = SOUND_EFFECTS::SFX_TR4_LARA_FEET;
fx = SOUND_EFFECTS::SFX_TR4_LARA_FOOTSTEPS;
break;
case FLOOR_MATERIAL::Water:
@ -152,7 +152,7 @@ namespace Footprints {
}
// HACK: must be here until reference wad2 is revised
if (fx != SOUND_EFFECTS::SFX_TR4_LARA_FEET)
if (fx != SOUND_EFFECTS::SFX_TR4_LARA_FOOTSTEPS)
SoundEffect(fx, &item->Pose);
if (floor->Material != FLOOR_MATERIAL::Sand &&
@ -209,9 +209,9 @@ namespace Footprints {
footprint.Position[1] = p1;
footprint.Position[2] = p2;
footprint.Position[3] = p3;
footprint.LifeStartFading = 30 * 10;
footprint.StartOpacity = 0.25f;
footprint.Life = 30 * 20;
footprint.LifeStartFading = FPS * 10;
footprint.Life = FPS * 20;
footprint.Active = true;
footprint.RightFoot = rightFoot;

View file

@ -8,6 +8,7 @@
#include "Game/effects/smoke.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_helpers.h"
#include "Specific/level.h"
using namespace TEN::Effects::Smoke;
@ -19,7 +20,7 @@ namespace TEN::Effects::Lara
if (!item->Data.is<LaraInfo*>())
return;
auto lara = (LaraInfo*&)item->Data;
auto* lara = GetLaraInfo(item);
if (!lara->Burn && !lara->BurnSmoke)
{
@ -37,7 +38,7 @@ namespace TEN::Effects::Lara
if (!item->Data.is<LaraInfo*>())
return;
auto lara = (LaraInfo*&)item->Data;
auto* lara = GetLaraInfo(item);
if (item->HitPoints >= 0 && lara->Control.WaterStatus != WaterStatus::FlyCheat)
{
@ -62,7 +63,7 @@ namespace TEN::Effects::Lara
if (!item->Data.is<LaraInfo*>())
return;
auto lara = (LaraInfo*&)item->Data;
auto* lara = GetLaraInfo(item);
if (lara->Control.WaterStatus == WaterStatus::Underwater || item->HitPoints <= 0)
return;

View file

@ -380,7 +380,7 @@ namespace TEN::Effects::Lightning
void TriggerLightningGlow(int x, int y, int z, byte size, byte r, byte g, byte b)
{
SPARKS* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->dG = g;
spark->sG = g;
@ -389,7 +389,7 @@ namespace TEN::Effects::Lightning
spark->dR = r;
spark->sR = r;
spark->colFadeSpeed = 2;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->on = 1;
spark->dB = b;
spark->sB = b;
@ -403,7 +403,7 @@ namespace TEN::Effects::Lightning
spark->flags = SP_DEF | SP_SCALE;
spark->scalar = 3;
spark->maxYvel = 0;
spark->def = Objects[ID_MISC_SPRITES].meshIndex;
spark->spriteIndex = Objects[ID_MISC_SPRITES].meshIndex;
spark->gravity = 0;
spark->dSize = spark->sSize = spark->size = size + (GetRandomControl() & 3);
}

View file

@ -201,7 +201,154 @@ void TriggerGlobalFireFlame()
spark->dSize = spark->size;
}
void keep_those_fires_burning()
void TriggerPilotFlame(int itemNum, int nodeIndex)
{
auto* item = &g_Level.Items[itemNum];
int dx = Camera.pos.x - item->Pose.Position.x;
int dz = Camera.pos.z - item->Pose.Position.z;
if (dx < -SECTOR(16) || dx > SECTOR(16) || dz < -SECTOR(16) || dz > SECTOR(16))
return;
auto* spark = GetFreeParticle();
spark->on = 1;
spark->sR = 48 + (GetRandomControl() & 31);
spark->sG = spark->sR;
spark->sB = 192 + (GetRandomControl() & 63);
spark->dR = 192 + (GetRandomControl() & 63);
spark->dG = 128 + (GetRandomControl() & 63);
spark->dB = 32;
spark->colFadeSpeed = 12 + (GetRandomControl() & 3);
spark->fadeToBlack = 4;
spark->sLife = spark->life = (GetRandomControl() & 3) + 20;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->extras = 0;
spark->dynamic = -1;
spark->fxObj = itemNum;
spark->x = (GetRandomControl() & 31) - 16;
spark->y = (GetRandomControl() & 31) - 16;
spark->z = (GetRandomControl() & 31) - 16;
spark->xVel = (GetRandomControl() & 31) - 16;
spark->yVel = -(GetRandomControl() & 3);
spark->zVel = (GetRandomControl() & 31) - 16;
spark->flags = SP_SCALE | SP_DEF | SP_EXPDEF | SP_ITEM | SP_NODEATTACH;
spark->nodeNumber = nodeIndex;
spark->friction = 4;
spark->gravity = -(GetRandomControl() & 3) - 2;
spark->maxYvel = -(GetRandomControl() & 3) - 4;
//spark->def = Objects[EXPLOSION1].mesh_index;
spark->scalar = 0;
int size = (GetRandomControl() & 7) + 32;
spark->size = size / 2;
spark->dSize = size;
}
Particle* SetupPoisonSpark(Vector3 color)
{
auto* spark = GetFreeParticle();
bool rMax = color.x > color.y && color.x > color.z;
bool gMax = color.y > color.x && color.y > color.z;
bool bMax = color.z > color.x && color.z > color.y;
char seed = (GetRandomControl() & 0x1F) + 220;
spark->sR = (rMax ? seed : 255) * (color.x * 0.4);
spark->sG = (gMax ? seed : 255) * (color.y * 0.4);
spark->sB = (bMax ? seed : 255) * (color.z * 0.4);
spark->dR = 255 * color.x;
spark->dG = 255 * color.y;
spark->dB = 255 * color.z;
spark->colFadeSpeed = 14;
spark->fadeToBlack = 8;
spark->blendMode = BLEND_MODES::BLENDMODE_SCREEN;
return spark;
}
Particle* SetupFireSpark()
{
auto* spark = GetFreeParticle();
spark->sR = (GetRandomControl() & 0x1F) + 48;
spark->sG = 38;
spark->sB = 255;
spark->dR = (GetRandomControl() & 0x3F) - 64;
spark->dG = (GetRandomControl() & 0x3F) + -128;
spark->dB = 32;
spark->colFadeSpeed = 12;
spark->fadeToBlack = 8;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
return spark;
}
void AttachAndCreateSpark(Particle* spark, ItemInfo* item, int meshIndex, Vector3Int offset, Vector3Int speed)
{
auto pos1 = Vector3Int(-4, -30, -4) + offset;
GetJointAbsPosition(item, &pos1, meshIndex);
spark->x = (GetRandomControl() & 0x1F) + pos1.x - 16;
spark->y = (GetRandomControl() & 0x1F) + pos1.y - 16;
spark->z = (GetRandomControl() & 0x1F) + pos1.z - 16;
auto pos2 = Vector3Int(-4, -30, -4) + offset + speed;
GetJointAbsPosition(item, &pos2, meshIndex);
int v = (GetRandomControl() & 0x3F) + 192;
spark->life = spark->sLife = v / 6;
spark->xVel = v * (pos2.x - pos1.x) / 10;
spark->yVel = v * (pos2.y - pos1.y) / 10;
spark->zVel = v * (pos2.z - pos1.z) / 10;
spark->friction = 85;
spark->gravity = -16 - (GetRandomControl() & 0x1F);
spark->maxYvel = 0;
spark->flags = SP_FIRE | SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF;
spark->scalar = 3;
spark->dSize = (v * ((GetRandomControl() & 7) + 60)) / 256;
spark->sSize = spark->dSize / 4;
spark->size = spark->dSize / 2;
spark->on = 1;
}
void ThrowFire(int itemNum, int meshIndex, Vector3Int offset, Vector3Int speed)
{
auto* item = &g_Level.Items[itemNum];
for (int i = 0; i < 3; i++)
{
auto* spark = SetupFireSpark();
AttachAndCreateSpark(spark, item, meshIndex, offset, speed);
spark->flags = SP_FIRE | SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF;
}
}
void ThrowPoison(int itemNum, int meshIndex, Vector3Int offset, Vector3Int speed, Vector3 color)
{
auto* item = &g_Level.Items[itemNum];
for (int i = 0; i < 2; i++)
{
auto* spark = SetupPoisonSpark(color);
AttachAndCreateSpark(spark, item, meshIndex, offset, speed);
spark->flags = SP_POISON | SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF;
}
}
void UpdateFireProgress()
{
TriggerGlobalStaticFlame();
if (!(Wibble & 0xF))
@ -212,7 +359,7 @@ void keep_those_fires_burning()
}
}
void AddFire(int x, int y, int z, char size, short roomNum, short on)
void AddFire(int x, int y, int z, short roomNum, float size, short fade)
{
FIRE_LIST* fptr = &Fires[0];
int i = 0;
@ -223,8 +370,8 @@ void AddFire(int x, int y, int z, char size, short roomNum, short on)
return;
}
if (on)
fptr->on = on;
if (fade)
fptr->on = fade;
else
fptr->on = 1;
@ -232,19 +379,7 @@ void AddFire(int x, int y, int z, char size, short roomNum, short on)
fptr->y = y;
fptr->z = z;
fptr->roomNumber = roomNum;
switch (size)
{
case SP_NORMALFIRE:
fptr->size = 1;
break;
case SP_SMALLFIRE:
fptr->size = 2;
break;
case SP_BIGFIRE:
fptr->size = 3;
break;
}
fptr->size = size;
}
void ClearFires()
@ -255,7 +390,7 @@ void ClearFires()
void UpdateFireSparks()
{
keep_those_fires_burning();
UpdateFireProgress();
for (int i = 0; i < MAX_SPARKS_FIRE; i++)
{
@ -503,7 +638,7 @@ void TriggerGunSmoke(int x, int y, int z, short xv, short yv, short zv, byte ini
spark->dShade = 64;
}
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->x = x + (GetRandomControl() & 31) - 16;
spark->y = y + (GetRandomControl() & 31) - 16;
spark->z = z + (GetRandomControl() & 31) - 16;
@ -586,7 +721,7 @@ void TriggerShatterSmoke(int x, int y, int z)
spark->colFadeSpeed = 4;
spark->dShade = (GetRandomControl() & 0x1F) + 64;
spark->fadeToBlack = 24 - (GetRandomControl() & 7);
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 7) + 48;
spark->x = (GetRandomControl() & 0x1F) + x - 16;
spark->y = (GetRandomControl() & 0x1F) + y - 16;
@ -959,7 +1094,7 @@ void UpdateGunShells()
int ceiling = GetCeiling(floor, gs->pos.Position.x, gs->pos.Position.y, gs->pos.Position.z);
if (gs->pos.Position.y < ceiling)
{
SoundEffect(SFX_TR4_LARA_SHOTGUN_SHELL, &gs->pos);
SoundEffect(SFX_TR4_SHOTGUN_SHELL, &gs->pos);
gs->speed -= 4;
if (gs->speed < 8)
@ -975,7 +1110,7 @@ void UpdateGunShells()
int height = GetFloorHeight(floor, gs->pos.Position.x, gs->pos.Position.y, gs->pos.Position.z);
if (gs->pos.Position.y >= height)
{
SoundEffect(SFX_TR4_LARA_SHOTGUN_SHELL, &gs->pos);
SoundEffect(SFX_TR4_SHOTGUN_SHELL, &gs->pos);
gs->speed -= 8;
if (gs->speed >= 8)
{
@ -1004,7 +1139,7 @@ void AddWaterSparks(int x, int y, int z, int num)
{
for (int i = 0; i < num; i++)
{
SPARKS* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = 1;
spark->sR = 127;
@ -1020,7 +1155,7 @@ void AddWaterSparks(int x, int y, int z, int num)
spark->sSize = 8;
spark->dSize = 32;
spark->scalar = 1;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
int random = GetRandomControl() & 0xFFF;
spark->xVel = -phd_sin(random << 4) * 128;
spark->yVel = -GenerateInt(128, 256);
@ -1203,7 +1338,7 @@ void TriggerLaraDrips(ItemInfo* item)
}
}
int ExplodingDeath(short itemNumber, int meshBits, short flags)
int ExplodingDeath(short itemNumber, unsigned int meshBits, short flags)
{
ItemInfo* item = &g_Level.Items[itemNumber];
ObjectInfo* obj = &Objects[item->ObjectNumber];
@ -1319,7 +1454,7 @@ int ExplodingDeath(short itemNumber, int meshBits, short flags)
}
}
return item->MeshBits == 0;
return item->MeshBits == NO_JOINT_BITS;
}
int GetFreeShockwave()
@ -1355,7 +1490,7 @@ void TriggerShockwave(PHD_3DPOS* pos, short innerRad, short outerRad, int speed,
sptr->b = b;
sptr->life = life;
SoundEffect(SFX_TR5_IMP_STONEHIT, pos);
SoundEffect(SFX_TR5_IMP_STONE_HIT, pos);
}
}
@ -1366,7 +1501,7 @@ void TriggerShockwaveHitEffect(int x, int y, int z, byte r, byte g, byte b, shor
if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384)
{
SPARKS* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->dB = b;
spark->on = true;
spark->sR = 0;
@ -1376,7 +1511,7 @@ void TriggerShockwaveHitEffect(int x, int y, int z, byte r, byte g, byte b, shor
spark->dR = r;
spark->colFadeSpeed = 4;
spark->fadeToBlack = 8;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 3) + 16;
int speed = (GetRandomControl() & 0xF) + vel;
@ -1407,7 +1542,7 @@ void TriggerShockwaveHitEffect(int x, int y, int z, byte r, byte g, byte b, shor
spark->rotAdd = (GetRandomControl() & 0xF) + 16;
spark->scalar = 1;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_UNDERWATERDUST;
spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_UNDERWATERDUST;
spark->maxYvel = 0;
spark->gravity = (GetRandomControl() & 0x3F) + 64;
spark->sSize = spark->size = (GetRandomControl() & 0x1F) + 32;
@ -1472,7 +1607,7 @@ void TriggerExplosionBubble(int x, int y, int z, short roomNumber)
if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384)
{
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->sR = 128;
spark->dR = 128;
@ -1485,7 +1620,7 @@ void TriggerExplosionBubble(int x, int y, int z, short roomNumber)
spark->sB = 0;
spark->colFadeSpeed = 8;
spark->fadeToBlack = 12;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->x = x;
spark->y = y;
spark->z = z;
@ -1496,7 +1631,7 @@ void TriggerExplosionBubble(int x, int y, int z, short roomNumber)
spark->flags = 2058;
spark->scalar = 3;
spark->gravity = 0;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex + 13;
spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + 13;
spark->maxYvel = 0;
int size = (GetRandomControl() & 7) + 63;
spark->sSize = size >> 1;
@ -1516,7 +1651,7 @@ void TriggerExplosionBubble(int x, int y, int z, short roomNumber)
/*void TriggerExplosionSmokeEnd(int x, int y, int z, int unk)
{
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = 1;
if (unk)
@ -1543,9 +1678,9 @@ void TriggerExplosionBubble(int x, int y, int z, short roomNumber)
spark->life = spark->sLife = (GetRandomControl() & 0x1F) + 96;
if (unk)
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
else
spark->transType = 3;
spark->blendMode = 3;
spark->x = (GetRandomControl() & 0x1F) + x - 16;
spark->y = (GetRandomControl() & 0x1F) + y - 16;
@ -1603,7 +1738,7 @@ void TriggerExplosionBubble(int x, int y, int z, short roomNumber)
void TriggerFenceSparks(int x, int y, int z, int kill, int crane)
{
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = 1;
spark->sR = (GetRandomControl() & 0x3F) - 0x40;
@ -1618,7 +1753,7 @@ void TriggerFenceSparks(int x, int y, int z, int kill, int crane)
spark->life = (GetRandomControl() & 7) + 24;
spark->sLife = (GetRandomControl() & 7) + 24;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->dynamic = -1;
spark->x = x;
@ -1643,7 +1778,7 @@ void TriggerSmallSplash(int x, int y, int z, int number)
{
for (int i = 0; i < number; i++)
{
auto* sptr = &Sparks[GetFreeSpark()];
auto* sptr = GetFreeParticle();
sptr->on = 1;
@ -1661,7 +1796,7 @@ void TriggerSmallSplash(int x, int y, int z, int number)
sptr->life = 24;
sptr->sLife = 24;
sptr->transType = TransTypeEnum::COLADD;
sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
int angle = GetRandomControl() << 3;

View file

@ -2,6 +2,7 @@
#include "Game/effects/effects.h"
#include "Game/Lara/lara_struct.h"
#include "Specific/phd_global.h"
#include "Renderer/Renderer11Enums.h"
enum class LaraWeaponType;
struct ItemInfo;
@ -33,7 +34,7 @@ struct SMOKE_SPARKS
byte fadeToBlack;
signed char sLife;
signed char life;
TransTypeEnum transType;
BLEND_MODES blendMode;
byte fxObj;
byte nodeNumber;
byte mirror;
@ -96,7 +97,7 @@ struct FIRE_LIST
int y;
int z;
byte on;
byte size;
float size;
short roomNumber;
};
@ -179,14 +180,17 @@ extern int NextBlood;
extern int NextSpider;
extern int NextGunShell;
#define MAX_SPARKS_FIRE 20
#define MAX_FIRE_LIST 32
#define MAX_SPARKS_SMOKE 32
#define MAX_SPARKS_BLOOD 32
#define MAX_GUNFLASH 4
#define MAX_GUNSHELL 24
#define MAX_DRIPS 32
#define MAX_SHOCKWAVE 16
constexpr auto MAX_SPARKS_FIRE = 20;
constexpr auto MAX_FIRE_LIST = 32;
constexpr auto MAX_SPARKS_SMOKE = 32;
constexpr auto MAX_SPARKS_BLOOD = 32;
constexpr auto MAX_GUNFLASH = 4;
constexpr auto MAX_GUNSHELL = 24;
constexpr auto MAX_DRIPS = 32;
constexpr auto MAX_SHOCKWAVE = 16;
constexpr auto EXPLODE_HIT_EFFECT = 258;
constexpr auto EXPLODE_NORMAL = 256;
extern GUNFLASH_STRUCT Gunflashes[MAX_GUNFLASH];
extern FIRE_SPARKS FireSparks[MAX_SPARKS_FIRE];
@ -196,7 +200,6 @@ extern BLOOD_STRUCT Blood[MAX_SPARKS_BLOOD];
extern DRIP_STRUCT Drips[MAX_DRIPS];
extern SHOCKWAVE_STRUCT ShockWaves[MAX_SHOCKWAVE];
extern FIRE_LIST Fires[MAX_FIRE_LIST];
extern SMOKE_SPARKS SmokeSparks[MAX_SPARKS_SMOKE];
void TriggerBlood(int x, int y, int z, int unk, int num);
void TriggerExplosionBubble(int x, int y, int z, short roomNumber);
@ -204,9 +207,12 @@ int GetFreeFireSpark();
void TriggerGlobalStaticFlame();
void TriggerGlobalFireSmoke();
void TriggerGlobalFireFlame();
void keep_those_fires_burning();
void TriggerPilotFlame(int itemNum, int nodeIndex);
void ThrowFire(int itemNum, int meshIndex, Vector3Int offset, Vector3Int speed);
void ThrowPoison(int itemNum, int meshIndex, Vector3Int offset, Vector3Int speed, Vector3 color);
void UpdateFireProgress();
void ClearFires();
void AddFire(int x, int y, int z, char size, short roomNum, short on);
void AddFire(int x, int y, int z, short roomNum, float size, short fade);
void UpdateFireSparks();
int GetFreeSmokeSpark();
void UpdateSmoke();
@ -227,14 +233,9 @@ void UpdateBubbles();
int GetFreeDrip();
void UpdateDrips();
void TriggerLaraDrips(ItemInfo* item);
constexpr auto EXPLODE_HIT_EFFECT = 258;
constexpr auto EXPLODE_NORMAL = 256;
int ExplodingDeath(short itemNumber, int meshBits, short flags); // EXPLODE_ flags
int ExplodingDeath(short itemNumber, unsigned int meshBits, short flags); // EXPLODE_ flags
int GetFreeShockwave();
void TriggerShockwave(PHD_3DPOS* pos, short innerRad, short outerRad, int speed, char r, char g, char b, char life, short angle, short flags);
void TriggerShockwaveHitEffect(int x, int y, int z, int color, short rot, int vel);
void TriggerShockwaveHitEffect(int x, int y, int z, byte r, byte g, byte b, short rot, int vel);
void UpdateShockwaves();
void TriggerSmallSplash(int x, int y, int z, int number);
void SetFadeClip(short height, int velocity);

View file

@ -146,254 +146,254 @@ InventoryObject inventry_objects_list[INVENTORY_TABLE_SIZE] =
{
// Weapons
{ID_PISTOLS_ITEM, 6, 0.5f, ANGLE(90), ANGLE(243.69873046875f), ANGLE(276.1328125), OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_PISTOLS, STRING_PISTOLS, NO_MESH_BITS, INV_ROT_Y},
{ID_PISTOLS_AMMO_ITEM, 4, 0.5f, 0, ANGLE(90), 0, OPT_USE, STRING_PISTOLS_AMMO, NO_MESH_BITS, INV_ROT_Y},
{ID_UZI_ITEM, -4, 0.5f, ANGLE(-90) , ANGLE(135), ANGLE(90), OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_UZI, STRING_UZI , NO_MESH_BITS, INV_ROT_Y},
{ID_UZI_AMMO_ITEM, 5, 0.5f, 0, 5384, 0, OPT_USE, STRING_UZI_AMMO, NO_MESH_BITS, INV_ROT_Y},
{ID_PISTOLS_ITEM, 6, 0.5f, ANGLE(90), ANGLE(243.69873046875f), ANGLE(276.1328125), OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_PISTOLS, STRING_PISTOLS, NO_JOINT_BITS, INV_ROT_Y},
{ID_PISTOLS_AMMO_ITEM, 4, 0.5f, 0, ANGLE(90), 0, OPT_USE, STRING_PISTOLS_AMMO, NO_JOINT_BITS, INV_ROT_Y},
{ID_UZI_ITEM, -4, 0.5f, ANGLE(-90) , ANGLE(135), ANGLE(90), OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_UZI, STRING_UZI , NO_JOINT_BITS, INV_ROT_Y},
{ID_UZI_AMMO_ITEM, 5, 0.5f, 0, 5384, 0, OPT_USE, STRING_UZI_AMMO, NO_JOINT_BITS, INV_ROT_Y},
{ID_SHOTGUN_ITEM, -6, 0.8f, ANGLE(-20), ANGLE(270), ANGLE(45), OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_SHOTGUN, STRING_SHOTGUN, 1, INV_ROT_Y},
{ID_SHOTGUN_AMMO1_ITEM, 0, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_SHOTGUN_AMMO1, NO_MESH_BITS, INV_ROT_Y},
{ID_SHOTGUN_AMMO2_ITEM, 0, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_SHOTGUN_AMMO2, NO_MESH_BITS, INV_ROT_Y},
{ID_SHOTGUN_AMMO1_ITEM, 0, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_SHOTGUN_AMMO1, NO_JOINT_BITS, INV_ROT_Y},
{ID_SHOTGUN_AMMO2_ITEM, 0, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_SHOTGUN_AMMO2, NO_JOINT_BITS, INV_ROT_Y},
{ID_REVOLVER_ITEM, 0, 0.5f, ANGLE(-90), ANGLE(60), ANGLE(85), OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_REVOLVER, STRING_REVOLVER , 1, INV_ROT_Y},
{ID_REVOLVER_AMMO_ITEM, 0, 0.5f, ANGLE(90), ANGLE(-16), 0, OPT_USE, STRING_REVOLVER_AMMO, NO_MESH_BITS, INV_ROT_Y},
{ID_REVOLVER_AMMO_ITEM, 0, 0.5f, ANGLE(90), ANGLE(-16), 0, OPT_USE, STRING_REVOLVER_AMMO, NO_JOINT_BITS, INV_ROT_Y},
{ID_REVOLVER_ITEM, 0, 0.5f, ANGLE(90), ANGLE(60), ANGLE(85), OPT_EQUIP | OPT_SEPERATABLE | OPT_CHOOSEAMMO_REVOLVER, STRING_REVOLVER_LASER, 3, INV_ROT_Y},
{ID_CROSSBOW_ITEM, 0, 0.5f, ANGLE(-90), ANGLE(33), 0, OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_CROSSBOW, STRING_CROSSBOW, 1, INV_ROT_Y},
{ID_CROSSBOW_ITEM, 0, 0.5f, ANGLE(-90), ANGLE(33), 0, OPT_EQUIP | OPT_SEPERATABLE | OPT_CHOOSEAMMO_CROSSBOW, STRING_CROSSBOW_LASER, 3, INV_ROT_Y},
{ID_CROSSBOW_AMMO1_ITEM, 0, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_CROSSBOW_AMMO1, NO_MESH_BITS, INV_ROT_Y},
{ID_CROSSBOW_AMMO2_ITEM, 0, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_CROSSBOW_AMMO1, NO_MESH_BITS, INV_ROT_Y},
{ID_CROSSBOW_AMMO3_ITEM, 0, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_CROSSBOW_AMMO1, NO_MESH_BITS, INV_ROT_Y},
{ID_CROSSBOW_AMMO1_ITEM, 0, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_CROSSBOW_AMMO1, NO_JOINT_BITS, INV_ROT_Y},
{ID_CROSSBOW_AMMO2_ITEM, 0, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_CROSSBOW_AMMO1, NO_JOINT_BITS, INV_ROT_Y},
{ID_CROSSBOW_AMMO3_ITEM, 0, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_CROSSBOW_AMMO1, NO_JOINT_BITS, INV_ROT_Y},
{ID_HK_ITEM, 0, 0.5f, ANGLE(0), ANGLE(280), 0, OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_HK, STRING_HK, 1, INV_ROT_Y},
{ID_HK_ITEM, 0, 0.5f, ANGLE(-45), ANGLE(280), 0, OPT_EQUIP | OPT_SEPERATABLE | OPT_CHOOSEAMMO_HK, STRING_HK_SILENCED, NO_MESH_BITS, INV_ROT_Y},
{ID_HK_ITEM, 0, 0.5f, ANGLE(-45), ANGLE(280), 0, OPT_EQUIP | OPT_SEPERATABLE | OPT_CHOOSEAMMO_HK, STRING_HK_SILENCED, NO_JOINT_BITS, INV_ROT_Y},
{ID_HK_AMMO_ITEM, 3, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_HK_AMMO, 2},
{ID_GRENADE_GUN_ITEM, 0, 0.5f, ANGLE(90), 0, ANGLE(65), OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_GRENADEGUN, STRING_GRENADE_LAUNCHER, NO_MESH_BITS, INV_ROT_Y},
{ID_GRENADE_AMMO1_ITEM, 3, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_GRENADE_AMMO1, NO_MESH_BITS, INV_ROT_Y},
{ID_GRENADE_AMMO2_ITEM, 3, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_GRENADE_AMMO2, NO_MESH_BITS, INV_ROT_Y},
{ID_GRENADE_AMMO3_ITEM, 3, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_GRENADE_AMMO3, NO_MESH_BITS, INV_ROT_Y},
{ID_HARPOON_ITEM, 0, 0.5f, 0, ANGLE(-65), ANGLE(-20), OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_HARPOON, STRING_HARPOON_GUN, NO_MESH_BITS, INV_ROT_Y},
{ID_HARPOON_AMMO_ITEM, 3, 0.5f, 0, ANGLE(15), 0, OPT_USE, STRING_HARPOON_AMMO, NO_MESH_BITS, INV_ROT_Y},
{ID_ROCKET_LAUNCHER_ITEM, 0, 0.5f, ANGLE(180), ANGLE(80), 0, OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_ROCKET, STRING_ROCKET_LAUNCHER, NO_MESH_BITS, INV_ROT_Y},
{ID_ROCKET_LAUNCHER_AMMO_ITEM, 3, 0.5f, ANGLE(90), 0, ANGLE(15), OPT_USE, STRING_ROCKET_AMMO, NO_MESH_BITS, INV_ROT_Y},
{ID_GRENADE_GUN_ITEM, 0, 0.5f, ANGLE(90), 0, ANGLE(65), OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_GRENADEGUN, STRING_GRENADE_LAUNCHER, NO_JOINT_BITS, INV_ROT_Y},
{ID_GRENADE_AMMO1_ITEM, 3, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_GRENADE_AMMO1, NO_JOINT_BITS, INV_ROT_Y},
{ID_GRENADE_AMMO2_ITEM, 3, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_GRENADE_AMMO2, NO_JOINT_BITS, INV_ROT_Y},
{ID_GRENADE_AMMO3_ITEM, 3, 0.5f, ANGLE(90), 0, 0, OPT_USE, STRING_GRENADE_AMMO3, NO_JOINT_BITS, INV_ROT_Y},
{ID_HARPOON_ITEM, 0, 0.5f, 0, ANGLE(-65), ANGLE(-20), OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_HARPOON, STRING_HARPOON_GUN, NO_JOINT_BITS, INV_ROT_Y},
{ID_HARPOON_AMMO_ITEM, 3, 0.5f, 0, ANGLE(15), 0, OPT_USE, STRING_HARPOON_AMMO, NO_JOINT_BITS, INV_ROT_Y},
{ID_ROCKET_LAUNCHER_ITEM, 0, 0.5f, ANGLE(180), ANGLE(80), 0, OPT_EQUIP | OPT_COMBINABLE | OPT_CHOOSEAMMO_ROCKET, STRING_ROCKET_LAUNCHER, NO_JOINT_BITS, INV_ROT_Y},
{ID_ROCKET_LAUNCHER_AMMO_ITEM, 3, 0.5f, ANGLE(90), 0, ANGLE(15), OPT_USE, STRING_ROCKET_AMMO, NO_JOINT_BITS, INV_ROT_Y},
// Misc
{ID_LASERSIGHT_ITEM, 2, 0.5f, ANGLE(90), ANGLE(10), 0, OPT_USE | OPT_COMBINABLE, STRING_LASERSIGHT, NO_MESH_BITS, INV_ROT_Y},
{ID_SILENCER_ITEM, 1, 0.5f, 0, ANGLE(10), 0, OPT_USE | OPT_COMBINABLE, STRING_SILENCER, NO_MESH_BITS, INV_ROT_Y},
{ID_BIGMEDI_ITEM, 2, 0.7f, ANGLE(180), 0, 0, OPT_USE, STRING_LARGE_MEDIPACK, NO_MESH_BITS, INV_ROT_Y},
{ID_SMALLMEDI_ITEM, 0, 0.7f, ANGLE(180), ANGLE(112), 0, OPT_USE, STRING_SMALL_MEDIPACK, NO_MESH_BITS, INV_ROT_Y},
{ID_BINOCULARS_ITEM, -1, 0.5f, ANGLE(180), ANGLE(10), 0, OPT_USE, STRING_BINOCULARS, NO_MESH_BITS, INV_ROT_Y},
{ID_FLARE_INV_ITEM, 52, 0.8f, ANGLE(0), 0, 0, OPT_USE, STRING_FLARES, NO_MESH_BITS, INV_ROT_Y},
{ID_TIMEX_ITEM, 2, 0.4f, 0, 0, 0, OPT_STATS, STRING_TIMEX, NO_MESH_BITS, INV_ROT_Y},
{ID_PC_LOAD_INV_ITEM, 52, 0.3f, ANGLE(180), 0, 0, OPT_LOAD, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PC_LOAD_SAVE_ITEM, 52, 0.3f, ANGLE(180), 0, 0, OPT_SAVE, STRING_SAVE_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_BURNING_TORCH_ITEM, 14, 0.5f, 0, ANGLE(90), 0, OPT_USE, STRING_TORCH, NO_MESH_BITS, INV_ROT_Y},
{ID_CROWBAR_ITEM, 4, 0.5f, 0, ANGLE(90), 0, OPT_USE, STRING_CROWBAR, NO_MESH_BITS, INV_ROT_Y},
{ID_DIARY_ITEM, 0, 0.3f, ANGLE(180), 0, 0, OPT_DIARY, STRING_DIARY, NO_MESH_BITS, INV_ROT_Y},
{ID_COMPASS_ITEM, -14, 0.5f, 0, 0, 0, 0, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_CLOCKWORK_BEETLE, 14, 0.5f, 0, 0, 0, OPT_USE, STRING_CLOCKWORK_BEETLE, NO_MESH_BITS, INV_ROT_Y},
{ID_CLOCKWORK_BEETLE_COMBO1, 18, 0.5f, 0, 0, 0, OPT_COMBINABLE, STRING_CLOCKWORK_BEETLE_COMBO1, NO_MESH_BITS, INV_ROT_Y},
{ID_CLOCKWORK_BEETLE_COMBO2, 14, 0.5f, 0, 0, 0, OPT_COMBINABLE, STRING_CLOCKWORK_BEETLE_COMBO2, NO_MESH_BITS, INV_ROT_Y},
{ID_WATERSKIN1_EMPTY, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_SMALL_EMPTY, NO_MESH_BITS, INV_ROT_Y},
{ID_WATERSKIN1_1, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_SMALL_1, NO_MESH_BITS, INV_ROT_Y},
{ID_WATERSKIN1_2, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_SMALL_2, NO_MESH_BITS, INV_ROT_Y},
{ID_WATERSKIN1_3, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_SMALL_3, NO_MESH_BITS, INV_ROT_Y},
{ID_WATERSKIN2_EMPTY, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_LARGE_EMPTY, NO_MESH_BITS, INV_ROT_Y},
{ID_WATERSKIN2_1, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_LARGE_1, NO_MESH_BITS, INV_ROT_Y},
{ID_WATERSKIN2_2, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_LARGE_2, NO_MESH_BITS, INV_ROT_Y},
{ID_WATERSKIN2_3, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_LARGE_3, NO_MESH_BITS, INV_ROT_Y},
{ID_WATERSKIN2_4, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_LARGE_4, NO_MESH_BITS, INV_ROT_Y},
{ID_WATERSKIN2_5, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_LARGE_5, NO_MESH_BITS, INV_ROT_Y},
{ID_LASERSIGHT_ITEM, 2, 0.5f, ANGLE(90), ANGLE(10), 0, OPT_USE | OPT_COMBINABLE, STRING_LASERSIGHT, NO_JOINT_BITS, INV_ROT_Y},
{ID_SILENCER_ITEM, 1, 0.5f, 0, ANGLE(10), 0, OPT_USE | OPT_COMBINABLE, STRING_SILENCER, NO_JOINT_BITS, INV_ROT_Y},
{ID_BIGMEDI_ITEM, 2, 0.7f, ANGLE(180), 0, 0, OPT_USE, STRING_LARGE_MEDIPACK, NO_JOINT_BITS, INV_ROT_Y},
{ID_SMALLMEDI_ITEM, 0, 0.7f, ANGLE(180), ANGLE(112), 0, OPT_USE, STRING_SMALL_MEDIPACK, NO_JOINT_BITS, INV_ROT_Y},
{ID_BINOCULARS_ITEM, -1, 0.5f, ANGLE(180), ANGLE(10), 0, OPT_USE, STRING_BINOCULARS, NO_JOINT_BITS, INV_ROT_Y},
{ID_FLARE_INV_ITEM, 52, 0.8f, ANGLE(0), 0, 0, OPT_USE, STRING_FLARES, NO_JOINT_BITS, INV_ROT_Y},
{ID_TIMEX_ITEM, 2, 0.4f, 0, 0, 0, OPT_STATS, STRING_TIMEX, NO_JOINT_BITS, INV_ROT_Y},
{ID_PC_LOAD_INV_ITEM, 52, 0.3f, ANGLE(180), 0, 0, OPT_LOAD, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PC_LOAD_SAVE_ITEM, 52, 0.3f, ANGLE(180), 0, 0, OPT_SAVE, STRING_SAVE_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_BURNING_TORCH_ITEM, 14, 0.5f, 0, ANGLE(90), 0, OPT_USE, STRING_TORCH, NO_JOINT_BITS, INV_ROT_Y},
{ID_CROWBAR_ITEM, 4, 0.5f, 0, ANGLE(90), 0, OPT_USE, STRING_CROWBAR, NO_JOINT_BITS, INV_ROT_Y},
{ID_DIARY_ITEM, 0, 0.3f, ANGLE(180), 0, 0, OPT_DIARY, STRING_DIARY, NO_JOINT_BITS, INV_ROT_Y},
{ID_COMPASS_ITEM, -14, 0.5f, 0, 0, 0, 0, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_CLOCKWORK_BEETLE, 14, 0.5f, 0, 0, 0, OPT_USE, STRING_CLOCKWORK_BEETLE, NO_JOINT_BITS, INV_ROT_Y},
{ID_CLOCKWORK_BEETLE_COMBO1, 18, 0.5f, 0, 0, 0, OPT_COMBINABLE, STRING_CLOCKWORK_BEETLE_COMBO1, NO_JOINT_BITS, INV_ROT_Y},
{ID_CLOCKWORK_BEETLE_COMBO2, 14, 0.5f, 0, 0, 0, OPT_COMBINABLE, STRING_CLOCKWORK_BEETLE_COMBO2, NO_JOINT_BITS, INV_ROT_Y},
{ID_WATERSKIN1_EMPTY, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_SMALL_EMPTY, NO_JOINT_BITS, INV_ROT_Y},
{ID_WATERSKIN1_1, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_SMALL_1, NO_JOINT_BITS, INV_ROT_Y},
{ID_WATERSKIN1_2, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_SMALL_2, NO_JOINT_BITS, INV_ROT_Y},
{ID_WATERSKIN1_3, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_SMALL_3, NO_JOINT_BITS, INV_ROT_Y},
{ID_WATERSKIN2_EMPTY, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_LARGE_EMPTY, NO_JOINT_BITS, INV_ROT_Y},
{ID_WATERSKIN2_1, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_LARGE_1, NO_JOINT_BITS, INV_ROT_Y},
{ID_WATERSKIN2_2, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_LARGE_2, NO_JOINT_BITS, INV_ROT_Y},
{ID_WATERSKIN2_3, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_LARGE_3, NO_JOINT_BITS, INV_ROT_Y},
{ID_WATERSKIN2_4, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_LARGE_4, NO_JOINT_BITS, INV_ROT_Y},
{ID_WATERSKIN2_5, 2, 0.5f, 0, ANGLE(285), 0, OPT_USE | OPT_COMBINABLE, STRING_WATERSKIN_LARGE_5, NO_JOINT_BITS, INV_ROT_Y},
{ID_OPEN_DIARY_ITEM, 0, 0.9f, ANGLE(90), 0, 0, 0, 0, 0, 0},
// Puzzles
{ID_PUZZLE_ITEM1, 14, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM2, 14, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM3, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM4, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM5, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM6, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM7, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM8, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM9, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM10, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM11, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM12, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM13, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM14, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM15, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM16, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM1, 14, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM2, 14, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM3, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM4, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM5, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM6, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM7, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM8, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM9, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM10, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM11, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM12, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM13, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM14, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM15, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM16, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
// Puzzle combos
{ID_PUZZLE_ITEM1_COMBO1, 18, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM1_COMBO2, 18, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM2_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM2_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM3_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM3_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM4_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM4_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM5_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM5_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM6_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM6_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM7_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM7_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM8_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM8_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM9_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM9_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM10_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM10_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM11_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM11_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM12_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM12_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM13_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM13_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM14_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM14_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM15_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM15_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM16_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM16_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM1_COMBO1, 18, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM1_COMBO2, 18, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM2_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM2_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM3_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM3_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM4_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM4_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM5_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM5_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM6_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM6_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM7_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM7_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM8_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM8_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM9_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM9_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM10_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM10_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM11_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM11_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM12_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM12_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM13_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM13_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM14_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM14_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM15_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM15_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM16_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PUZZLE_ITEM16_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
// Keys
{ID_KEY_ITEM1, 14, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM2, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM3, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM4, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM5, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM6, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM7, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM8, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM9, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM10, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM11, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM12, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM13, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM14, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM15, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM16, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM1, 14, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM2, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM3, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM4, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM5, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM6, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM7, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM8, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM9, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM10, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM11, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM12, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM13, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM14, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM15, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM16, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
// Key combos
{ID_KEY_ITEM1_COMBO1, 18, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM1_COMBO2, 18, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM2_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM2_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM3_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM3_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM4_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM4_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM5_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM5_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM6_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM6_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM7_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM7_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM8_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM8_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM9_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM9_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM10_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM10_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM11_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM11_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM12_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM12_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM13_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM13_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM14_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM14_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM15_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM15_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM16_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM16_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_KEY_ITEM1_COMBO1, 18, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM1_COMBO2, 18, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM2_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM2_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM3_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM3_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM4_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM4_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM5_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM5_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM6_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM6_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM7_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM7_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM8_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM8_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM9_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM9_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM10_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM10_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM11_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM11_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM12_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM12_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM13_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM13_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM14_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM14_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM15_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM15_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM16_COMBO1, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_KEY_ITEM16_COMBO2, 8, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
// Pickups
{ID_PICKUP_ITEM1, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM2, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM3, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM4, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM5, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM6, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM7, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM8, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM9, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM10, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM11, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM12, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM13, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM14, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM15, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM16, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM1, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM2, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM3, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM4, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM5, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM6, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM7, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM8, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM9, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM10, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM11, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM12, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM13, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM14, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM15, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM16, 8, 0.5f, 0, 0, 0, OPT_USE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
// Pickup combos
{ID_PICKUP_ITEM1_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM1_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM2_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM2_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM3_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM3_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM4_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM4_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM5_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM5_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM6_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM6_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM7_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM7_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM8_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM8_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM9_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM9_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM10_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM10_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM11_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM11_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM12_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM12_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM13_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM13_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM14_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM14_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM15_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM15_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM16_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM16_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM1_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM1_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM2_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM2_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM3_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM3_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM4_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM4_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM5_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM5_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM6_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM6_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM7_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM7_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM8_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM8_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM9_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM9_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM10_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM10_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM11_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM11_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM12_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM12_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM13_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM13_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM14_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM14_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM15_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM15_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM16_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_PICKUP_ITEM16_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
// Examines
{ID_EXAMINE1, 4, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE2, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE3, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE4, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE5, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE6, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE7, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE8, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE1, 4, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE2, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE3, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE4, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE5, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE6, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE7, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE8, 14, 0.5f, 0, 0, 0, OPT_EXAMINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
// Examines combos
{ID_EXAMINE1_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE1_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE2_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE2_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE3_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE3_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE4_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE4_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE5_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE5_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE6_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE6_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE7_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE7_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE8_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE8_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_MESH_BITS, INV_ROT_Y},
{ID_EXAMINE1_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE1_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE2_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE2_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE3_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE3_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE4_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE4_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE5_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE5_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE6_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE6_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE7_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE7_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE8_COMBO1, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
{ID_EXAMINE8_COMBO2, 14, 0.5f, 0, 0, 0, OPT_USE | OPT_COMBINABLE, STRING_LOAD_GAME, NO_JOINT_BITS, INV_ROT_Y},
};
SettingsData GuiController::GetCurrentSettings()
@ -626,15 +626,27 @@ InventoryResult GuiController::TitleOptions()
DoDebouncedInput();
if (menu_to_display == Menu::Title || menu_to_display == Menu::SelectLevel ||
menu_to_display == Menu::LoadGame || menu_to_display == Menu::Options)
if (menu_to_display == Menu::LoadGame)
{
DoLoad();
if (invMode == InventoryMode::InGame)
{
menu_to_display = Menu::Title;
selected_option = selected_option_bak;
}
}
else if (menu_to_display == Menu::Title ||
menu_to_display == Menu::SelectLevel ||
menu_to_display == Menu::Options)
{
if (goUp)
{
if (selected_option <= 0)
selected_option += option_count;
if (menu_to_display == Menu::LoadGame)
selected_save_slot = (selected_save_slot <= 0) ? option_count : selected_option - 1;
else
selected_option--;
selected_option = (selected_option <= 0) ? option_count : selected_option - 1;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
}
@ -705,13 +717,12 @@ InventoryResult GuiController::TitleOptions()
}
else if (menu_to_display == Menu::LoadGame)
{
if (!SavegameInfos[selected_option].Present)
if (!SavegameInfos[selected_save_slot].Present)
SayNo();
else
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
g_GameFlow->SelectedSaveGame = selected_option;
selected_option = 0;
g_GameFlow->SelectedSaveGame = selected_save_slot;
ret = InventoryResult::LoadGame;
}
}
@ -1250,8 +1261,10 @@ InventoryResult GuiController::DoPauseMenu()
if (goSelect)
{
if (menu_to_display == Menu::Pause)
switch (menu_to_display)
{
case Menu::Pause:
switch (selected_option)
{
case 0:
@ -1268,9 +1281,10 @@ InventoryResult GuiController::DoPauseMenu()
invMode = InventoryMode::None;
return InventoryResult::ExitToTitle;
}
}
else if (menu_to_display == Menu::Options)
{
break;
case Menu::Options:
switch (selected_option)
{
case 0:
@ -1289,6 +1303,12 @@ InventoryResult GuiController::DoPauseMenu()
menu_to_display = Menu::Sound;
break;
}
break;
case Menu::Statistics:
menu_to_display = Menu::Pause;
SoundEffect(SFX_TR4_MENU_SELECT, nullptr, SoundEnvironment::Always);
break;
}
}
@ -1996,7 +2016,7 @@ void GuiController::UseCurrentItem()
OldBinocular = BinocularRange;
Lara.Inventory.OldBusy = false;
BinocularRange = 0;
LaraItem->MeshBits = -1;
LaraItem->MeshBits = ALL_JOINT_BITS;
invobject = rings[(int)RingTypes::Inventory]->current_object_list[rings[(int)RingTypes::Inventory]->curobjinlist].invitem;
gmeobject = inventry_objects_list[invobject].object_number;
@ -2250,7 +2270,7 @@ void GuiController::UseCurrentItem()
}
}
void GuiController::HandleInventoryMenu()
void GuiController::DoInventory()
{
int n;
unsigned __int64 opts;
@ -2260,7 +2280,7 @@ void GuiController::HandleInventoryMenu()
if (rings[(int)RingTypes::Ammo]->ringactive)
{
g_Renderer.DrawString(phd_centerx, phd_centery, g_GameFlow->GetString(optmessages[5]), PRINTSTRING_COLOR_WHITE, PRINTSTRING_BLINK | PRINTSTRING_CENTER);
g_Renderer.DrawString(phd_centerx, phd_centery, g_GameFlow->GetString(optmessages[5]), PRINTSTRING_COLOR_WHITE, PRINTSTRING_BLINK | PRINTSTRING_CENTER | PRINTSTRING_OUTLINE);
if (rings[(int)RingTypes::Inventory]->objlistmovement)
return;
@ -2452,12 +2472,12 @@ void GuiController::HandleInventoryMenu()
{
if (i == current_selected_option)
{
g_Renderer.DrawString(phd_centerx, ypos, current_options[i].text, PRINTSTRING_COLOR_WHITE, PRINTSTRING_BLINK | PRINTSTRING_CENTER);
g_Renderer.DrawString(phd_centerx, ypos, current_options[i].text, PRINTSTRING_COLOR_WHITE, PRINTSTRING_BLINK | PRINTSTRING_CENTER | PRINTSTRING_OUTLINE);
ypos += font_height;
}
else
{
g_Renderer.DrawString(phd_centerx, ypos, current_options[i].text, PRINTSTRING_COLOR_WHITE, PRINTSTRING_CENTER);
g_Renderer.DrawString(phd_centerx, ypos, current_options[i].text, PRINTSTRING_COLOR_WHITE, PRINTSTRING_CENTER | PRINTSTRING_OUTLINE);
ypos += font_height;
}
}
@ -2710,7 +2730,7 @@ void GuiController::DrawAmmoSelector()
yrot = ammo_object_list[n].yrot;
zrot = ammo_object_list[n].zrot;
x = phd_centerx - 300 + xpos;
y = 430;
y = 480;
short obj = ConvertInventoryItemToObject(ammo_object_list[n].invitem);
float scaler = inventry_objects_list[ammo_object_list[n].invitem].scale1;
@ -2722,7 +2742,7 @@ void GuiController::DrawAmmoSelector()
sprintf(&invTextBuffer[0], "%d x %s", ammo_object_list[n].amount, g_GameFlow->GetString(inventry_objects_list[ammo_object_list[n].invitem].objname));
if (ammo_selector_fade_val)
g_Renderer.DrawString(phd_centerx, 380, &invTextBuffer[0], PRINTSTRING_COLOR_YELLOW, PRINTSTRING_CENTER);
g_Renderer.DrawString(phd_centerx, 380, &invTextBuffer[0], PRINTSTRING_COLOR_YELLOW, PRINTSTRING_CENTER | PRINTSTRING_OUTLINE);
if (n == *current_ammo_type)
@ -3028,7 +3048,7 @@ void GuiController::DrawCurrentObjectList(int ringnum)
else
objmeup = (int)((phd_winymax + 1) * 0.0625 * 3.0 + phd_centery);
g_Renderer.DrawString(phd_centerx, ringnum == (int)RingTypes::Inventory ? 230 : 300, textbufme, PRINTSTRING_COLOR_YELLOW, PRINTSTRING_CENTER);
g_Renderer.DrawString(phd_centerx, ringnum == (int)RingTypes::Inventory ? 230 : 300, textbufme, PRINTSTRING_COLOR_YELLOW, PRINTSTRING_CENTER | PRINTSTRING_OUTLINE);
}
if (!i && !rings[ringnum]->objlistmovement)
@ -3083,7 +3103,7 @@ void GuiController::DrawCurrentObjectList(int ringnum)
int x, y, y2;
x = 400 + xoff + i * OBJLIST_SPACING;
y = 150;
y2 = 430;//combine
y2 = 480;//combine
short obj = ConvertInventoryItemToObject(rings[ringnum]->current_object_list[n].invitem);
float scaler = inventry_objects_list[rings[ringnum]->current_object_list[n].invitem].scale1;
g_Renderer.DrawObjectOn2DPosition(x, ringnum == (int)RingTypes::Inventory ? y : y2, obj, xrot, yrot, zrot, scaler);
@ -3204,25 +3224,36 @@ int GuiController::CallInventory(bool reset_mode)
return return_value;
DoDebouncedInput();
if (invMode == InventoryMode::Statistics)
DoStatisticsMode();
if (invMode == InventoryMode::Examine)
DoExamineMode();
if (invMode == InventoryMode::Diary)
DoDiary();
if (invMode == InventoryMode::Load)
return_value = DoLoad();
if (invMode == InventoryMode::Save)
DoSave();
DrawInventory();
DrawCompass();
switch (invMode)
{
case InventoryMode::InGame:
DoInventory();
break;
case InventoryMode::Statistics:
DoStatisticsMode();
break;
case InventoryMode::Examine:
DoExamineMode();
break;
case InventoryMode::Diary:
DoDiary();
break;
case InventoryMode::Load:
return_value = DoLoad();
break;
case InventoryMode::Save:
DoSave();
break;
}
if (useItem && !TrInput)
val = 1;
@ -3310,7 +3341,7 @@ void GuiController::DoDiary()
short GuiController::GetLoadSaveSelection()
{
return selected_slot;
return selected_save_slot;
}
int GuiController::DoLoad()
@ -3321,30 +3352,30 @@ int GuiController::DoLoad()
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
if (selected_slot == SAVEGAME_MAX - 1)
selected_slot -= SAVEGAME_MAX - 1; //go back up
if (selected_save_slot == SAVEGAME_MAX - 1)
selected_save_slot -= SAVEGAME_MAX - 1; //go back up
else
selected_slot++;
selected_save_slot++;
}
if (goUp)
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
if (selected_slot== 0)
selected_slot += SAVEGAME_MAX - 1; //go back down
if (selected_save_slot== 0)
selected_save_slot += SAVEGAME_MAX - 1; //go back down
else
selected_slot--;
selected_save_slot--;
}
if (goSelect)
{
if (!SavegameInfos[selected_slot].Present)
if (!SavegameInfos[selected_save_slot].Present)
SayNo();
else
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
g_GameFlow->SelectedSaveGame = selected_slot;
g_GameFlow->SelectedSaveGame = selected_save_slot;
ExitInvLoop = 1;
return 1;
}
@ -3368,26 +3399,26 @@ void GuiController::DoSave()
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
if (selected_slot == SAVEGAME_MAX - 1)
selected_slot -= SAVEGAME_MAX - 1; //go back up
if (selected_save_slot == SAVEGAME_MAX - 1)
selected_save_slot -= SAVEGAME_MAX - 1; //go back up
else
selected_slot++;
selected_save_slot++;
}
if (goUp)
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
if (selected_slot == 0)
selected_slot += SAVEGAME_MAX - 1; //go back down
if (selected_save_slot == 0)
selected_save_slot += SAVEGAME_MAX - 1; //go back down
else
selected_slot--;
selected_save_slot--;
}
if (goSelect)
{
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
SaveGame::Save(selected_slot);
SaveGame::Save(selected_save_slot);
ExitInvLoop = 1; //exit inv if the user has saved
}

View file

@ -392,7 +392,7 @@ struct InventoryObject
short zrot;
unsigned __int64 opts;
const char* objname;
unsigned long meshbits;
unsigned int meshbits;
short rot_flags;
};
@ -402,7 +402,6 @@ public:
int CallInventory(bool reset_mode);
InventoryResult TitleOptions();
InventoryResult DoPauseMenu();
void HandleInventoryMenu();
void DrawInventory();
void DrawCurrentObjectList(int ringnum);
int IsObjectInInventory(short object_number);
@ -460,9 +459,11 @@ private:
void DoDiary();
int DoLoad();
void DoSave();
void DoInventory();
void ConstructCombineObjectList();
/*vars*/
// Input
bool goUp, goDown, goRight, goLeft, goSelect, goDeselect;
bool dbUp, dbDown, dbRight, dbLeft, dbSelect, dbDeselect;
@ -536,14 +537,13 @@ private:
char StashedCurrentHarpoonAmmoType;
char StashedCurrentRocketAmmoType;
// GUI vars
Menu menu_to_display = Menu::Title;
short selected_option;
int selected_option;
int option_count;
int selected_save_slot;
SettingsData CurrentSettings;
// Load / save
short selected_slot;
};
/*inventory*/

View file

@ -13,6 +13,7 @@
#include "Specific/level.h"
using namespace TEN::Renderer;
short PickupX;
short PickupY;
short CurrentPickup;
@ -31,6 +32,18 @@ extern RendererHUDBar* g_AirBar;
bool EnableSmoothHealthBar = true;
void DrawHUD(ItemInfo* item)
{
if (CurrentLevel == 0 || CinematicBarsHeight > 0)
return;
int flash = FlashIt();
UpdateSprintBar(LaraItem);
UpdateHealthBar(LaraItem, flash);
UpdateAirBar(LaraItem, flash);
DrawAllPickups();
}
void DrawHealthBarOverlay(ItemInfo* item, int value)
{
auto* lara = GetLaraInfo(item);
@ -43,7 +56,7 @@ void DrawHealthBarOverlay(ItemInfo* item, int value)
else
color2 = 0xA00000;
g_Renderer.DrawBar(value, ::g_HealthBar, ID_HEALTH_BAR_TEXTURE, GlobalCounter, Lara.PoisonPotency);
g_Renderer.DrawBar(value, g_HealthBar, ID_HEALTH_BAR_TEXTURE, GlobalCounter, Lara.PoisonPotency);
}
}
@ -52,7 +65,7 @@ void DrawHealthBar(ItemInfo* item, float value)
auto* lara = GetLaraInfo(item);
if (CurrentLevel)
g_Renderer.DrawBar(value, ::g_HealthBar, ID_HEALTH_BAR_TEXTURE, GlobalCounter, Lara.PoisonPotency);
g_Renderer.DrawBar(value, g_HealthBar, ID_HEALTH_BAR_TEXTURE, GlobalCounter, Lara.PoisonPotency);
}
void UpdateHealthBar(ItemInfo* item, int flash)
@ -141,7 +154,7 @@ void UpdateHealthBar(ItemInfo* item, int flash)
void DrawAirBar(float value)
{
if (CurrentLevel)
g_Renderer.DrawBar(value, ::g_AirBar,ID_AIR_BAR_TEXTURE,0,0);
g_Renderer.DrawBar(value, g_AirBar,ID_AIR_BAR_TEXTURE,0,0);
}
void UpdateAirBar(ItemInfo* item, int flash)
@ -180,7 +193,7 @@ void UpdateAirBar(ItemInfo* item, int flash)
void DrawSprintBar(float value)
{
if (CurrentLevel)
g_Renderer.DrawBar(value, ::g_DashBar, ID_DASH_BAR_TEXTURE, 0, 0);
g_Renderer.DrawBar(value, g_DashBar, ID_DASH_BAR_TEXTURE, 0, 0);
}
void UpdateSprintBar(ItemInfo* item)
@ -236,7 +249,7 @@ void DrawAllPickups()
}
}
g_Renderer.drawPickup(Pickups[CurrentPickup].ObjectNumber);
g_Renderer.DrawPickup(Pickups[CurrentPickup].ObjectNumber);
}

View file

@ -11,6 +11,7 @@ struct DisplayPickup
short ObjectNumber;
};
void DrawHUD(ItemInfo* item);
void DrawHealthBarOverlay(ItemInfo* item, int value);
void DrawHealthBar(ItemInfo* item, float value);
void UpdateHealthBar(ItemInfo* item, int flash);

View file

@ -12,6 +12,98 @@
using namespace TEN::Floordata;
void ItemInfo::SetBits(JointBitType type, std::vector<int> jointIndices)
{
for (int i = 0; i < jointIndices.size(); i++)
{
unsigned int jointBit = (unsigned int)(1 << jointIndices[i]);
switch (type)
{
case JointBitType::Touch:
this->TouchBits |= jointBit;
break;
case JointBitType::Mesh:
this->MeshBits |= jointBit;
break;
case JointBitType::MeshSwap:
this->MeshSwapBits |= jointBit;
break;
}
}
}
void ItemInfo::SetBits(JointBitType type, int jointIndex)
{
return SetBits(type, std::vector { jointIndex });
}
void ItemInfo::ClearBits(JointBitType type, std::vector<int> jointIndices)
{
for (int i = 0; i < jointIndices.size(); i++)
{
unsigned int jointBit = (unsigned int)(1 << jointIndices[i]);
switch (type)
{
case JointBitType::Touch:
this->TouchBits &= ~jointBit;
break;
case JointBitType::Mesh:
this->MeshBits &= ~jointBit;
break;
case JointBitType::MeshSwap:
this->MeshSwapBits &= ~jointBit;
break;
}
}
}
void ItemInfo::ClearBits(JointBitType type, int jointIndex)
{
return ClearBits(type, std::vector { jointIndex });
}
bool ItemInfo::TestBits(JointBitType type, std::vector<int> jointIndices)
{
for (int i = 0; i < jointIndices.size(); i++)
{
unsigned int jointBit = (unsigned int)(1 << jointIndices[i]);
switch (type)
{
case JointBitType::Touch:
if ((TouchBits & jointBit) == jointBit)
return true;
break;
case JointBitType::Mesh:
if ((MeshBits & jointBit) == jointBit)
return true;
break;
case JointBitType::MeshSwap:
if ((MeshSwapBits & jointBit) == jointBit)
return true;
break;
}
}
return false;
}
bool ItemInfo::TestBits(JointBitType type, int jointIndex)
{
return TestBits(type, std::vector { jointIndex });
}
void ClearItem(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
@ -76,13 +168,10 @@ void KillItem(short const itemNumber)
if (Objects[item->ObjectNumber].floor != nullptr)
UpdateBridgeItem(itemNumber, true);
g_GameScriptEntities->NotifyKilled(item);
g_GameScriptEntities->TryRemoveColliding(itemNumber, true);
if (!item->LuaCallbackOnKilledName.empty())
{
g_GameScript->ExecuteFunction(item->LuaCallbackOnKilledName, itemNumber);
}
item->LuaName.clear();
item->LuaCallbackOnKilledName.clear();
@ -96,10 +185,7 @@ void KillItem(short const itemNumber)
NextItemFree = itemNumber;
}
else
{
item->Flags |= IFLAG_KILLED;
}
}
}
@ -383,11 +469,11 @@ void InitialiseItem(short itemNumber)
item->MeshBits = 1;
}
else
item->MeshBits = -1;
item->MeshBits = ALL_JOINT_BITS;
item->TouchBits = 0;
item->TouchBits = NO_JOINT_BITS;
item->AfterDeath = 0;
item->SwapMeshFlags = 0;
item->MeshSwapBits = NO_JOINT_BITS;
if (item->Flags & IFLAG_INVISIBLE)
{

View file

@ -16,10 +16,7 @@ enum GAME_OBJECT_ID : short;
#define GRAY555 RGB555(128, 128, 128)
#define BLACK555 RGB555( 0, 0, 0)
constexpr unsigned int NO_MESH_BITS = UINT_MAX;
constexpr auto NO_ITEM = -1;
constexpr auto ALL_MESHBITS = -1;
constexpr auto NOT_TARGETABLE = -16384;
constexpr auto NUM_ITEMS = 1024;
@ -52,6 +49,16 @@ enum ItemFlags
IFLAG_ACTIVATION_MASK = 0x3E00 // bits 9-13
};
constexpr unsigned int ALL_JOINT_BITS = UINT_MAX;
constexpr unsigned int NO_JOINT_BITS = 0;
enum class JointBitType
{
Touch,
Mesh,
MeshSwap
};
struct EntityAnimationData
{
int AnimNumber;
@ -94,13 +101,13 @@ struct ItemInfo
int Timer;
short Shade;
uint32_t TouchBits;
uint32_t MeshBits;
unsigned int TouchBits;
unsigned int MeshBits;
unsigned int MeshSwapBits;
uint16_t Flags; // ItemFlags enum
unsigned short Flags; // ItemFlags enum
short ItemFlags[8];
short TriggerFlags;
uint32_t SwapMeshFlags;
// TODO: Move to CreatureInfo?
uint8_t AIBits; // AIObjectType enum.
@ -113,6 +120,13 @@ struct ItemInfo
std::string LuaCallbackOnHitName;
std::string LuaCallbackOnCollidedWithObjectName;
std::string LuaCallbackOnCollidedWithRoomName;
void SetBits(JointBitType type, std::vector<int> jointIndices);
void SetBits(JointBitType type, int jointIndex);
void ClearBits(JointBitType type, std::vector<int> jointIndices);
void ClearBits(JointBitType type, int jointIndex);
bool TestBits(JointBitType type, std::vector<int> jointIndices);
bool TestBits(JointBitType type, int jointIndex);
};
void EffectNewRoom(short fxNumber, short roomNumber);

View file

@ -67,7 +67,7 @@ void ControlMissile(short fxNumber)
// fx->frameNumber = -GetRandomControl()/11000;
// fx->counter = 6;
// fx->objectNumber = RICOCHET1;
SoundEffect((fx->objectNumber == ID_SCUBA_HARPOON) ? SFX_TR4_LARA_RICOCHET : SFX_TR2_CIRCLE_BLADE_HIT, &fx->pos);
SoundEffect((fx->objectNumber == ID_SCUBA_HARPOON) ? SFX_TR4_WEAPON_RICOCHET : SFX_TR2_CIRCLE_BLADE_HIT, &fx->pos);
}
/*else if (fx->objectNumber == DRAGON_FIRE)
{

View file

@ -40,24 +40,25 @@ namespace TEN::Effects
p.size = GenerateFloat(256, 512);
}
void TriggerSpeedboatFoam(ItemInfo* boat)
void TriggerSpeedboatFoam(ItemInfo* boat, Vector3 offset)
{
for (float i = -0.5; i < 1; i += 1)
{
float size = GenerateFloat(96, 128);
float angle = TO_RAD(boat->Pose.Orientation.y);
float angleVariation = i*2*10 * RADIAN;
float y = float(boat->Pose.Position.y) - size / 2 + offset.y;
float x = std::sin(angle + angleVariation);
float z = std::cos(angle + angleVariation);
x = x * -700 + boat->Pose.Position.x;
z = z * -700 + boat->Pose.Position.z;
x = x * offset.z + z * offset.x + boat->Pose.Position.x;
z = z * offset.z + x * offset.x + boat->Pose.Position.z;
SimpleParticle& p = getFreeSimpleParticle();
p = {};
p.active = true;
p.life = GenerateFloat(5, 9);
p.room = boat->RoomNumber;
p.ageRate = GenerateFloat(0.9, 1.3);
float size = GenerateFloat(96, 128);
p.worldPosition = { x, float(boat->Pose.Position.y) - size / 2, z };
p.worldPosition = { x, y, z };
p.sequence = ID_MOTOR_BOAT_FOAM_SPRITES;
p.size = GenerateFloat(256, 512);
}

View file

@ -22,6 +22,6 @@ namespace TEN::Effects{
SimpleParticle& getFreeSimpleParticle();
void TriggerSnowmobileSnow(ItemInfo* snowMobile);
void TriggerSpeedboatFoam(ItemInfo* boat);
void TriggerSpeedboatFoam(ItemInfo* boat, Vector3 offset);
void updateSimpleParticles();
}

View file

@ -204,9 +204,7 @@ void DoPickup(ItemInfo* laraItem)
short pickupItemNumber = getThisItemPlease;
auto* pickupItem = &g_Level.Items[pickupItemNumber];
short oldXrot = pickupItem->Pose.Orientation.x;
short oldYrot = pickupItem->Pose.Orientation.y;
short oldZrot = pickupItem->Pose.Orientation.z;
auto oldOrient = pickupItem->Pose.Orientation;
if (pickupItem->ObjectNumber == ID_BURNING_TORCH_ITEM)
{
@ -215,9 +213,7 @@ void DoPickup(ItemInfo* laraItem)
lara->Torch.IsLit = (pickupItem->ItemFlags[3] & 1);
KillItem(pickupItemNumber);
pickupItem->Pose.Orientation.x = oldXrot;
pickupItem->Pose.Orientation.y = oldYrot;
pickupItem->Pose.Orientation.z = oldZrot;
pickupItem->Pose.Orientation = oldOrient;
getThisItemPlease = NO_ITEM;
return;
}
@ -233,9 +229,7 @@ void DoPickup(ItemInfo* laraItem)
DrawFlareMeshes(laraItem);
KillItem(pickupItemNumber);
pickupItem->Pose.Orientation.x = oldXrot;
pickupItem->Pose.Orientation.y = oldYrot;
pickupItem->Pose.Orientation.z = oldZrot;
pickupItem->Pose.Orientation = oldOrient;
getThisItemPlease = NO_ITEM;
return;
}
@ -265,9 +259,7 @@ void DoPickup(ItemInfo* laraItem)
pickupItem->Status = ITEM_INVISIBLE;
}
pickupItem->Pose.Orientation.x = oldXrot;
pickupItem->Pose.Orientation.y = oldYrot;
pickupItem->Pose.Orientation.z = oldZrot;
pickupItem->Pose.Orientation = oldOrient;
getThisItemPlease = NO_ITEM;
return;
}
@ -298,9 +290,7 @@ void DoPickup(ItemInfo* laraItem)
pickupItem->Status = ITEM_INVISIBLE;
}
pickupItem->Pose.Orientation.x = oldXrot;
pickupItem->Pose.Orientation.y = oldYrot;
pickupItem->Pose.Orientation.z = oldZrot;
pickupItem->Pose.Orientation = oldOrient;
KillItem(pickupItemNumber);
getThisItemPlease = NO_ITEM;
return;
@ -315,9 +305,7 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
{
auto* item = &g_Level.Items[itemNumber];
short oldXrot = item->Pose.Orientation.x;
short oldYrot = item->Pose.Orientation.y;
short oldZrot = item->Pose.Orientation.z;
auto oldOrient = item->Pose.Orientation;
if (item->Status == ITEM_INVISIBLE)
return;
@ -386,16 +374,12 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
}
}
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
}
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
@ -412,16 +396,12 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
{
if (laraItem->Animation.ActiveState != LS_PICKUP && laraItem->Animation.ActiveState != LS_HOLE)
{
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
else
{
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
}
@ -429,9 +409,7 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
if (lara->InteractedItem != itemNumber)
{
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
}
@ -453,9 +431,7 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
}
}
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
else if (MoveLaraPosition(&HiddenPickUpPosition, item, laraItem))
@ -470,14 +446,12 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
break;
case 2: // Pickup with crowbar
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.y = oldOrient.y;
if (lara->Control.IsLow || !TestLaraPosition(&CrowbarPickUpBounds, item, laraItem))
{
if (!lara->Control.IsMoving)
{
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
@ -487,9 +461,7 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
Lara.Control.HandStatus = HandStatus::Free;
}
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
if (!lara->Control.IsMoving)
@ -499,17 +471,13 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
if (g_Gui.IsObjectInInventory(ID_CROWBAR_ITEM))
g_Gui.SetEnterInventory(ID_CROWBAR_ITEM);
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
if (g_Gui.GetInventoryItemChosen() != ID_CROWBAR_ITEM)
{
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
@ -537,9 +505,7 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
if (!plinth)
{
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
@ -579,9 +545,7 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
if (!lara->Control.IsMoving)
{
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
@ -591,19 +555,15 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
Lara.Control.HandStatus = HandStatus::Free;
}
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
case 9: // Pickup object and conver it to crowbar (like submarine level)
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.y = oldOrient.y;
if (!TestLaraPosition(&JobyCrowPickUpBounds, item, laraItem))
{
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
@ -625,9 +585,7 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
{
if (!lara->Control.IsMoving)
{
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
@ -637,9 +595,7 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
Lara.Control.HandStatus = HandStatus::Free;
}
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
return;
}
@ -726,9 +682,7 @@ void PickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
lara->Control.HandStatus = HandStatus::Busy;
}
item->Pose.Orientation.x = oldXrot;
item->Pose.Orientation.y = oldYrot;
item->Pose.Orientation.z = oldZrot;
item->Pose.Orientation = oldOrient;
}
void RegeneratePickups()
@ -740,32 +694,60 @@ void RegeneratePickups()
if (item->Status == ITEM_INVISIBLE)
{
short ammo = 0;
if (item->ObjectNumber == ID_CROSSBOW_AMMO1_ITEM)
switch (item->ObjectNumber)
{
case ID_CROSSBOW_AMMO1_ITEM:
ammo = Lara.Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo1];
else if (item->ObjectNumber == ID_CROSSBOW_AMMO2_ITEM)
break;
case ID_CROSSBOW_AMMO2_ITEM:
ammo = Lara.Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo2];
else if (item->ObjectNumber == ID_CROSSBOW_AMMO3_ITEM)
break;
case ID_CROSSBOW_AMMO3_ITEM:
ammo = Lara.Weapons[(int)LaraWeaponType::Crossbow].Ammo[(int)WeaponAmmoType::Ammo3];
else if(item->ObjectNumber == ID_GRENADE_AMMO1_ITEM)
break;
case ID_GRENADE_AMMO1_ITEM:
ammo = Lara.Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo1];
else if (item->ObjectNumber == ID_GRENADE_AMMO2_ITEM)
break;
case ID_GRENADE_AMMO2_ITEM:
ammo = Lara.Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo2];
else if (item->ObjectNumber == ID_GRENADE_AMMO3_ITEM)
break;
case ID_GRENADE_AMMO3_ITEM:
ammo = Lara.Weapons[(int)LaraWeaponType::GrenadeLauncher].Ammo[(int)WeaponAmmoType::Ammo3];
else if (item->ObjectNumber == ID_HK_AMMO_ITEM)
break;
case ID_HK_AMMO_ITEM:
ammo = Lara.Weapons[(int)LaraWeaponType::HK].Ammo[(int)WeaponAmmoType::Ammo1];
else if (item->ObjectNumber == ID_UZI_AMMO_ITEM)
break;
case ID_UZI_AMMO_ITEM:
ammo = Lara.Weapons[(int)LaraWeaponType::Uzi].Ammo[(int)WeaponAmmoType::Ammo1];
else if (item->ObjectNumber == ID_HARPOON_AMMO_ITEM)
break;
case ID_HARPOON_AMMO_ITEM:
ammo = Lara.Weapons[(int)LaraWeaponType::HarpoonGun].Ammo[(int)WeaponAmmoType::Ammo1];
else if (item->ObjectNumber == ID_ROCKET_LAUNCHER_AMMO_ITEM)
break;
case ID_ROCKET_LAUNCHER_AMMO_ITEM:
ammo = Lara.Weapons[(int)LaraWeaponType::RocketLauncher].Ammo[(int)WeaponAmmoType::Ammo1];
else if (item->ObjectNumber == ID_REVOLVER_AMMO_ITEM)
break;
case ID_REVOLVER_AMMO_ITEM:
ammo = Lara.Weapons[(int)LaraWeaponType::Revolver].Ammo[(int)WeaponAmmoType::Ammo1];
else if (item->ObjectNumber == ID_SHOTGUN_AMMO1_ITEM)
break;
case ID_SHOTGUN_AMMO1_ITEM:
ammo = Lara.Weapons[(int)LaraWeaponType::Shotgun].Ammo[(int)WeaponAmmoType::Ammo1];
else if (item->ObjectNumber == ID_SHOTGUN_AMMO1_ITEM)
break;
case ID_SHOTGUN_AMMO2_ITEM:
ammo = Lara.Weapons[(int)LaraWeaponType::Shotgun].Ammo[(int)WeaponAmmoType::Ammo2];
break;
}
if (ammo == 0)
item->Status = ITEM_NOT_ACTIVE;
@ -909,7 +891,7 @@ void InitialiseSearchObject(short itemNumber)
auto* item = &g_Level.Items[itemNumber];
if (item->ObjectNumber == ID_SEARCH_OBJECT1)
{
item->SwapMeshFlags = -1;
item->MeshSwapBits = ALL_JOINT_BITS;
item->MeshBits = 7;
}
else if (item->ObjectNumber == ID_SEARCH_OBJECT2)
@ -932,9 +914,7 @@ void InitialiseSearchObject(short itemNumber)
}
}
else if (Objects[item2->ObjectNumber].isPickup &&
item->Pose.Position.x == item2->Pose.Position.x &&
item->Pose.Position.y == item2->Pose.Position.y &&
item->Pose.Position.z == item2->Pose.Position.z)
item->Pose.Position == item2->Pose.Position)
{
item->ItemFlags[1] = itemNumber2;
break;
@ -981,10 +961,10 @@ void SearchObjectCollision(short itemNumber, ItemInfo* laraitem, CollisionInfo*
{
if (MoveLaraPosition(&SOPos, item, laraitem))
{
laraitem->Animation.ActiveState = LS_MISC_CONTROL;
ResetLaraFlex(laraitem);
laraitem->Animation.AnimNumber = SearchAnims[objectNumber];
laraitem->Animation.FrameNumber = g_Level.Anims[laraitem->Animation.AnimNumber].frameBase;
ResetLaraFlex(laraitem);
laraitem->Animation.ActiveState = LS_MISC_CONTROL;
Lara.Control.IsMoving = false;
Lara.Control.HandStatus = HandStatus::Busy;
@ -1028,12 +1008,12 @@ void SearchObjectControl(short itemNumber)
{
if (frameNumber > 0)
{
item->SwapMeshFlags = 0;
item->MeshBits = -1;
item->MeshSwapBits = NO_JOINT_BITS;
item->MeshBits = ALL_JOINT_BITS;
}
else
{
item->SwapMeshFlags = -1;
item->MeshSwapBits = ALL_JOINT_BITS;
item->MeshBits = 7;
}
}
@ -1129,7 +1109,7 @@ bool UseSpecialItem(ItemInfo* item)
if (flag == 1)
{
if (use != ID_WATERSKIN1_3 && use != ID_WATERSKIN2_5 && (LaraItem->Pose.Position.y > Lara.WaterSurfaceDist))
if (use != ID_WATERSKIN1_3 && use != ID_WATERSKIN2_5 && (Lara.WaterSurfaceDist < -SHALLOW_WATER_START_LEVEL))
{
if (use < ID_WATERSKIN1_3)
Lara.Inventory.SmallWaterskin = 4;
@ -1165,8 +1145,8 @@ bool UseSpecialItem(ItemInfo* item)
item->Animation.AnimNumber = LA_WATERSKIN_POUR_LOW;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.TargetState = LS_MISC_CONTROL;
item->Animation.ActiveState = LS_MISC_CONTROL;
item->Animation.TargetState = LS_MISC_CONTROL;
Lara.Control.HandStatus = HandStatus::Busy;
g_Gui.SetInventoryItemChosen(NO_ITEM);

View file

@ -8,6 +8,7 @@
#include "Game/control/flipeffect.h"
#include "Game/control/lot.h"
#include "Game/effects/lara_fx.h"
#include "Game/effects/effects.h"
#include "Game/items.h"
#include "Game/itemdata/creature_info.h"
#include "Game/Lara/lara.h"
@ -90,10 +91,10 @@ bool SaveGame::Save(int slot)
Save::SaveGameHeaderBuilder sghb{ fbb };
sghb.add_level_name(levelNameOffset);
sghb.add_days((GameTimer / 30) / 8640);
sghb.add_hours(((GameTimer / 30) % 86400) / 3600);
sghb.add_minutes(((GameTimer / 30) / 60) % 6);
sghb.add_seconds((GameTimer / 30) % 60);
sghb.add_days((GameTimer / FPS) / 8640);
sghb.add_hours(((GameTimer / FPS) % 86400) / 3600);
sghb.add_minutes(((GameTimer / FPS) / 60) % 6);
sghb.add_seconds((GameTimer / FPS) % 60);
sghb.add_level(CurrentLevel);
sghb.add_timer(GameTimer);
sghb.add_count(++LastSaveGame);
@ -426,6 +427,9 @@ bool SaveGame::Save(int slot)
auto itemFlagsOffset = fbb.CreateVector(itemFlags);
flatbuffers::Offset<Save::Creature> creatureOffset;
flatbuffers::Offset<Save::QuadBike> quadOffset;
flatbuffers::Offset<Save::UPV> upvOffset;
flatbuffers::Offset<Save::Short> shortOffset;
flatbuffers::Offset<Save::Int> intOffset;
@ -467,6 +471,45 @@ bool SaveGame::Save(int slot)
creatureBuilder.add_ai_target_number(creature->AITargetNumber);
creatureOffset = creatureBuilder.Finish();
}
else if (itemToSerialize.Data.is<QuadInfo>())
{
auto quad = (QuadInfo*)itemToSerialize.Data;
Save::QuadBikeBuilder quadBuilder{ fbb };
quadBuilder.add_can_start_drift(quad->CanStartDrift);
quadBuilder.add_drift_starting(quad->DriftStarting);
quadBuilder.add_engine_revs(quad->EngineRevs);
quadBuilder.add_extra_rotation(quad->ExtraRotation);
quadBuilder.add_flags(quad->Flags);
quadBuilder.add_front_rot(quad->FrontRot);
quadBuilder.add_left_vertical_velocity(quad->LeftVerticalVelocity);
quadBuilder.add_momentum_angle(quad->MomentumAngle);
quadBuilder.add_no_dismount(quad->NoDismount);
quadBuilder.add_pitch(quad->Pitch);
quadBuilder.add_rear_rot(quad->RearRot);
quadBuilder.add_revs(quad->Revs);
quadBuilder.add_right_vertical_velocity(quad->RightVerticalVelocity);
quadBuilder.add_smoke_start(quad->SmokeStart);
quadBuilder.add_turn_rate(quad->TurnRate);
quadBuilder.add_velocity(quad->Velocity);
quadOffset = quadBuilder.Finish();
}
else if (itemToSerialize.Data.is<UPVInfo>())
{
auto upv = (UPVInfo*)itemToSerialize.Data;
Save::UPVBuilder upvBuilder{ fbb };
upvBuilder.add_fan_rot(upv->FanRot);
upvBuilder.add_flags(upv->Flags);
upvBuilder.add_harpoon_left(upv->HarpoonLeft);
upvBuilder.add_harpoon_timer(upv->HarpoonTimer);
upvBuilder.add_rot(upv->Rot);
upvBuilder.add_velocity(upv->Velocity);
upvBuilder.add_x_rot(upv->XRot);
upvOffset = upvBuilder.Finish();
}
else if (itemToSerialize.Data.is<short>())
{
Save::ShortBuilder sb{ fbb };
@ -521,7 +564,7 @@ bool SaveGame::Save(int slot)
serializedItem.add_ai_bits(itemToSerialize.AIBits);
serializedItem.add_collidable(itemToSerialize.Collidable);
serializedItem.add_looked_at(itemToSerialize.LookedAt);
serializedItem.add_swap_mesh_flags(itemToSerialize.SwapMeshFlags);
serializedItem.add_swap_mesh_flags(itemToSerialize.MeshSwapBits);
if (Objects[itemToSerialize.ObjectNumber].intelligent
&& itemToSerialize.Data.is<CreatureInfo>())
@ -529,6 +572,16 @@ bool SaveGame::Save(int slot)
serializedItem.add_data_type(Save::ItemData::Creature);
serializedItem.add_data(creatureOffset.Union());
}
else if (itemToSerialize.Data.is<QuadInfo>())
{
serializedItem.add_data_type(Save::ItemData::QuadBike);
serializedItem.add_data(quadOffset.Union());
}
else if (itemToSerialize.Data.is<UPVInfo>())
{
serializedItem.add_data_type(Save::ItemData::UPV);
serializedItem.add_data(upvOffset.Union());
}
else if (itemToSerialize.Data.is<short>())
{
serializedItem.add_data_type(Save::ItemData::Short);
@ -627,6 +680,59 @@ bool SaveGame::Save(int slot)
}
auto staticMeshesOffset = fbb.CreateVector(staticMeshes);
// Particles
std::vector<flatbuffers::Offset<Save::ParticleInfo>> particles;
for (int i = 0; i < MAX_PARTICLES; i++)
{
auto* particle = &Particles[i];
if (!particle->on)
continue;
Save::ParticleInfoBuilder particleInfo{ fbb };
particleInfo.add_b(particle->b);
particleInfo.add_col_fade_speed(particle->colFadeSpeed);
particleInfo.add_d_b(particle->dB);
particleInfo.add_sprite_index(particle->spriteIndex);
particleInfo.add_d_g(particle->dG);
particleInfo.add_d_r(particle->dR);
particleInfo.add_d_size(particle->dSize);
particleInfo.add_dynamic(particle->dynamic);
particleInfo.add_extras(particle->extras);
particleInfo.add_fade_to_black(particle->fadeToBlack);
particleInfo.add_flags(particle->flags);
particleInfo.add_friction(particle->friction);
particleInfo.add_fx_obj(particle->fxObj);
particleInfo.add_g(particle->g);
particleInfo.add_gravity(particle->gravity);
particleInfo.add_life(particle->life);
particleInfo.add_max_y_vel(particle->maxYvel);
particleInfo.add_node_number(particle->nodeNumber);
particleInfo.add_on(particle->on);
particleInfo.add_r(particle->r);
particleInfo.add_room_number(particle->roomNumber);
particleInfo.add_rot_add(particle->rotAdd);
particleInfo.add_rot_ang(particle->rotAng);
particleInfo.add_s_b(particle->sB);
particleInfo.add_scalar(particle->scalar);
particleInfo.add_s_g(particle->sG);
particleInfo.add_size(particle->size);
particleInfo.add_s_life(particle->sLife);
particleInfo.add_s_r(particle->sR);
particleInfo.add_s_size(particle->sSize);
particleInfo.add_blend_mode(particle->blendMode);
particleInfo.add_x(particle->x);
particleInfo.add_x_vel(particle->sSize);
particleInfo.add_y(particle->y);
particleInfo.add_y_vel(particle->yVel);
particleInfo.add_z(particle->z);
particleInfo.add_z_vel(particle->zVel);
particles.push_back(particleInfo.Finish());
}
auto particleOffset = fbb.CreateVector(particles);
// Particle enemies
std::vector<flatbuffers::Offset<Save::BatInfo>> bats;
for (int i = 0; i < NUM_BATS; i++)
@ -905,6 +1011,7 @@ bool SaveGame::Save(int slot)
sgb.add_flip_timer(0);
sgb.add_static_meshes(staticMeshesOffset);
sgb.add_fixed_cameras(camerasOffset);
sgb.add_particles(particleOffset);
sgb.add_bats(batsOffset);
sgb.add_rats(ratsOffset);
sgb.add_spiders(spidersOffset);
@ -954,6 +1061,23 @@ bool SaveGame::Load(int slot)
const Save::SaveGame* s = Save::GetSaveGame(buffer.get());
// Statistics
Statistics.Game.AmmoHits = s->game()->ammo_hits();
Statistics.Game.AmmoUsed = s->game()->ammo_used();
Statistics.Game.Distance = s->game()->distance();
Statistics.Game.HealthUsed = s->game()->medipacks_used();
Statistics.Game.Kills = s->game()->kills();
Statistics.Game.Secrets = s->game()->secrets();
Statistics.Game.Timer = s->game()->timer();
Statistics.Level.AmmoHits = s->level()->ammo_hits();
Statistics.Level.AmmoUsed = s->level()->ammo_used();
Statistics.Level.Distance = s->level()->distance();
Statistics.Level.HealthUsed = s->level()->medipacks_used();
Statistics.Level.Kills = s->level()->kills();
Statistics.Level.Secrets = s->level()->secrets();
Statistics.Level.Timer = s->level()->timer();
// Flipmaps
for (int i = 0; i < s->flip_stats()->size(); i++)
{
@ -1112,6 +1236,25 @@ bool SaveGame::Load(int slot)
item->Collidable = savedItem->collidable();
item->LookedAt = savedItem->looked_at();
// Mesh stuff
item->MeshBits = savedItem->mesh_bits();
item->MeshSwapBits = savedItem->swap_mesh_flags();
if (item->ObjectNumber >= ID_SMASH_OBJECT1 && item->ObjectNumber <= ID_SMASH_OBJECT8 &&
(item->Flags & ONESHOT))
item->MeshBits = 0x00100;
// Now some post-load specific hacks for objects
if (item->ObjectNumber >= ID_PUZZLE_HOLE1 && item->ObjectNumber <= ID_PUZZLE_HOLE16 &&
(item->Status == ITEM_ACTIVE || item->Status == ITEM_DEACTIVATED))
{
item->ObjectNumber = (GAME_OBJECT_ID)((int)item->ObjectNumber + ID_PUZZLE_DONE1 - ID_PUZZLE_HOLE1);
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + savedItem->anim_number();
}
if (obj->floor != nullptr)
UpdateBridgeItem(i);
// Creature data for intelligent items
if (item->ObjectNumber != ID_LARA && obj->intelligent && (savedItem->flags() & (TRIGGERED | CODE_BITS | ONESHOT)))
{
@ -1151,6 +1294,41 @@ bool SaveGame::Load(int slot)
creature->Tosspad = savedCreature->tosspad();
SetBaddyTarget(i, savedCreature->ai_target_number());
}
else if (item->Data.is<QuadInfo>())
{
auto quad = (QuadInfo*)item->Data;
auto savedQuad = (Save::QuadBike*)savedItem->data();
quad->CanStartDrift = savedQuad->can_start_drift();
quad->DriftStarting = savedQuad->drift_starting();
quad->EngineRevs = savedQuad->engine_revs();
quad->ExtraRotation = savedQuad->extra_rotation();
quad->Flags = savedQuad->flags();
quad->FrontRot = savedQuad->front_rot();
quad->LeftVerticalVelocity = savedQuad->left_vertical_velocity();
quad->MomentumAngle = savedQuad->momentum_angle();
quad->NoDismount = savedQuad->no_dismount();
quad->Pitch = savedQuad->pitch();
quad->RearRot = savedQuad->rear_rot();
quad->Revs = savedQuad->revs();
quad->RightVerticalVelocity = savedQuad->right_vertical_velocity();
quad->SmokeStart = savedQuad->smoke_start();
quad->TurnRate = savedQuad->turn_rate();
quad->Velocity = savedQuad->velocity();
}
else if (item->Data.is<UPVInfo>())
{
auto upv = (UPVInfo*)item->Data;
auto savedUpv = (Save::UPV*)savedItem->data();
upv->FanRot = savedUpv->fan_rot();
upv->Flags = savedUpv->flags();
upv->HarpoonLeft = savedUpv->harpoon_left();
upv->HarpoonTimer = savedUpv->harpoon_timer();
upv->Rot = savedUpv->rot();
upv->Velocity = savedUpv->velocity();
upv->XRot = savedUpv->x_rot();
}
else if (savedItem->data_type() == Save::ItemData::Short)
{
auto data = savedItem->data();
@ -1163,29 +1341,50 @@ bool SaveGame::Load(int slot)
auto savedData = (Save::Int*)data;
item->Data = savedData->scalar();
}
}
for (int i = 0; i < s->particles()->size(); i++)
{
auto particleInfo = s->particles()->Get(i);
auto* particle = &Particles[i];
// Mesh stuff
item->MeshBits = savedItem->mesh_bits();
item->SwapMeshFlags = savedItem->swap_mesh_flags();
// Now some post-load specific hacks for objects
if (item->ObjectNumber >= ID_PUZZLE_HOLE1
&& item->ObjectNumber <= ID_PUZZLE_HOLE16
&& (item->Status == ITEM_ACTIVE
|| item->Status == ITEM_DEACTIVATED))
{
item->ObjectNumber = (GAME_OBJECT_ID)((int)item->ObjectNumber + ID_PUZZLE_DONE1 - ID_PUZZLE_HOLE1);
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + savedItem->anim_number();
}
if ((item->ObjectNumber >= ID_SMASH_OBJECT1)
&& (item->ObjectNumber <= ID_SMASH_OBJECT8)
&& (item->Flags & ONESHOT))
item->MeshBits = 0x00100;
if (obj->floor != nullptr)
UpdateBridgeItem(i);
particle->x = particleInfo->x();
particle->y = particleInfo->y();
particle->z = particleInfo->z();
particle->xVel = particleInfo->x_vel();
particle->yVel = particleInfo->y_vel();
particle->zVel = particleInfo->z_vel();
particle->gravity = particleInfo->gravity();
particle->rotAng = particleInfo->rot_ang();
particle->flags = particleInfo->flags();
particle->sSize = particleInfo->s_size();
particle->dSize = particleInfo->d_size();
particle->size = particleInfo->size();
particle->friction = particleInfo->friction();
particle->scalar = particleInfo->scalar();
particle->spriteIndex = particleInfo->sprite_index();
particle->rotAdd = particleInfo->rot_add();
particle->maxYvel = particleInfo->max_y_vel();
particle->on = particleInfo->on();
particle->sR = particleInfo->s_r();
particle->sG = particleInfo->s_g();
particle->sB = particleInfo->s_b();
particle->dR = particleInfo->d_r();
particle->dG = particleInfo->d_g();
particle->dB = particleInfo->d_b();
particle->r = particleInfo->r();
particle->g = particleInfo->g();
particle->b = particleInfo->b();
particle->colFadeSpeed = particleInfo->col_fade_speed();
particle->fadeToBlack = particleInfo->fade_to_black();
particle->sLife = particleInfo->s_life();
particle->life = particleInfo->life();
particle->blendMode = (BLEND_MODES)particleInfo->blend_mode();
particle->extras = particleInfo->extras();
particle->dynamic = particleInfo->dynamic();
particle->fxObj = particleInfo->fx_obj();
particle->roomNumber = particleInfo->room_number();
particle->nodeNumber = particleInfo->node_number();
}
for (int i = 0; i < s->bats()->size(); i++)

View file

@ -115,7 +115,7 @@ void InitialiseSpotCam(short Sequence)
AlterFOV(16380);
LaraItem->MeshBits = -1;
LaraItem->MeshBits = ALL_JOINT_BITS;
ResetLaraFlex(LaraItem);
@ -521,7 +521,7 @@ void CalculateSpotCameras()
}
}
LookAt(&Camera, 0);
LookAt(&Camera, croll);
if (CheckTrigger)
{
@ -814,6 +814,9 @@ void CalculateSpotCameras()
SplineToCamera = 1;
}
if (CurrentSplineCamera > LastCamera)
CurrentSplineCamera = LastCamera;
}
else
{

View file

@ -13,6 +13,7 @@
#include "Objects/TR4/Entity/tr4_mutant.h"
#include "Objects/TR4/Entity/tr4_demigod.h"
#include "Specific/level.h"
#include "Renderer/Renderer11Enums.h"
using namespace TEN::Effects::Lara;
using namespace TEN::Entities::TR4;
@ -29,7 +30,7 @@ namespace TEN::Entities::Effects
if (dx >= -SECTOR(16) && dx <= SECTOR(16) &&
dz >= -SECTOR(16) && dz <= SECTOR(16))
{
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = 1;
spark->sR = 0;
@ -40,7 +41,7 @@ namespace TEN::Entities::Effects
spark->dG = spark->dB + 64;
spark->fadeToBlack = 8;
spark->colFadeSpeed = (GetRandomControl() & 3) + 4;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 3) + 16;
spark->y = 0;
spark->x = (GetRandomControl() & 0xF) - 8;
@ -80,7 +81,7 @@ namespace TEN::Entities::Effects
if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384)
{
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = 1;
spark->sR = 0;
@ -90,7 +91,7 @@ namespace TEN::Entities::Effects
spark->dG = spark->dR = (GetRandomControl() & 0x7F) + 32;
spark->fadeToBlack = 8;
spark->colFadeSpeed = (GetRandomControl() & 3) + 4;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 3) + 16;
spark->y = 0;
spark->x = (GetRandomControl() & 0xF) - 8;

View file

@ -107,7 +107,7 @@ namespace TEN::Entities::Effects
}
if (item->ItemFlags[2])
AddFire(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, SP_NORMALFIRE, item->RoomNumber, item->ItemFlags[2]);
AddFire(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber, 0.5f, item->ItemFlags[2]);
if (item->ItemFlags[1])
{
@ -143,7 +143,7 @@ namespace TEN::Entities::Effects
if (item->TriggerFlags < 8)
FlameEmitterFlags[item->TriggerFlags] = true;
AddFire(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, SP_BIGFIRE, item->RoomNumber, 0);
AddFire(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber, 1.0f, 0);
TriggerDynamicLight(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z,
16 - (GetRandomControl() & 1),
@ -171,7 +171,7 @@ namespace TEN::Entities::Effects
void FlameEmitter2Control(short itemNumber)
{
ItemInfo* item = &g_Level.Items[itemNumber];
auto* item = &g_Level.Items[itemNumber];
if (TriggerActive(item))
{
@ -184,10 +184,29 @@ namespace TEN::Entities::Effects
if (item->TriggerFlags == 123)
{
// Middle of the block
AddFire(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, SP_SMALLFIRE, item->RoomNumber, item->ItemFlags[3]);
AddFire(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber, 0.25f, item->ItemFlags[3]);
}
else
AddFire(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, SP_SMALLFIRE - item->TriggerFlags, item->RoomNumber, item->ItemFlags[3]);
{
float size = 1.0f;
switch (item->TriggerFlags)
{
default:
case 0:
size = 2.0f;
break;
case 1:
size = 1.0f;
break;
case 3:
size = 0.5f;
break;
}
AddFire(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber, size, item->ItemFlags[3]);
}
}
if (item->TriggerFlags == 0 || item->TriggerFlags == 2)
@ -212,30 +231,27 @@ namespace TEN::Entities::Effects
if (item->TriggerFlags == 2)
{
item->Pose.Position.x += phd_sin(item->Pose.Orientation.y - ANGLE(180));
item->Pose.Position.z += phd_cos(item->Pose.Orientation.y - ANGLE(180));
item->Pose.Position.x += phd_sin(item->Pose.Orientation.y - ANGLE(180)) * (CLICK(1) / FPS);
item->Pose.Position.z += phd_cos(item->Pose.Orientation.y - ANGLE(180)) * (CLICK(1) / FPS);
short roomNumber = item->RoomNumber;
FloorInfo* floor = GetFloor(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, &roomNumber);
auto probe = GetCollision(item);
if (g_Level.Rooms[roomNumber].flags & ENV_FLAG_WATER)
if (TestEnvironment(ENV_FLAG_WATER, probe.RoomNumber) ||
probe.Position.Floor - item->Pose.Position.y > CLICK(2) ||
probe.Position.Floor == NO_HEIGHT)
{
Weather.Flash(255, 128, 0, 0.03f);
KillItem(itemNumber);
return;
}
if (item->RoomNumber != roomNumber)
{
ItemNewRoom(itemNumber, roomNumber);
}
if (item->RoomNumber != probe.RoomNumber)
ItemNewRoom(itemNumber, probe.RoomNumber);
item->Pose.Position.y = GetFloorHeight(floor, item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z);;
item->Pose.Position.y = probe.Position.Floor;
if (Wibble & 7)
{
TriggerFireFlame(item->Pose.Position.x, item->Pose.Position.y - 32, item->Pose.Position.z, -1, 1);
}
}
SoundEffect(SFX_TR4_LOOP_FOR_SMALL_FIRES, &item->Pose);
@ -251,7 +267,7 @@ namespace TEN::Entities::Effects
void FlameControl(short fxNumber)
{
FX_INFO* fx = &EffectList[fxNumber];
auto* fx = &EffectList[fxNumber];
for (int i = 0; i < 14; i++)
{
@ -279,7 +295,7 @@ namespace TEN::Entities::Effects
byte g = (GetRandomControl() & 0x1F) + 96;
byte b;
Vector3Int pos{ 0,0,0 };
auto pos = Vector3Int();
GetLaraJointPosition(&pos, LM_HIPS);
if (!Lara.BurnSmoke)
@ -333,8 +349,8 @@ namespace TEN::Entities::Effects
if (LaraItem->RoomNumber != fx->roomNumber)
EffectNewRoom(fxNumber, LaraItem->RoomNumber);
int wh = GetWaterHeight(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, fx->roomNumber);
if (wh == NO_HEIGHT || fx->pos.Position.y <= wh || Lara.BurnBlue)
int waterHeight = GetWaterHeight(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, fx->roomNumber);
if (waterHeight == NO_HEIGHT || fx->pos.Position.y <= waterHeight || Lara.BurnBlue)
{
SoundEffect(SFX_TR4_LOOP_FOR_SMALL_FIRES, &fx->pos);
@ -356,7 +372,7 @@ namespace TEN::Entities::Effects
void InitialiseFlameEmitter(short itemNumber)
{
ItemInfo* item = &g_Level.Items[itemNumber];
auto* item = &g_Level.Items[itemNumber];
if (item->TriggerFlags < 0)
{
@ -389,7 +405,7 @@ namespace TEN::Entities::Effects
void InitialiseFlameEmitter2(short itemNumber)
{
ItemInfo* item = &g_Level.Items[itemNumber];
auto* item = &g_Level.Items[itemNumber];
item->Pose.Position.y -= 64;
@ -402,6 +418,7 @@ namespace TEN::Entities::Effects
item->Pose.Position.z += 80;
else
item->Pose.Position.z += 256;
break;
case 0x4000:
@ -409,6 +426,7 @@ namespace TEN::Entities::Effects
item->Pose.Position.x += 80;
else
item->Pose.Position.x += 256;
break;
case -0x8000:
@ -416,6 +434,7 @@ namespace TEN::Entities::Effects
item->Pose.Position.z -= 80;
else
item->Pose.Position.z -= 256;
break;
case -0x4000:
@ -423,6 +442,7 @@ namespace TEN::Entities::Effects
item->Pose.Position.x -= 80;
else
item->Pose.Position.x -= 256;
break;
}
}
@ -430,13 +450,13 @@ namespace TEN::Entities::Effects
void InitialiseFlameEmitter3(short itemNumber)
{
ItemInfo* item = &g_Level.Items[itemNumber];
auto* item = &g_Level.Items[itemNumber];
if (item->TriggerFlags >= 3)
{
for (int i = 0; i < g_Level.NumItems; i++)
{
ItemInfo* currentItem = &g_Level.Items[i];
auto* currentItem = &g_Level.Items[i];
if (currentItem->ObjectNumber == ID_ANIMATING3)
{
@ -451,24 +471,20 @@ namespace TEN::Entities::Effects
void FlameEmitter3Control(short itemNumber)
{
ItemInfo* item = &g_Level.Items[itemNumber];
auto* item = &g_Level.Items[itemNumber];
if (TriggerActive(item))
{
if (item->TriggerFlags)
{
SoundEffect(SFX_TR4_ELEC_ARCING_LOOP, &item->Pose);
SoundEffect(SFX_TR4_ELECTRIC_ARCING_LOOP, &item->Pose);
byte g = (GetRandomControl() & 0x3F) + 192;
byte b = (GetRandomControl() & 0x3F) + 192;
Vector3Int src;
auto src = item->Pose.Position;
Vector3Int dest;
src.x = item->Pose.Position.x;
src.y = item->Pose.Position.y;
src.z = item->Pose.Position.z;
if (!(GlobalCounter & 3))
{
if (item->TriggerFlags == 2 || item->TriggerFlags == 4)
@ -511,11 +527,9 @@ namespace TEN::Entities::Effects
if (item->TriggerFlags >= 3 && !(GlobalCounter & 1))
{
short targetItemNumber = item->ItemFlags[((GlobalCounter >> 2) & 1) + 2];
ItemInfo* targetItem = &g_Level.Items[targetItemNumber];
auto* targetItem = &g_Level.Items[targetItemNumber];
dest.x = 0;
dest.y = -64;
dest.z = 20;
dest = Vector3Int(0, -64, 20);
GetJointAbsPosition(targetItem, &dest, 0);
if (!(GlobalCounter & 3))
@ -549,19 +563,20 @@ namespace TEN::Entities::Effects
5);
}
}
if (item->TriggerFlags != 3 || targetItem->TriggerFlags)
TriggerLightningGlow(dest.x, dest.y, dest.z, 64, 0, g, b);
}
if ((GlobalCounter & 3) == 2)
{
src.x = item->Pose.Position.x;
src.y = item->Pose.Position.y;
src.z = item->Pose.Position.z;
src = item->Pose.Position;
dest.x = (GetRandomControl() & 0x1FF) + src.x - 256;
dest.y = (GetRandomControl() & 0x1FF) + src.y - 256;
dest.z = (GetRandomControl() & 0x1FF) + src.z - 256;
dest = Vector3Int(
(GetRandomControl() & 0x1FF) + src.x - 256,
(GetRandomControl() & 0x1FF) + src.y - 256,
(GetRandomControl() & 0x1FF) + src.z - 256
);
TriggerLightning(
&src,
@ -581,9 +596,7 @@ namespace TEN::Entities::Effects
{
// Small fires
if (item->ItemFlags[0] != 0)
{
item->ItemFlags[0]--;
}
else
{
item->ItemFlags[0] = (GetRandomControl() & 3) + 8;
@ -614,10 +627,7 @@ namespace TEN::Entities::Effects
TriggerDynamicLight(x, item->Pose.Position.y, z, 12, (GetRandomControl() & 0x3F) + 192, ((GetRandomControl() >> 4) & 0x1F) + 96, 0);
PHD_3DPOS pos;
pos.Position.x = item->Pose.Position.x;
pos.Position.y = item->Pose.Position.y;
pos.Position.z = item->Pose.Position.z;
auto pos = PHD_3DPOS(item->Pose.Position);
if (ItemNearLara(&pos, 600))
{
@ -637,22 +647,22 @@ namespace TEN::Entities::Effects
}
}
void FlameEmitterCollision(short itemNumber, ItemInfo* l, CollisionInfo* coll)
void FlameEmitterCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
{
ItemInfo* item = &g_Level.Items[itemNumber];
auto* item = &g_Level.Items[itemNumber];
if (Lara.Control.Weapon.GunType != LaraWeaponType::Torch
|| Lara.Control.HandStatus != HandStatus::WeaponReady
|| Lara.LeftArm.Locked
|| Lara.Torch.IsLit == (item->Status & 1)
|| item->Timer == -1
|| !(TrInput & IN_ACTION)
|| l->Animation.ActiveState != LS_IDLE
|| l->Animation.AnimNumber != LA_STAND_IDLE
|| l->Animation.Airborne)
if (Lara.Control.Weapon.GunType != LaraWeaponType::Torch ||
Lara.Control.HandStatus != HandStatus::WeaponReady ||
Lara.LeftArm.Locked ||
Lara.Torch.IsLit == (item->Status & 1) ||
item->Timer == -1 ||
!(TrInput & IN_ACTION) ||
laraItem->Animation.ActiveState != LS_IDLE ||
laraItem->Animation.AnimNumber != LA_STAND_IDLE ||
laraItem->Animation.Airborne)
{
if (item->ObjectNumber == ID_BURNING_ROOTS)
ObjectCollision(itemNumber, l, coll);
ObjectCollision(itemNumber, laraItem, coll);
}
else
{
@ -684,28 +694,25 @@ namespace TEN::Entities::Effects
FireBounds.boundingBox.Z1 = -384;
FireBounds.boundingBox.Z2 = 384;
break;
}
short oldYrot = item->Pose.Orientation.y;
item->Pose.Orientation.y = l->Pose.Orientation.y;
item->Pose.Orientation.y = laraItem->Pose.Orientation.y;
if (TestLaraPosition(&FireBounds, item, l))
if (TestLaraPosition(&FireBounds, item, laraItem))
{
if (item->ObjectNumber == ID_BURNING_ROOTS)
{
l->Animation.AnimNumber = LA_TORCH_LIGHT_5;
}
laraItem->Animation.AnimNumber = LA_TORCH_LIGHT_5;
else
{
Lara.Torch.State = TorchState::JustLit;
int dy = abs(l->Pose.Position.y - item->Pose.Position.y);
l->ItemFlags[3] = 1;
l->Animation.AnimNumber = (dy >> 8) + LA_TORCH_LIGHT_1;
int dy = abs(laraItem->Pose.Position.y - item->Pose.Position.y);
laraItem->ItemFlags[3] = 1;
laraItem->Animation.AnimNumber = (dy >> 8) + LA_TORCH_LIGHT_1;
}
l->Animation.ActiveState = LS_MISC_CONTROL;
l->Animation.FrameNumber = g_Level.Anims[l->Animation.AnimNumber].frameBase;
laraItem->Animation.ActiveState = LS_MISC_CONTROL;
laraItem->Animation.FrameNumber = g_Level.Anims[laraItem->Animation.AnimNumber].frameBase;
Lara.Flare.ControlLeft = false;
Lara.LeftArm.Locked = true;
Lara.InteractedItem = itemNumber;
@ -714,13 +721,13 @@ namespace TEN::Entities::Effects
item->Pose.Orientation.y = oldYrot;
}
if (Lara.InteractedItem == itemNumber
&& item->Status != ITEM_ACTIVE
&& l->Animation.ActiveState == LS_MISC_CONTROL)
if (Lara.InteractedItem == itemNumber &&
item->Status != ITEM_ACTIVE &&
laraItem->Animation.ActiveState == LS_MISC_CONTROL)
{
if (l->Animation.AnimNumber >= LA_TORCH_LIGHT_1 && l->Animation.AnimNumber <= LA_TORCH_LIGHT_5)
if (laraItem->Animation.AnimNumber >= LA_TORCH_LIGHT_1 && laraItem->Animation.AnimNumber <= LA_TORCH_LIGHT_5)
{
if (l->Animation.FrameNumber - g_Level.Anims[l->Animation.AnimNumber].frameBase == 40)
if (laraItem->Animation.FrameNumber - g_Level.Anims[laraItem->Animation.AnimNumber].frameBase == 40)
{
TestTriggers(item, true, item->Flags & IFLAG_ACTIVATION_MASK);

View file

@ -20,7 +20,7 @@ using namespace TEN::Effects::Lara;
void TriggerElectricityWireSparks(int x, int z, byte objNum, byte node, bool glow)
{
SPARKS* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = true;
spark->sR = 255;
spark->sG = 255;
@ -43,7 +43,7 @@ void TriggerElectricityWireSparks(int x, int z, byte objNum, byte node, bool glo
}
spark->fxObj = objNum;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->flags = SP_ITEM | SP_NODEATTACH | SP_SCALE | SP_DEF;
spark->nodeNumber = node;
spark->x = x;
@ -71,13 +71,13 @@ void TriggerElectricityWireSparks(int x, int z, byte objNum, byte node, bool glo
if (glow)
{
spark->scalar = 1;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex + 11;
spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + 11;
spark->size = spark->sSize = (GetRandomControl() & 0x1F) + 160;
}
else
{
spark->scalar = 0;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex + 14;
spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + 14;
spark->size = spark->sSize = (GetRandomControl() & 7) + 8;
}
@ -89,7 +89,7 @@ void TriggerElectricitySparks(ItemInfo* item, int joint, int flame)
Vector3Int pos = { 0, 0, 0 };
GetJointAbsPosition(item, &pos, joint);
SPARKS* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = 1;
spark->dR = 0;
@ -100,7 +100,7 @@ void TriggerElectricitySparks(ItemInfo* item, int joint, int flame)
spark->sG = color;
spark->dB = color;
spark->dG = color / 2;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->fadeToBlack = 4;
spark->life = 12;
spark->sLife = 12;

View file

@ -14,6 +14,7 @@
#include "Game/collision/collide_room.h"
#include "Game/collision/collide_item.h"
#include "Game/control/los.h"
#include "Renderer/Renderer11Enums.h"
using namespace TEN::Entities::Effects;
@ -21,7 +22,7 @@ namespace TEN::Entities::Generic
{
void TriggerTorchFlame(char fxObj, char node)
{
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = true;
@ -34,7 +35,7 @@ namespace TEN::Entities::Generic
spark->fadeToBlack = 8;
spark->colFadeSpeed = (GetRandomControl() & 3) + 12;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 7) + 24;
spark->x = (GetRandomControl() & 0xF) - 8;
@ -81,8 +82,8 @@ namespace TEN::Entities::Generic
Lara.Torch.State = TorchState::Dropping;
}
else if (TrInput & IN_DRAW &&
!LaraItem->Animation.VerticalVelocity &&
!LaraItem->Animation.Airborne &&
!LaraItem->Animation.VerticalVelocity &&
LaraItem->Animation.ActiveState != LS_JUMP_PREPARE &&
LaraItem->Animation.ActiveState != LS_JUMP_UP &&
LaraItem->Animation.ActiveState != LS_JUMP_FORWARD &&
@ -185,11 +186,11 @@ namespace TEN::Entities::Generic
if (Lara.Control.Weapon.GunType == LaraWeaponType::Flare)
CreateFlare(LaraItem, ID_FLARE_ITEM, false);
Lara.Control.HandStatus = HandStatus::WeaponReady;
Lara.Control.Weapon.RequestGunType = LaraWeaponType::Torch;
Lara.Control.Weapon.GunType = LaraWeaponType::Torch;
Lara.Flare.ControlLeft = true;
Lara.LeftArm.AnimNumber = Objects[ID_LARA_TORCH_ANIM].animIndex;
Lara.Control.HandStatus = HandStatus::WeaponReady;
Lara.LeftArm.Locked = false;
Lara.LeftArm.FrameNumber = 0;
Lara.LeftArm.FrameBase = g_Level.Anims[Lara.LeftArm.AnimNumber].framePtr;
@ -201,25 +202,28 @@ namespace TEN::Entities::Generic
{
auto* item = &g_Level.Items[itemNumber];
int oldX = item->Pose.Position.x;
int oldY = item->Pose.Position.y;
int oldZ = item->Pose.Position.z;
if (item->Animation.VerticalVelocity)
item->Pose.Orientation.z += ANGLE(5);
{
item->Pose.Orientation.x -= ANGLE(5.0f);
item->Pose.Orientation.z += ANGLE(5.0f);
}
else if (!item->Animation.Velocity)
{
item->Pose.Orientation.x = 0;
item->Pose.Orientation.z = 0;
}
int xv = item->Animation.Velocity * phd_sin(item->Pose.Orientation.y);
int zv = item->Animation.Velocity * phd_cos(item->Pose.Orientation.y);
auto velocity = Vector3Int(
item->Animation.Velocity * phd_sin(item->Pose.Orientation.y),
item->Animation.VerticalVelocity,
item->Animation.Velocity * phd_cos(item->Pose.Orientation.y)
);
item->Pose.Position.x += xv;
item->Pose.Position.z += zv;
auto oldPos = item->Pose.Position;
item->Pose.Position += Vector3Int(velocity.x, 0, velocity.z);
if (g_Level.Rooms[item->RoomNumber].flags & ENV_FLAG_WATER)
if (TestEnvironment(ENV_FLAG_WATER, item) ||
TestEnvironment(ENV_FLAG_SWAMP, item))
{
item->Animation.VerticalVelocity += (5 - item->Animation.VerticalVelocity) / 2;
item->Animation.Velocity += (5 - item->Animation.Velocity) / 2;
@ -231,22 +235,24 @@ namespace TEN::Entities::Generic
item->Animation.VerticalVelocity += 6;
item->Pose.Position.y += item->Animation.VerticalVelocity;
DoProjectileDynamics(itemNumber, oldPos.x, oldPos.y, oldPos.z, velocity.x, velocity.y, velocity.z);
DoProjectileDynamics(itemNumber, oldX, oldY, oldZ, xv, item->Animation.VerticalVelocity, zv);
if (GetCollidedObjects(item, 0, true, CollidedItems, CollidedMeshes, 0))
// Collide with entities.
if (GetCollidedObjects(item, 0, true, CollidedItems, CollidedMeshes, true))
{
LaraCollision.Setup.EnableObjectPush = true;
if (CollidedItems)
{
if (!Objects[CollidedItems[0]->ObjectNumber].intelligent
&& CollidedItems[0]->ObjectNumber != ID_LARA)
if (!Objects[CollidedItems[0]->ObjectNumber].intelligent &&
CollidedItems[0]->ObjectNumber != ID_LARA)
{
ObjectCollision(CollidedItems[0] - g_Level.Items.data(), item, &LaraCollision);
}
}
else
ItemPushStatic(item, CollidedMeshes[0], &LaraCollision);
item->Animation.Velocity >>= 1;
item->Animation.Velocity /= 2;
}
if (item->ItemFlags[3])
@ -262,16 +268,18 @@ namespace TEN::Entities::Generic
void LaraTorch(Vector3Int* src, Vector3Int* target, int rot, int color)
{
GameVector pos1;
pos1.x = src->x;
pos1.y = src->y;
pos1.z = src->z;
pos1.roomNumber = LaraItem->RoomNumber;
auto pos1 = GameVector(
src->x,
src->y,
src->z,
LaraItem->RoomNumber
);
GameVector pos2;
pos2.x = target->x;
pos2.y = target->y;
pos2.z = target->z;
auto pos2 = GameVector(
target->x,
target->y,
target->z
);
TriggerDynamicLight(pos1.x, pos1.y, pos1.z, 12, color, color, color >> 1);

View file

@ -12,6 +12,7 @@
#include "Specific/input.h"
#include "Sound/sound.h"
#include "Game/collision/collide_item.h"
#include "Renderer/Renderer11Enums.h"
OBJECT_TEXTURE* WaterfallTextures[6];
float WaterfallY[6];
@ -347,7 +348,7 @@ void HighObject2Control(short itemNumber)
if (--item->ItemFlags[2] < 15)
{
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = 1;
spark->sR = -1;
spark->sB = 16;
@ -357,7 +358,7 @@ void HighObject2Control(short itemNumber)
spark->dG = (GetRandomControl() & 0x3F) + -128;
spark->fadeToBlack = 4;
spark->colFadeSpeed = (GetRandomControl() & 3) + 4;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 3) + 24;
spark->x = item->ItemFlags[1] + (GetRandomControl() & 0x3F) + item->Pose.Position.x - 544;
spark->y = item->Pose.Position.y;
@ -381,7 +382,7 @@ void HighObject2Control(short itemNumber)
else
{
spark->flags = SP_ROTATE | SP_DEF | SP_SCALE;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_UNDERWATERDUST;
spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_UNDERWATERDUST;
spark->scalar = 1;
spark->gravity = (GetRandomControl() & 0xF) + 64;
}

View file

@ -692,7 +692,7 @@ namespace TEN::Entities::Generic
if (item->Animation.AnimNumber == LA_ROPE_DOWN && item->Animation.FrameNumber == g_Level.Anims[item->Animation.AnimNumber].frameEnd)
{
SoundEffect(SFX_TR4_LARA_POLE_LOOP, &LaraItem->Pose);
SoundEffect(SFX_TR4_LARA_POLE_SLIDE_LOOP, &LaraItem->Pose);
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
Lara.Control.Rope.Flag = 0;
++Lara.Control.Rope.Segment;

View file

@ -6,6 +6,7 @@
#include "Game/effects/effects.h"
#include "Game/items.h"
#include "Sound/sound.h"
#include "Renderer/Renderer11Enums.h"
namespace TEN::Entities::Traps
{
@ -138,7 +139,7 @@ namespace TEN::Entities::Traps
AddActiveItem(dartItemNumber);
dartItem->Status = ITEM_ACTIVE;
SoundEffect(SFX_TR4_DART_SPITT, &dartItem->Pose);
SoundEffect(SFX_TR4_DART_SPIT, &dartItem->Pose);
}
}
@ -150,7 +151,7 @@ namespace TEN::Entities::Traps
if (dx < -16384 || dx > 16384 || dz < -16384 || dz > 16384)
return;
SPARKS* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = true;
@ -165,7 +166,7 @@ namespace TEN::Entities::Traps
spark->colFadeSpeed = 8;
spark->fadeToBlack = 4;
spark->transType = TransTypeEnum::COLADD;
spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 3) + 32;

View file

@ -19,6 +19,7 @@ constexpr auto FALLINGBLOCK_FALL_VELOCITY = 4;
constexpr auto FALLINGBLOCK_FALL_ROTATION_SPEED = 1;
constexpr auto FALLINGBLOCK_DELAY = 52;
constexpr auto FALLINGBLOCK_WIBBLE = 3;
constexpr auto FALLINGBLOCK_HEIGHT_TOLERANCE = 8;
constexpr auto FALLINGBLOCK_CRUMBLE_DELAY = 100;
void InitialiseFallingBlock(short itemNumber)
@ -36,7 +37,7 @@ void InitialiseFallingBlock(short itemNumber)
void FallingBlockCollision(short itemNum, ItemInfo* l, CollisionInfo* coll)
{
ItemInfo* item = &g_Level.Items[itemNum];
if (!item->ItemFlags[0] && !item->TriggerFlags && item->Pose.Position.y == l->Pose.Position.y)
if (!item->ItemFlags[0] && !item->TriggerFlags && abs(item->Pose.Position.y - l->Pose.Position.y) < FALLINGBLOCK_HEIGHT_TOLERANCE)
{
if (!((item->Pose.Position.x ^ l->Pose.Position.x) & 0xFFFFFC00) && !((l->Pose.Position.z ^ item->Pose.Position.z) & 0xFFFFFC00))
{

View file

@ -259,7 +259,7 @@ void WreckingBallControl(short itemNumber)
item->Pose.Position.y += item->Animation.VerticalVelocity;
if (item->Pose.Position.y < item2->Pose.Position.y + 1644)
{
StopSoundEffect(SFX_TR5_BASE_CLAW_WINCH_LOOP);
StopSoundEffect(SFX_TR5_BASE_CLAW_WINCH_UP_LOOP);
item->ItemFlags[0] = 1;
item->Pose.Position.y = item2->Pose.Position.y + 1644;
if (item->Animation.VerticalVelocity < -32)
@ -277,7 +277,7 @@ void WreckingBallControl(short itemNumber)
}
else if (!item->ItemFlags[0])
{
SoundEffect(SFX_TR5_BASE_CLAW_WINCH_LOOP, &item->Pose);
SoundEffect(SFX_TR5_BASE_CLAW_WINCH_UP_LOOP, &item->Pose);
}
}
item2->Pose.Position.x = item->Pose.Position.x;

View file

@ -263,17 +263,15 @@ void KeyHoleCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
}
}
if (!((TrInput & IN_ACTION || g_Gui.GetInventoryItemChosen() != NO_ITEM) &&
!BinocularRange &&
laraItem->Animation.ActiveState == LS_IDLE &&
laraItem->Animation.AnimNumber == LA_STAND_IDLE) &&
laraInfo->Control.HandStatus == HandStatus::Free &&
(!laraInfo->Control.IsMoving || laraInfo->InteractedItem != itemNumber))
{
if (keyHoleItem->ObjectNumber < ID_KEY_HOLE6)
ObjectCollision(itemNumber, laraItem, coll);
}
else
bool actionReady = (TrInput & IN_ACTION || g_Gui.GetInventoryItemChosen() != NO_ITEM);
bool laraAvailable = !BinocularRange &&
laraItem->Animation.ActiveState == LS_IDLE &&
laraItem->Animation.AnimNumber == LA_STAND_IDLE;
bool actionActive = laraInfo->Control.IsMoving && laraInfo->InteractedItem == itemNumber;
if (actionActive || (actionReady && laraAvailable))
{
if (TestLaraPosition(&KeyHoleBounds, keyHoleItem, laraItem))
{
@ -292,8 +290,13 @@ void KeyHoleCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
if (g_Gui.GetInventoryItemChosen() != keyHoleItem->ObjectNumber - (ID_KEY_HOLE1 - ID_KEY_ITEM1))
return;
laraInfo->InteractedItem = itemNumber;
}
if (laraInfo->InteractedItem != itemNumber)
return;
if (MoveLaraPosition(&KeyHolePosition, keyHoleItem, laraItem))
{
if (keyHoleItem->ObjectNumber == ID_KEY_HOLE8)
@ -319,8 +322,6 @@ void KeyHoleCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
return;
}
}
else
laraInfo->InteractedItem = itemNumber;
g_Gui.SetInventoryItemChosen(NO_ITEM);
return;
@ -332,6 +333,11 @@ void KeyHoleCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
laraInfo->Control.HandStatus = HandStatus::Free;
}
}
else
{
if (keyHoleItem->ObjectNumber < ID_KEY_HOLE6)
ObjectCollision(itemNumber, laraItem, coll);
}
return;
}

View file

@ -10,288 +10,291 @@
#include "Specific/level.h"
#include "Specific/setup.h"
BITE_INFO ApeBite = { 0, -19, 75, 15 };
#define ATTACK_DAMAGE 200
#define TOUCH (0xFF00)
#define RUN_TURN ANGLE(5.0f)
#define DISPLAY_ANGLE ANGLE(45.0f)
#define ATTACK_RANGE pow(430, 2)
#define PANIC_RANGE pow(SECTOR(2), 2)
#define JUMP_CHANCE 0xa0
#define WARNING_1_CHANCE (JUMP_CHANCE + 0xA0)
#define WARNING_2_CHANCE (WARNING_1_CHANCE + 0xA0)
#define RUNLEFT_CHANCE (WARNING_2_CHANCE + 0x110)
#define SHIFT 75
enum ApeState
namespace TEN::Entities::TR1
{
APE_STATE_NONE = 0,
APE_STATE_IDLE = 1,
APE_STATE_WALK = 2,
APE_STATE_RUN = 3,
APE_STATE_ATTACK = 4,
APE_STATE_DEATH = 5,
APE_STATE_WARNING_1 = 6,
APE_STATE_WARNING_2 = 7,
APE_STATE_RUN_LEFT = 8,
APE_STATE_RUN_RIGHT = 9,
APE_STATE_JUMP = 10,
APE_STATE_VAULT = 11
};
BITE_INFO ApeBite = { 0, -19, 75, 15 };
const std::vector<int> ApeAttackJoints = { 8, 9, 10, 11, 12, 13, 14, 15 };
// TODO
enum ApeAnim
{
APE_ANIM_DEATH = 7,
constexpr auto APE_ATTACK_DAMAGE = 200;
APE_ANIM_VAULT = 19,
};
#define RUN_TURN ANGLE(5.0f)
enum ApeFlags
{
APE_FLAG_ATTACK = 1,
APE_FLAG_TURN_LEFT = 2,
APE_FLAG_TURN_RIGHT = 4
};
#define DISPLAY_ANGLE ANGLE(45.0f)
void ApeVault(short itemNumber, short angle)
{
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
#define ATTACK_RANGE pow(430, 2)
#define PANIC_RANGE pow(SECTOR(2), 2)
if (creature->Flags & APE_FLAG_TURN_LEFT)
#define JUMP_CHANCE 0xa0
#define WARNING_1_CHANCE (JUMP_CHANCE + 0xA0)
#define WARNING_2_CHANCE (WARNING_1_CHANCE + 0xA0)
#define RUNLEFT_CHANCE (WARNING_2_CHANCE + 0x110)
#define SHIFT 75
enum ApeState
{
item->Pose.Orientation.y -= ANGLE(90.0f);
creature->Flags -= APE_FLAG_TURN_LEFT;
}
else if (item->Flags & APE_FLAG_TURN_RIGHT)
APE_STATE_NONE = 0,
APE_STATE_IDLE = 1,
APE_STATE_WALK = 2,
APE_STATE_RUN = 3,
APE_STATE_ATTACK = 4,
APE_STATE_DEATH = 5,
APE_STATE_WARNING_1 = 6,
APE_STATE_WARNING_2 = 7,
APE_STATE_RUN_LEFT = 8,
APE_STATE_RUN_RIGHT = 9,
APE_STATE_JUMP = 10,
APE_STATE_VAULT = 11
};
// TODO
enum ApeAnim
{
item->Pose.Orientation.y += ANGLE(90.0f);
creature->Flags -= APE_FLAG_TURN_RIGHT;
}
APE_ANIM_DEATH = 7,
long long xx = item->Pose.Position.z / SECTOR(1);
long long yy = item->Pose.Position.x / SECTOR(1);
long long y = item->Pose.Position.y;
APE_ANIM_VAULT = 19,
};
CreatureAnimation(itemNumber, angle, 0);
if (item->Pose.Position.y > (y - CLICK(1.5f)))
return;
long long xFloor = item->Pose.Position.z / SECTOR(1);
long long yFloor = item->Pose.Position.x / SECTOR(1);
if (xx == xFloor)
enum ApeFlags
{
if (yy == yFloor)
APE_FLAG_ATTACK = 1,
APE_FLAG_TURN_LEFT = 2,
APE_FLAG_TURN_RIGHT = 4
};
void ApeVault(short itemNumber, short angle)
{
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
if (creature->Flags & APE_FLAG_TURN_LEFT)
{
item->Pose.Orientation.y -= ANGLE(90.0f);
creature->Flags -= APE_FLAG_TURN_LEFT;
}
else if (item->Flags & APE_FLAG_TURN_RIGHT)
{
item->Pose.Orientation.y += ANGLE(90.0f);
creature->Flags -= APE_FLAG_TURN_RIGHT;
}
long long xx = item->Pose.Position.z / SECTOR(1);
long long yy = item->Pose.Position.x / SECTOR(1);
long long y = item->Pose.Position.y;
CreatureAnimation(itemNumber, angle, 0);
if (item->Pose.Position.y > (y - CLICK(1.5f)))
return;
if (yy < yFloor)
long long xFloor = item->Pose.Position.z / SECTOR(1);
long long yFloor = item->Pose.Position.x / SECTOR(1);
if (xx == xFloor)
{
item->Pose.Position.x = (yFloor * SECTOR(1)) - SHIFT;
item->Pose.Orientation.y = ANGLE(90.0f);
}
else
{
item->Pose.Position.x = (yy * SECTOR(1)) + SHIFT;
item->Pose.Orientation.y = -ANGLE(90.0f);
}
}
else if (yy == yFloor)
{
if (xx < xFloor)
{
item->Pose.Position.z = (xFloor * SECTOR(1)) - SHIFT;
item->Pose.Orientation.y = 0;
}
else
{
item->Pose.Position.z = (xx * SECTOR(1)) + SHIFT;
item->Pose.Orientation.y = -ANGLE(180.0f);
}
}
else
{
// diagonal
}
if (yy == yFloor)
return;
switch (CreatureVault(itemNumber, angle, 2, SHIFT))
{
case 2:
item->Pose.Position.y = y;
item->Animation.AnimNumber = Objects[ID_APE].animIndex + APE_ANIM_VAULT;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = APE_STATE_VAULT;
break;
default:
return;
}
}
void ApeControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creatureInfo = GetCreatureInfo(item);
short head = 0;
short angle = 0;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != APE_STATE_DEATH)
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + APE_ANIM_DEATH + (short)(GetRandomControl() / 0x4000);
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = APE_STATE_DEATH;
}
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, TIMID);
CreatureMood(item, &AI, TIMID);
angle = CreatureTurn(item, creatureInfo->MaxTurn);
if (item->HitStatus || AI.distance < PANIC_RANGE)
creatureInfo->Flags |= APE_FLAG_ATTACK;
short random;
switch (item->Animation.ActiveState)
{
case APE_STATE_IDLE:
if (creatureInfo->Flags & APE_FLAG_TURN_LEFT)
if (yy < yFloor)
{
item->Pose.Orientation.y -= ANGLE(90);
creatureInfo->Flags -= APE_FLAG_TURN_LEFT;
}
else if (item->Flags & APE_FLAG_TURN_RIGHT)
{
item->Pose.Orientation.y += ANGLE(90);
creatureInfo->Flags -= APE_FLAG_TURN_RIGHT;
}
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (AI.bite && AI.distance < ATTACK_RANGE)
item->Animation.TargetState = APE_STATE_ATTACK;
else if (!(creatureInfo->Flags & APE_FLAG_ATTACK) &&
AI.zoneNumber == AI.enemyZone && AI.ahead)
{
random = (short)(GetRandomControl() / 32);
if (random < JUMP_CHANCE)
item->Animation.TargetState = APE_STATE_JUMP;
else if (random < WARNING_1_CHANCE)
item->Animation.TargetState = APE_STATE_WARNING_1;
else if (random < WARNING_2_CHANCE)
item->Animation.TargetState = APE_STATE_WARNING_2;
else if (random < RUNLEFT_CHANCE)
{
item->Animation.TargetState = APE_STATE_RUN_LEFT;
creatureInfo->MaxTurn = 0;
}
else
{
item->Animation.TargetState = APE_STATE_RUN_RIGHT;
creatureInfo->MaxTurn = 0;
}
item->Pose.Position.x = (yFloor * SECTOR(1)) - SHIFT;
item->Pose.Orientation.y = ANGLE(90.0f);
}
else
item->Animation.TargetState = APE_STATE_RUN;
{
item->Pose.Position.x = (yy * SECTOR(1)) + SHIFT;
item->Pose.Orientation.y = -ANGLE(90.0f);
}
}
else if (yy == yFloor)
{
if (xx < xFloor)
{
item->Pose.Position.z = (xFloor * SECTOR(1)) - SHIFT;
item->Pose.Orientation.y = 0;
}
else
{
item->Pose.Position.z = (xx * SECTOR(1)) + SHIFT;
item->Pose.Orientation.y = -ANGLE(180.0f);
}
}
else
{
// diagonal
}
switch (CreatureVault(itemNumber, angle, 2, SHIFT))
{
case 2:
item->Pose.Position.y = y;
item->Animation.AnimNumber = Objects[ID_APE].animIndex + APE_ANIM_VAULT;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = APE_STATE_VAULT;
break;
case APE_STATE_RUN:
creatureInfo->MaxTurn = RUN_TURN;
if (creatureInfo->Flags == 0 &&
AI.angle > -DISPLAY_ANGLE &&
AI.angle < DISPLAY_ANGLE)
{
item->Animation.TargetState = APE_STATE_IDLE;
}
else if (AI.ahead && item->TouchBits & TOUCH)
{
item->Animation.RequiredState = APE_STATE_ATTACK;
item->Animation.TargetState = APE_STATE_IDLE;
}
else if (creatureInfo->Mood != MoodType::Escape)
{
random = (short)GetRandomControl();
if (random < JUMP_CHANCE)
{
item->Animation.RequiredState = APE_STATE_JUMP;
item->Animation.TargetState = APE_STATE_IDLE;
}
else if (random < WARNING_1_CHANCE)
{
item->Animation.RequiredState = APE_STATE_WARNING_1;
item->Animation.TargetState = APE_STATE_IDLE;
}
else if (random < WARNING_2_CHANCE)
{
item->Animation.RequiredState = APE_STATE_WARNING_2;
item->Animation.TargetState = APE_STATE_IDLE;
}
}
break;
case APE_STATE_RUN_LEFT:
if (!(creatureInfo->Flags & APE_FLAG_TURN_RIGHT))
{
item->Pose.Orientation.y -= ANGLE(90);
creatureInfo->Flags |= APE_FLAG_TURN_RIGHT;
}
item->Animation.TargetState = APE_STATE_IDLE;
break;
case APE_STATE_RUN_RIGHT:
if (!(creatureInfo->Flags & APE_FLAG_TURN_LEFT))
{
item->Pose.Orientation.y += ANGLE(90);
creatureInfo->Flags |= APE_FLAG_TURN_LEFT;
}
item->Animation.TargetState = APE_STATE_IDLE;
break;
case APE_STATE_ATTACK:
if (!item->Animation.RequiredState && item->TouchBits & TOUCH)
{
CreatureEffect(item, &ApeBite, DoBloodSplat);
item->Animation.RequiredState = APE_STATE_IDLE;
LaraItem->HitPoints -= ATTACK_DAMAGE;
LaraItem->HitStatus = true;
}
break;
default:
return;
}
}
CreatureJoint(item, 0, head);
void ApeControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
if (item->Animation.ActiveState != APE_STATE_VAULT)
ApeVault(itemNumber, angle);
else
CreatureAnimation(itemNumber, angle, 0);
auto* item = &g_Level.Items[itemNumber];
auto* creatureInfo = GetCreatureInfo(item);
short head = 0;
short angle = 0;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != APE_STATE_DEATH)
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + APE_ANIM_DEATH + (short)(GetRandomControl() / 0x4000);
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = APE_STATE_DEATH;
}
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, TIMID);
CreatureMood(item, &AI, TIMID);
angle = CreatureTurn(item, creatureInfo->MaxTurn);
if (item->HitStatus || AI.distance < PANIC_RANGE)
creatureInfo->Flags |= APE_FLAG_ATTACK;
short random;
switch (item->Animation.ActiveState)
{
case APE_STATE_IDLE:
if (creatureInfo->Flags & APE_FLAG_TURN_LEFT)
{
item->Pose.Orientation.y -= ANGLE(90);
creatureInfo->Flags -= APE_FLAG_TURN_LEFT;
}
else if (item->Flags & APE_FLAG_TURN_RIGHT)
{
item->Pose.Orientation.y += ANGLE(90);
creatureInfo->Flags -= APE_FLAG_TURN_RIGHT;
}
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (AI.bite && AI.distance < ATTACK_RANGE)
item->Animation.TargetState = APE_STATE_ATTACK;
else if (!(creatureInfo->Flags & APE_FLAG_ATTACK) &&
AI.zoneNumber == AI.enemyZone && AI.ahead)
{
random = (short)(GetRandomControl() / 32);
if (random < JUMP_CHANCE)
item->Animation.TargetState = APE_STATE_JUMP;
else if (random < WARNING_1_CHANCE)
item->Animation.TargetState = APE_STATE_WARNING_1;
else if (random < WARNING_2_CHANCE)
item->Animation.TargetState = APE_STATE_WARNING_2;
else if (random < RUNLEFT_CHANCE)
{
item->Animation.TargetState = APE_STATE_RUN_LEFT;
creatureInfo->MaxTurn = 0;
}
else
{
item->Animation.TargetState = APE_STATE_RUN_RIGHT;
creatureInfo->MaxTurn = 0;
}
}
else
item->Animation.TargetState = APE_STATE_RUN;
break;
case APE_STATE_RUN:
creatureInfo->MaxTurn = RUN_TURN;
if (creatureInfo->Flags == 0 &&
AI.angle > -DISPLAY_ANGLE &&
AI.angle < DISPLAY_ANGLE)
{
item->Animation.TargetState = APE_STATE_IDLE;
}
else if (AI.ahead && item->TestBits(JointBitType::Touch, ApeAttackJoints))
{
item->Animation.RequiredState = APE_STATE_ATTACK;
item->Animation.TargetState = APE_STATE_IDLE;
}
else if (creatureInfo->Mood != MoodType::Escape)
{
random = (short)GetRandomControl();
if (random < JUMP_CHANCE)
{
item->Animation.RequiredState = APE_STATE_JUMP;
item->Animation.TargetState = APE_STATE_IDLE;
}
else if (random < WARNING_1_CHANCE)
{
item->Animation.RequiredState = APE_STATE_WARNING_1;
item->Animation.TargetState = APE_STATE_IDLE;
}
else if (random < WARNING_2_CHANCE)
{
item->Animation.RequiredState = APE_STATE_WARNING_2;
item->Animation.TargetState = APE_STATE_IDLE;
}
}
break;
case APE_STATE_RUN_LEFT:
if (!(creatureInfo->Flags & APE_FLAG_TURN_RIGHT))
{
item->Pose.Orientation.y -= ANGLE(90);
creatureInfo->Flags |= APE_FLAG_TURN_RIGHT;
}
item->Animation.TargetState = APE_STATE_IDLE;
break;
case APE_STATE_RUN_RIGHT:
if (!(creatureInfo->Flags & APE_FLAG_TURN_LEFT))
{
item->Pose.Orientation.y += ANGLE(90);
creatureInfo->Flags |= APE_FLAG_TURN_LEFT;
}
item->Animation.TargetState = APE_STATE_IDLE;
break;
case APE_STATE_ATTACK:
if (!item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, ApeAttackJoints))
{
CreatureEffect(item, &ApeBite, DoBloodSplat);
item->Animation.RequiredState = APE_STATE_IDLE;
LaraItem->HitPoints -= APE_ATTACK_DAMAGE;
LaraItem->HitStatus = true;
}
break;
}
}
CreatureJoint(item, 0, head);
if (item->Animation.ActiveState != APE_STATE_VAULT)
ApeVault(itemNumber, angle);
else
CreatureAnimation(itemNumber, angle, 0);
}
}

View file

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

View file

@ -11,60 +11,62 @@
#include "Specific/level.h"
#include "Specific/setup.h"
BITE_INFO BearBite = { 0, 96, 335, 14 };
#define TOUCH 0x2406C
#define ROAR_CHANCE 0x50
#define REAR_CHANCE 0x300
#define DROP_CHANCE 0x600
#define REAR_RANGE pow(SECTOR(2), 2)
#define ATTACK_RANGE pow(SECTOR(1), 2)
#define PAT_RANGE pow(600, 2)
#define RUN_TURN ANGLE(5.0f)
#define WALK_TURN ANGLE(2.0f)
#define EAT_RANGE pow(CLICK(3), 2)
#define CHARGE_DAMAGE 3
#define SLAM_DAMAGE 200
#define ATTACK_DAMAGE 200
#define PAT_DAMAGE 400
enum BearState
namespace TEN::Entities::TR1
{
BEAR_STATE_STROLL = 0,
BEAR_STATE_IDLE = 1,
BEAR_STATE_WALK = 2,
BEAR_STATE_RUN = 3,
BEAR_STATE_REAR = 4,
BEAR_STATE_ROAR = 5,
BEAR_STATE_ATTACK_1 = 6,
BEAR_STATE_ATTACK_2 = 7,
BEAR_STATE_CHOMP = 8,
BEAR_STATE_DEATH = 9
};
BITE_INFO BearBite = { 0, 96, 335, 14 };
const std::vector<int> BearAttackJoints = { 2, 3, 5, 6, 14, 17 };
// TODO
enum BearAnim
{
#define ROAR_CHANCE 0x50
#define REAR_CHANCE 0x300
#define DROP_CHANCE 0x600
#define REAR_RANGE pow(SECTOR(2), 2)
#define ATTACK_RANGE pow(SECTOR(1), 2)
#define PAT_RANGE pow(600, 2)
#define RUN_TURN ANGLE(5.0f)
#define WALK_TURN ANGLE(2.0f)
#define EAT_RANGE pow(CLICK(3), 2)
#define CHARGE_DAMAGE 3
#define SLAM_DAMAGE 200
#define ATTACK_DAMAGE 200
#define PAT_DAMAGE 400
};
void BearControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0;
if (item->HitPoints <= 0)
enum BearState
{
angle = CreatureTurn(item, ANGLE(1.0f));
BEAR_STATE_STROLL = 0,
BEAR_STATE_IDLE = 1,
BEAR_STATE_WALK = 2,
BEAR_STATE_RUN = 3,
BEAR_STATE_REAR = 4,
BEAR_STATE_ROAR = 5,
BEAR_STATE_ATTACK_1 = 6,
BEAR_STATE_ATTACK_2 = 7,
BEAR_STATE_CHOMP = 8,
BEAR_STATE_DEATH = 9
};
switch (item->Animation.ActiveState)
// TODO
enum BearAnim
{
};
void BearControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0;
if (item->HitPoints <= 0)
{
angle = CreatureTurn(item, ANGLE(1.0f));
switch (item->Animation.ActiveState)
{
case BEAR_STATE_WALK:
{
item->Animation.TargetState = BEAR_STATE_REAR;
@ -90,7 +92,7 @@ void BearControl(short itemNumber)
}
case BEAR_STATE_DEATH:
{
if (creature->Flags && item->TouchBits & TOUCH)
if (creature->Flags && item->TestBits(JointBitType::Touch, BearAttackJoints))
{
creature->Flags = 0;
@ -100,157 +102,160 @@ void BearControl(short itemNumber)
break;
}
}
}
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
angle = CreatureTurn(item, creature->MaxTurn);
if (item->HitStatus)
creature->Flags = 1;
const bool laraDead = LaraItem->HitPoints <= 0;
switch (item->Animation.ActiveState)
else
{
case BEAR_STATE_IDLE:
if (laraDead)
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
angle = CreatureTurn(item, creature->MaxTurn);
if (item->HitStatus)
creature->Flags = 1;
const bool laraDead = LaraItem->HitPoints <= 0;
switch (item->Animation.ActiveState)
{
if (AI.bite && AI.distance < EAT_RANGE)
item->Animation.TargetState = BEAR_STATE_CHOMP;
else
item->Animation.TargetState = BEAR_STATE_STROLL;
}
else if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = BEAR_STATE_STROLL;
else
item->Animation.TargetState = BEAR_STATE_RUN;
break;
case BEAR_STATE_STROLL:
creature->MaxTurn = WALK_TURN;
if (laraDead && item->TouchBits & TOUCH && AI.ahead)
item->Animation.TargetState = BEAR_STATE_IDLE;
else if (creature->Mood != MoodType::Bored)
{
item->Animation.TargetState = BEAR_STATE_IDLE;
if (creature->Mood == MoodType::Escape)
item->Animation.RequiredState = BEAR_STATE_STROLL;
}
else if (GetRandomControl() < ROAR_CHANCE)
{
item->Animation.RequiredState = BEAR_STATE_ROAR;
item->Animation.TargetState = BEAR_STATE_IDLE;
}
break;
case BEAR_STATE_RUN:
creature->MaxTurn = RUN_TURN;
if (item->TouchBits & TOUCH)
{
LaraItem->HitPoints -= CHARGE_DAMAGE;
LaraItem->HitStatus = true;
}
if (creature->Mood == MoodType::Bored || laraDead)
item->Animation.TargetState = BEAR_STATE_IDLE;
else if (AI.ahead && !item->Animation.RequiredState)
{
if (!creature->Flags && AI.distance < REAR_RANGE && GetRandomControl() < REAR_CHANCE)
case BEAR_STATE_IDLE:
if (laraDead)
{
item->Animation.RequiredState = BEAR_STATE_REAR;
if (AI.bite && AI.distance < EAT_RANGE)
item->Animation.TargetState = BEAR_STATE_CHOMP;
else
item->Animation.TargetState = BEAR_STATE_STROLL;
}
else if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = BEAR_STATE_STROLL;
else
item->Animation.TargetState = BEAR_STATE_RUN;
break;
case BEAR_STATE_STROLL:
creature->MaxTurn = WALK_TURN;
if (laraDead && item->TestBits(JointBitType::Touch, BearAttackJoints) && AI.ahead)
item->Animation.TargetState = BEAR_STATE_IDLE;
else if (creature->Mood != MoodType::Bored)
{
item->Animation.TargetState = BEAR_STATE_IDLE;
if (creature->Mood == MoodType::Escape)
item->Animation.RequiredState = BEAR_STATE_STROLL;
}
else if (GetRandomControl() < ROAR_CHANCE)
{
item->Animation.RequiredState = BEAR_STATE_ROAR;
item->Animation.TargetState = BEAR_STATE_IDLE;
}
else if (AI.distance < ATTACK_RANGE)
item->Animation.TargetState = BEAR_STATE_ATTACK_1;
break;
case BEAR_STATE_RUN:
creature->MaxTurn = RUN_TURN;
if (item->TestBits(JointBitType::Touch, BearAttackJoints))
{
LaraItem->HitPoints -= CHARGE_DAMAGE;
LaraItem->HitStatus = true;
}
if (creature->Mood == MoodType::Bored || laraDead)
item->Animation.TargetState = BEAR_STATE_IDLE;
else if (AI.ahead && !item->Animation.RequiredState)
{
if (!creature->Flags && AI.distance < REAR_RANGE && GetRandomControl() < REAR_CHANCE)
{
item->Animation.RequiredState = BEAR_STATE_REAR;
item->Animation.TargetState = BEAR_STATE_IDLE;
}
else if (AI.distance < ATTACK_RANGE)
item->Animation.TargetState = BEAR_STATE_ATTACK_1;
}
break;
case BEAR_STATE_REAR:
if (creature->Flags)
{
item->Animation.RequiredState = BEAR_STATE_STROLL;
item->Animation.TargetState = BEAR_STATE_IDLE;
}
else if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (creature->Mood == MoodType::Bored || creature->Mood == MoodType::Escape)
item->Animation.TargetState = BEAR_STATE_IDLE;
else if (AI.bite && AI.distance < PAT_RANGE)
item->Animation.TargetState = BEAR_STATE_ATTACK_2;
else
item->Animation.TargetState = BEAR_STATE_WALK;
break;
case BEAR_STATE_WALK:
if (creature->Flags)
{
item->Animation.RequiredState = BEAR_STATE_STROLL;
item->Animation.TargetState = BEAR_STATE_REAR;
}
else if (AI.ahead && item->TestBits(JointBitType::Touch, BearAttackJoints))
item->Animation.TargetState = BEAR_STATE_REAR;
else if (creature->Mood == MoodType::Escape)
{
item->Animation.TargetState = BEAR_STATE_REAR;
item->Animation.RequiredState = BEAR_STATE_STROLL;
}
else if (creature->Mood == MoodType::Bored || GetRandomControl() < ROAR_CHANCE)
{
item->Animation.RequiredState = BEAR_STATE_ROAR;
item->Animation.TargetState = BEAR_STATE_REAR;
}
else if (AI.distance > REAR_RANGE || GetRandomControl() < DROP_CHANCE)
{
item->Animation.RequiredState = BEAR_STATE_IDLE;
item->Animation.TargetState = BEAR_STATE_REAR;
}
break;
case BEAR_STATE_ATTACK_2:
if (!item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, BearAttackJoints))
{
item->Animation.RequiredState = BEAR_STATE_REAR;
LaraItem->HitPoints -= PAT_DAMAGE;
LaraItem->HitStatus = true;
}
break;
case BEAR_STATE_ATTACK_1:
if (!item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, BearAttackJoints))
{
CreatureEffect(item, &BearBite, DoBloodSplat);
item->Animation.RequiredState = BEAR_STATE_IDLE;
LaraItem->HitPoints -= ATTACK_DAMAGE;
LaraItem->HitStatus = true;
}
break;
}
break;
case BEAR_STATE_REAR:
if (creature->Flags)
{
item->Animation.RequiredState = BEAR_STATE_STROLL;
item->Animation.TargetState = BEAR_STATE_IDLE;
}
else if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (creature->Mood == MoodType::Bored || creature->Mood == MoodType::Escape)
item->Animation.TargetState = BEAR_STATE_IDLE;
else if (AI.bite && AI.distance < PAT_RANGE)
item->Animation.TargetState = BEAR_STATE_ATTACK_2;
else
item->Animation.TargetState = BEAR_STATE_WALK;
break;
case BEAR_STATE_WALK:
if (creature->Flags)
{
item->Animation.RequiredState = BEAR_STATE_STROLL;
item->Animation.TargetState = BEAR_STATE_REAR;
}
else if (AI.ahead && (item->TouchBits & TOUCH))
item->Animation.TargetState = BEAR_STATE_REAR;
else if (creature->Mood == MoodType::Escape)
{
item->Animation.TargetState = BEAR_STATE_REAR;
item->Animation.RequiredState = BEAR_STATE_STROLL;
}
else if (creature->Mood == MoodType::Bored || GetRandomControl() < ROAR_CHANCE)
{
item->Animation.RequiredState = BEAR_STATE_ROAR;
item->Animation.TargetState = BEAR_STATE_REAR;
}
else if (AI.distance > REAR_RANGE || GetRandomControl() < DROP_CHANCE)
{
item->Animation.RequiredState = BEAR_STATE_IDLE;
item->Animation.TargetState = BEAR_STATE_REAR;
}
break;
case BEAR_STATE_ATTACK_2:
if (!item->Animation.RequiredState && item->TouchBits & TOUCH)
{
item->Animation.RequiredState = BEAR_STATE_REAR;
LaraItem->HitPoints -= PAT_DAMAGE;
LaraItem->HitStatus = true;
}
break;
case BEAR_STATE_ATTACK_1:
if (!item->Animation.RequiredState && (item->TouchBits & TOUCH))
{
CreatureEffect(item, &BearBite, DoBloodSplat);
item->Animation.RequiredState = BEAR_STATE_IDLE;
LaraItem->HitPoints -= ATTACK_DAMAGE;
LaraItem->HitStatus = true;
}
break;
}
}
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
}
}

View file

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

View file

@ -12,270 +12,276 @@
#include "Specific/level.h"
#include "Specific/setup.h"
static BITE_INFO BigRatBite = { 0, -11, 108, 3 };
#define BIG_RAT_RUN_TURN ANGLE(6.0f)
#define BIG_RAT_SWIM_TURN ANGLE(3.0f)
constexpr auto DEFAULT_SWIM_UPDOWN_SPEED = 32;
constexpr auto BIG_RAT_TOUCH = 0x300018f;
constexpr auto BIG_RAT_ALERT_RANGE = SQUARE(SECTOR(1) + CLICK(2));
constexpr auto BIG_RAT_VISIBILITY_RANGE = SQUARE(SECTOR(5));
constexpr auto BIG_RAT_BITE_RANGE = SQUARE(CLICK(1) + CLICK(1) / 3);
constexpr auto BIG_RAT_CHARGE_RANGE = SQUARE(SECTOR(1) / 2);
constexpr auto BIG_RAT_POSE_CHANCE = 0x100;
constexpr auto BIG_RAT_WATER_BITE_RANGE = SQUARE(CLICK(1) + CLICK(1) / 6);
constexpr auto BIG_RAT_BITE_DAMAGE = 20;
constexpr auto BIG_RAT_CHARGE_DAMAGE = 25;
enum BigRatState
namespace TEN::Entities::TR1
{
BIG_RAT_STATE_EMPTY = 0,
BIG_RAT_STATE_IDLE = 1,
BIG_RAT_STATE_CHARGE_ATTACK = 2,
BIG_RAT_STATE_RUN = 3,
BIG_RAT_STATE_BITE_ATTACK = 4,
BIG_RAT_STATE_LAND_DEATH = 5,
BIG_RAT_STATE_POSE = 6,
BIG_RAT_STATE_SWIM = 7,
BIG_RAT_STATE_SWIM_ATTACK = 8,
BIG_RAT_STATE_WATER_DEATH = 9
};
static BITE_INFO BigRatBite = { 0, -11, 108, 3 };
const std::vector<int> BigRatAttackJoints = { 0, 1, 2, 3, 7, 8, 24, 25 };
enum BigRatAnim
{
BIG_RAT_ANIM_EMPTY = 0,
BIG_RAT_ANIM_STOP_TO_RUN = 1,
BIG_RAT_ANIM_RUN = 2,
BIG_RAT_ANIM_RUN_TO_STOP = 3,
BIG_RAT_ANIM_POSE = 4,
BIG_RAT_ANIM_POSE_TO_STOP = 5,
BIG_RAT_ANIM_LAND_BITE_ATTACK = 6,
BIG_RAT_ANIM_CHARGE_ATTACK = 7,
BIG_RAT_ANIM_LAND_DEATH = 8,
BIG_RAT_ANIM_SWIM = 9,
BIG_RAT_ANIM_WATER_BITE = 10,
BIG_RAT_ANIM_WATER_DEATH = 11,
BIG_RAT_ANIM_RUN_TO_SWIM = 12,
BIG_RAT_ANIM_SWIM_TO_RUN = 13
};
#define BIG_RAT_RUN_TURN ANGLE(6.0f)
#define BIG_RAT_SWIM_TURN ANGLE(3.0f)
void InitialiseBigRat(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
InitialiseCreature(itemNumber);
constexpr auto DEFAULT_SWIM_UPDOWN_SPEED = 32;
constexpr auto BIG_RAT_ALERT_RANGE = SQUARE(SECTOR(1) + CLICK(2));
constexpr auto BIG_RAT_VISIBILITY_RANGE = SQUARE(SECTOR(5));
constexpr auto BIG_RAT_BITE_RANGE = SQUARE(CLICK(1) + CLICK(1) / 3);
constexpr auto BIG_RAT_CHARGE_RANGE = SQUARE(SECTOR(1) / 2);
constexpr auto BIG_RAT_POSE_CHANCE = 0x100;
constexpr auto BIG_RAT_WATER_BITE_RANGE = SQUARE(CLICK(1) + CLICK(1) / 6);
constexpr auto BIG_RAT_BITE_DAMAGE = 20;
constexpr auto BIG_RAT_CHARGE_DAMAGE = 25;
if (TestEnvironment(ENV_FLAG_WATER, item))
enum BigRatState
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + BIG_RAT_ANIM_SWIM;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BIG_RAT_STATE_SWIM;
item->Animation.TargetState = BIG_RAT_STATE_SWIM;
}
else
BIG_RAT_STATE_EMPTY = 0,
BIG_RAT_STATE_IDLE = 1,
BIG_RAT_STATE_CHARGE_ATTACK = 2,
BIG_RAT_STATE_RUN = 3,
BIG_RAT_STATE_BITE_ATTACK = 4,
BIG_RAT_STATE_LAND_DEATH = 5,
BIG_RAT_STATE_POSE = 6,
BIG_RAT_STATE_SWIM = 7,
BIG_RAT_STATE_SWIM_ATTACK = 8,
BIG_RAT_STATE_WATER_DEATH = 9
};
enum BigRatAnim
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + BIG_RAT_ANIM_EMPTY;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BIG_RAT_STATE_IDLE;
item->Animation.TargetState = BIG_RAT_STATE_IDLE;
}
}
BIG_RAT_ANIM_EMPTY = 0,
BIG_RAT_ANIM_STOP_TO_RUN = 1,
BIG_RAT_ANIM_RUN = 2,
BIG_RAT_ANIM_RUN_TO_STOP = 3,
BIG_RAT_ANIM_POSE = 4,
BIG_RAT_ANIM_POSE_TO_STOP = 5,
BIG_RAT_ANIM_LAND_BITE_ATTACK = 6,
BIG_RAT_ANIM_CHARGE_ATTACK = 7,
BIG_RAT_ANIM_LAND_DEATH = 8,
BIG_RAT_ANIM_SWIM = 9,
BIG_RAT_ANIM_WATER_BITE = 10,
BIG_RAT_ANIM_WATER_DEATH = 11,
BIG_RAT_ANIM_RUN_TO_SWIM = 12,
BIG_RAT_ANIM_SWIM_TO_RUN = 13
};
static bool RatIsInWater(ItemInfo* item)
{
auto* creature = GetCreatureInfo(item);
EntityStoringInfo storingInfo;
storingInfo.x = item->Pose.Position.x;
storingInfo.y = item->Pose.Position.y;
storingInfo.z = item->Pose.Position.z;
storingInfo.roomNumber = item->RoomNumber;
GetFloor(storingInfo.x, storingInfo.y, storingInfo.z, &storingInfo.roomNumber);
storingInfo.waterDepth = GetWaterSurface(storingInfo.x, storingInfo.y, storingInfo.z, storingInfo.roomNumber);
if (storingInfo.waterDepth != NO_HEIGHT)
void InitialiseBigRat(short itemNumber)
{
creature->LOT.Step = SECTOR(20);
creature->LOT.Drop = -SECTOR(20);
creature->LOT.Fly = DEFAULT_SWIM_UPDOWN_SPEED;
return true;
}
else
{
creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1);
creature->LOT.Fly = NO_FLYING;
return false;
}
}
void BigRatControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
auto* objectInfo = &Objects[item->ObjectNumber];
short head = 0;
short angle = 0;
int waterHeight = GetWaterHeight(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber);
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != BIG_RAT_STATE_LAND_DEATH &&
item->Animation.ActiveState != BIG_RAT_STATE_WATER_DEATH)
{
if (TestEnvironment(ENV_FLAG_WATER, item))
{
item->Animation.AnimNumber = objectInfo->animIndex + BIG_RAT_ANIM_WATER_DEATH;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BIG_RAT_STATE_WATER_DEATH;
item->Animation.TargetState = BIG_RAT_STATE_WATER_DEATH;
}
else
{
item->Animation.AnimNumber = objectInfo->animIndex + BIG_RAT_ANIM_LAND_DEATH;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BIG_RAT_STATE_LAND_DEATH;
item->Animation.TargetState = BIG_RAT_STATE_LAND_DEATH;
}
}
auto* item = &g_Level.Items[itemNumber];
InitialiseCreature(itemNumber);
if (TestEnvironment(ENV_FLAG_WATER, item))
CreatureFloat(itemNumber);
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, TIMID);
CreatureMood(item, &AI, TIMID);
angle = CreatureTurn(item, creature->MaxTurn);
if (item->AIBits & ALL_AIOBJ)
GetAITarget(creature);
else if (creature->HurtByLara)
creature->Enemy = LaraItem;
if ((item->HitStatus || AI.distance < BIG_RAT_ALERT_RANGE) ||
(TargetVisible(item, &AI) && AI.distance < BIG_RAT_VISIBILITY_RANGE))
{
if (!creature->Alerted)
creature->Alerted = true;
AlertAllGuards(itemNumber);
}
switch (item->Animation.ActiveState)
{
case BIG_RAT_STATE_IDLE:
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (AI.bite && AI.distance < BIG_RAT_BITE_RANGE)
item->Animation.TargetState = BIG_RAT_STATE_BITE_ATTACK;
else
item->Animation.TargetState = BIG_RAT_STATE_RUN;
break;
case BIG_RAT_STATE_RUN:
creature->MaxTurn = BIG_RAT_RUN_TURN;
if (RatIsInWater(item))
{
item->Animation.RequiredState = BIG_RAT_STATE_SWIM;
item->Animation.TargetState = BIG_RAT_STATE_SWIM;
break;
}
if (AI.ahead && (item->TouchBits & BIG_RAT_TOUCH))
item->Animation.TargetState = BIG_RAT_STATE_IDLE;
else if (AI.bite && AI.distance < BIG_RAT_CHARGE_RANGE)
item->Animation.TargetState = BIG_RAT_STATE_CHARGE_ATTACK;
else if (AI.ahead && GetRandomControl() < BIG_RAT_POSE_CHANCE)
{
item->Animation.RequiredState = BIG_RAT_STATE_POSE;
item->Animation.TargetState = BIG_RAT_STATE_IDLE;
}
break;
case BIG_RAT_STATE_BITE_ATTACK:
if (!item->Animation.RequiredState && AI.ahead && (item->TouchBits & BIG_RAT_TOUCH))
{
CreatureEffect(item, &BigRatBite, DoBloodSplat);
item->Animation.RequiredState = BIG_RAT_STATE_IDLE;
LaraItem->HitPoints -= BIG_RAT_BITE_DAMAGE;
LaraItem->HitStatus = true;
}
break;
case BIG_RAT_STATE_CHARGE_ATTACK:
if (!item->Animation.RequiredState && AI.ahead && (item->TouchBits & BIG_RAT_TOUCH))
{
CreatureEffect(item, &BigRatBite, DoBloodSplat);
item->Animation.RequiredState = BIG_RAT_STATE_RUN;
LaraItem->HitPoints -= BIG_RAT_CHARGE_DAMAGE;
LaraItem->HitStatus = true;
}
break;
case BIG_RAT_STATE_POSE:
if (creature->Mood != MoodType::Bored || GetRandomControl() < BIG_RAT_POSE_CHANCE)
item->Animation.TargetState = BIG_RAT_STATE_IDLE;
break;
case BIG_RAT_STATE_SWIM:
creature->MaxTurn = BIG_RAT_SWIM_TURN;
if (!RatIsInWater(item))
{
item->Animation.RequiredState = BIG_RAT_STATE_RUN;
item->Animation.TargetState = BIG_RAT_STATE_RUN;
break;
}
if (AI.ahead && item->TouchBits & BIG_RAT_TOUCH)
item->Animation.TargetState = BIG_RAT_STATE_SWIM_ATTACK;
break;
case BIG_RAT_STATE_SWIM_ATTACK:
if (!item->Animation.RequiredState && AI.ahead && item->TouchBits & BIG_RAT_TOUCH)
{
CreatureEffect(item, &BigRatBite, DoBloodSplat);
LaraItem->HitPoints -= BIG_RAT_BITE_DAMAGE;
LaraItem->HitStatus = true;
}
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + BIG_RAT_ANIM_SWIM;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BIG_RAT_STATE_SWIM;
item->Animation.TargetState = BIG_RAT_STATE_SWIM;
break;
}
else
{
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + BIG_RAT_ANIM_EMPTY;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BIG_RAT_STATE_IDLE;
item->Animation.TargetState = BIG_RAT_STATE_IDLE;
}
}
static bool RatIsInWater(ItemInfo* item)
{
auto* creature = GetCreatureInfo(item);
EntityStoringInfo storingInfo;
storingInfo.x = item->Pose.Position.x;
storingInfo.y = item->Pose.Position.y;
storingInfo.z = item->Pose.Position.z;
storingInfo.roomNumber = item->RoomNumber;
GetFloor(storingInfo.x, storingInfo.y, storingInfo.z, &storingInfo.roomNumber);
storingInfo.waterDepth = GetWaterSurface(storingInfo.x, storingInfo.y, storingInfo.z, storingInfo.roomNumber);
if (storingInfo.waterDepth != NO_HEIGHT)
{
creature->LOT.Step = SECTOR(20);
creature->LOT.Drop = -SECTOR(20);
creature->LOT.Fly = DEFAULT_SWIM_UPDOWN_SPEED;
return true;
}
else
{
creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1);
creature->LOT.Fly = NO_FLYING;
return false;
}
}
void BigRatControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
auto* objectInfo = &Objects[item->ObjectNumber];
short head = 0;
short angle = 0;
int waterHeight = GetWaterHeight(item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z, item->RoomNumber);
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != BIG_RAT_STATE_LAND_DEATH &&
item->Animation.ActiveState != BIG_RAT_STATE_WATER_DEATH)
{
if (TestEnvironment(ENV_FLAG_WATER, item))
{
item->Animation.AnimNumber = objectInfo->animIndex + BIG_RAT_ANIM_WATER_DEATH;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BIG_RAT_STATE_WATER_DEATH;
item->Animation.TargetState = BIG_RAT_STATE_WATER_DEATH;
}
else
{
item->Animation.AnimNumber = objectInfo->animIndex + BIG_RAT_ANIM_LAND_DEATH;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BIG_RAT_STATE_LAND_DEATH;
item->Animation.TargetState = BIG_RAT_STATE_LAND_DEATH;
}
}
if (TestEnvironment(ENV_FLAG_WATER, item))
CreatureFloat(itemNumber);
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, TIMID);
CreatureMood(item, &AI, TIMID);
angle = CreatureTurn(item, creature->MaxTurn);
if (item->AIBits & ALL_AIOBJ)
GetAITarget(creature);
else if (creature->HurtByLara)
creature->Enemy = LaraItem;
if ((item->HitStatus || AI.distance < BIG_RAT_ALERT_RANGE) ||
(TargetVisible(item, &AI) && AI.distance < BIG_RAT_VISIBILITY_RANGE))
{
if (!creature->Alerted)
creature->Alerted = true;
AlertAllGuards(itemNumber);
}
switch (item->Animation.ActiveState)
{
case BIG_RAT_STATE_IDLE:
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (AI.bite && AI.distance < BIG_RAT_BITE_RANGE)
item->Animation.TargetState = BIG_RAT_STATE_BITE_ATTACK;
else
item->Animation.TargetState = BIG_RAT_STATE_RUN;
break;
case BIG_RAT_STATE_RUN:
creature->MaxTurn = BIG_RAT_RUN_TURN;
if (RatIsInWater(item))
{
item->Animation.RequiredState = BIG_RAT_STATE_SWIM;
item->Animation.TargetState = BIG_RAT_STATE_SWIM;
break;
}
if (AI.ahead && item->TestBits(JointBitType::Touch, BigRatAttackJoints))
item->Animation.TargetState = BIG_RAT_STATE_IDLE;
else if (AI.bite && AI.distance < BIG_RAT_CHARGE_RANGE)
item->Animation.TargetState = BIG_RAT_STATE_CHARGE_ATTACK;
else if (AI.ahead && GetRandomControl() < BIG_RAT_POSE_CHANCE)
{
item->Animation.RequiredState = BIG_RAT_STATE_POSE;
item->Animation.TargetState = BIG_RAT_STATE_IDLE;
}
break;
case BIG_RAT_STATE_BITE_ATTACK:
if (!item->Animation.RequiredState && AI.ahead &&
item->TestBits(JointBitType::Touch, BigRatAttackJoints))
{
CreatureEffect(item, &BigRatBite, DoBloodSplat);
item->Animation.RequiredState = BIG_RAT_STATE_IDLE;
LaraItem->HitPoints -= BIG_RAT_BITE_DAMAGE;
LaraItem->HitStatus = true;
}
break;
case BIG_RAT_STATE_CHARGE_ATTACK:
if (!item->Animation.RequiredState && AI.ahead &&
item->TestBits(JointBitType::Touch, BigRatAttackJoints))
{
CreatureEffect(item, &BigRatBite, DoBloodSplat);
item->Animation.RequiredState = BIG_RAT_STATE_RUN;
LaraItem->HitPoints -= BIG_RAT_CHARGE_DAMAGE;
LaraItem->HitStatus = true;
}
break;
case BIG_RAT_STATE_POSE:
if (creature->Mood != MoodType::Bored || GetRandomControl() < BIG_RAT_POSE_CHANCE)
item->Animation.TargetState = BIG_RAT_STATE_IDLE;
break;
case BIG_RAT_STATE_SWIM:
creature->MaxTurn = BIG_RAT_SWIM_TURN;
if (!RatIsInWater(item))
{
item->Animation.RequiredState = BIG_RAT_STATE_RUN;
item->Animation.TargetState = BIG_RAT_STATE_RUN;
break;
}
if (AI.ahead && item->TestBits(JointBitType::Touch, BigRatAttackJoints))
item->Animation.TargetState = BIG_RAT_STATE_SWIM_ATTACK;
break;
case BIG_RAT_STATE_SWIM_ATTACK:
if (!item->Animation.RequiredState && AI.ahead &&
item->TestBits(JointBitType::Touch, BigRatAttackJoints))
{
CreatureEffect(item, &BigRatBite, DoBloodSplat);
LaraItem->HitPoints -= BIG_RAT_BITE_DAMAGE;
LaraItem->HitStatus = true;
}
item->Animation.TargetState = BIG_RAT_STATE_SWIM;
break;
}
}
}
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
if (RatIsInWater(item))
{
CreatureUnderwater(item, 0);
item->Pose.Position.y = waterHeight;
if (RatIsInWater(item))
{
CreatureUnderwater(item, 0);
item->Pose.Position.y = waterHeight;
}
else
item->Pose.Position.y = item->Floor;
}
else
item->Pose.Position.y = item->Floor;
}

View file

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

View file

@ -16,267 +16,269 @@
#include "Specific/level.h"
#include "Specific/setup.h"
BITE_INFO CentaurRocketBite = { 11, 415, 41, 13 };
BITE_INFO CentaurRearBite = { 50, 30, 0, 5 };
namespace TEN::Entities::TR1
{
BITE_INFO CentaurRocketBite = { 11, 415, 41, 13 };
BITE_INFO CentaurRearBite = { 50, 30, 0, 5 };
std::vector<int> CentaurAttackJoints = { 0, 3, 4, 7, 8, 16, 17 };
#define BOMB_SPEED 256
#define CENTAUR_TOUCH 0x30199
#define CENTAUR_TURN ANGLE(4.0f)
#define CENTAUR_REAR_CHANCE 0x60
#define CENTAUR_REAR_RANGE pow(SECTOR(1.5f), 2)
#define FLYER_PART_DAMAGE 100
#define CENTAUR_REAR_DAMAGE 200
enum CentaurState
{
CENTAUR_STATE_NONE = 0,
CENTAUR_STATE_IDLE = 1,
CENTAUR_STATE_SHOOT = 2,
CENTAUR_STATE_RUN = 3,
CENTAUR_STATE_AIM = 4,
CENTAUR_STATE_DEATH = 5,
CENTAUR_STATE_WARNING = 6
};
// TODO
enum CentaurAnim
{
CENTAUR_ANIM_DEATH = 8,
};
void ControlCentaurBomb(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
bool aboveWater = false;
Vector3Int oldPos = { item->Pose.Position.x, item->Pose.Position.y, item->Pose.Position.z };
item->Pose.Orientation.z += ANGLE(35.0f);
if (!TestEnvironment(ENV_FLAG_WATER, item->RoomNumber))
enum CentaurState
{
item->Pose.Orientation.x -= ANGLE(1.0f);
if (item->Pose.Orientation.x < -ANGLE(90.0f))
item->Pose.Orientation.x = -ANGLE(90.0f);
CENTAUR_STATE_NONE = 0,
CENTAUR_STATE_IDLE = 1,
CENTAUR_STATE_SHOOT = 2,
CENTAUR_STATE_RUN = 3,
CENTAUR_STATE_AIM = 4,
CENTAUR_STATE_DEATH = 5,
CENTAUR_STATE_WARNING = 6
};
aboveWater = true;
item->Animation.Velocity = BOMB_SPEED * phd_cos(item->Pose.Orientation.x);
item->Animation.VerticalVelocity = -BOMB_SPEED * phd_sin(item->Pose.Orientation.x);
}
else
// TODO
enum CentaurAnim
{
aboveWater = true;
item->Animation.VerticalVelocity += 3;
CENTAUR_ANIM_DEATH = 8,
};
if (item->Animation.Velocity)
void ControlCentaurBomb(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
bool aboveWater = false;
auto oldPos = item->Pose.Position;
item->Pose.Orientation.z += ANGLE(35.0f);
if (!TestEnvironment(ENV_FLAG_WATER, item->RoomNumber))
{
item->Pose.Orientation.z += ((item->Animation.Velocity / 4) + 7) * ANGLE(1.0f);
if (item->Animation.RequiredState)
item->Pose.Orientation.y += ((item->Animation.Velocity / 2) + 7) * ANGLE(1.0f);
else
item->Pose.Orientation.x += ((item->Animation.Velocity / 2) + 7) * ANGLE(1.0f);
item->Pose.Orientation.x -= ANGLE(1.0f);
if (item->Pose.Orientation.x < -ANGLE(90.0f))
item->Pose.Orientation.x = -ANGLE(90.0f);
aboveWater = true;
item->Animation.Velocity = BOMB_SPEED * phd_cos(item->Pose.Orientation.x);
item->Animation.VerticalVelocity = -BOMB_SPEED * phd_sin(item->Pose.Orientation.x);
}
}
item->Pose.Position.x += item->Animation.Velocity * phd_cos(item->Pose.Orientation.x) * phd_sin(item->Pose.Orientation.y);
item->Pose.Position.y += item->Animation.Velocity * phd_sin(-item->Pose.Orientation.x);
item->Pose.Position.z += item->Animation.Velocity * phd_cos(item->Pose.Orientation.x) * phd_cos(item->Pose.Orientation.y);
auto probe = GetCollision(item);
if (probe.Position.Floor < item->Pose.Position.y ||
probe.Position.Ceiling > item->Pose.Position.y)
{
item->Pose.Position = oldPos;
if (TestEnvironment(ENV_FLAG_WATER, item->RoomNumber))
TriggerUnderwaterExplosion(item, 0);
else
{
item->Pose.Position.y -= CLICK(0.5f);
TriggerShockwave(&item->Pose, 48, 304, 96, 0, 96, 128, 24, 0, 0);
aboveWater = true;
item->Animation.VerticalVelocity += 3;
TriggerExplosionSparks(oldPos.x, oldPos.y, oldPos.z, 3, -2, 0, item->RoomNumber);
for (int x = 0; x < 2; x++)
TriggerExplosionSparks(oldPos.x, oldPos.y, oldPos.z, 3, -1, 0, item->RoomNumber);
}
return;
}
if (item->RoomNumber != probe.RoomNumber)
ItemNewRoom(itemNumber, probe.RoomNumber);
if (TestEnvironment(ENV_FLAG_WATER, item->RoomNumber) && aboveWater)
SetupRipple(item->Pose.Position.x, g_Level.Rooms[item->RoomNumber].minfloor, item->Pose.Position.z, (GetRandomControl() & 7) + 8, 0);
GetCollidedObjects(item, HARPOON_HIT_RADIUS, true, &CollidedItems[0], &CollidedMeshes[0], 0);
if (!CollidedItems[0] && !CollidedMeshes[0])
return;
if (CollidedItems[0])
{
auto* currentItem = CollidedItems[0];
int k = 0;
do
{
auto* currentObject = &Objects[currentItem->ObjectNumber];
if (currentItem->Status == ITEM_ACTIVE &&
currentObject->intelligent && !currentObject->undead &&
currentObject->collision)
if (item->Animation.Velocity)
{
DoExplosiveDamageOnBaddy(LaraItem, currentItem, item, LaraWeaponType::Crossbow);
item->Pose.Orientation.z += ((item->Animation.Velocity / 4) + 7) * ANGLE(1.0f);
if (item->Animation.RequiredState)
item->Pose.Orientation.y += ((item->Animation.Velocity / 2) + 7) * ANGLE(1.0f);
else
item->Pose.Orientation.x += ((item->Animation.Velocity / 2) + 7) * ANGLE(1.0f);
}
k++;
currentItem = CollidedItems[k];
} while (currentItem);
}
}
static void RocketGun(ItemInfo* centaurItem)
{
short itemNumber;
itemNumber = CreateItem();
if (itemNumber != NO_ITEM)
{
auto* projectileItem = &g_Level.Items[itemNumber];
projectileItem->ObjectNumber = ID_PROJ_BOMB;
projectileItem->Shade = 16 * 256;
projectileItem->RoomNumber = centaurItem->RoomNumber;
auto pos = Vector3Int(11, 415, 41);
GetJointAbsPosition(centaurItem, &pos, 13);
projectileItem->Pose.Position = pos;
InitialiseItem(itemNumber);
projectileItem->Pose.Orientation.x = 0;
projectileItem->Pose.Orientation.y = centaurItem->Pose.Orientation.y;
projectileItem->Pose.Orientation.z = 0;
projectileItem->Animation.Velocity = BOMB_SPEED * phd_cos(projectileItem->Pose.Orientation.x);
projectileItem->Animation.VerticalVelocity = -BOMB_SPEED * phd_cos(projectileItem->Pose.Orientation.x);
projectileItem->ItemFlags[0] = 1;
AddActiveItem(itemNumber);
}
}
void CentaurControl(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
if (!CreatureActive(itemNumber))
return;
short head = 0;
short angle = 0;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != CENTAUR_STATE_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
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
TranslateItem(item, item->Pose.Orientation, item->Animation.Velocity);
CreatureMood(item, &AI, VIOLENT);
auto probe = GetCollision(item);
angle = CreatureTurn(item, CENTAUR_TURN);
switch (item->Animation.ActiveState)
if (probe.Position.Floor < item->Pose.Position.y ||
probe.Position.Ceiling > item->Pose.Position.y)
{
case CENTAUR_STATE_IDLE:
CreatureJoint(item, 17, 0);
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (AI.bite && AI.distance < CENTAUR_REAR_RANGE)
item->Animation.TargetState = CENTAUR_STATE_RUN;
else if (Targetable(item, &AI))
item->Animation.TargetState = CENTAUR_STATE_AIM;
item->Pose.Position = oldPos;
if (TestEnvironment(ENV_FLAG_WATER, item->RoomNumber))
TriggerUnderwaterExplosion(item, 0);
else
item->Animation.TargetState = CENTAUR_STATE_RUN;
{
item->Pose.Position.y -= CLICK(0.5f);
TriggerShockwave(&item->Pose, 48, 304, 96, 0, 96, 128, 24, 0, 0);
break;
case CENTAUR_STATE_RUN:
if (AI.bite && AI.distance < CENTAUR_REAR_RANGE)
{
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
item->Animation.TargetState = CENTAUR_STATE_IDLE;
}
else if (Targetable(item, &AI))
{
item->Animation.RequiredState = CENTAUR_STATE_AIM;
item->Animation.TargetState = CENTAUR_STATE_IDLE;
}
else if (GetRandomControl() < CENTAUR_REAR_CHANCE)
{
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
item->Animation.TargetState = CENTAUR_STATE_IDLE;
TriggerExplosionSparks(oldPos.x, oldPos.y, oldPos.z, 3, -2, 0, item->RoomNumber);
for (int x = 0; x < 2; x++)
TriggerExplosionSparks(oldPos.x, oldPos.y, oldPos.z, 3, -1, 0, item->RoomNumber);
}
break;
return;
}
case CENTAUR_STATE_AIM:
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (Targetable(item, &AI))
item->Animation.TargetState = CENTAUR_STATE_SHOOT;
else
item->Animation.TargetState = CENTAUR_STATE_IDLE;
if (item->RoomNumber != probe.RoomNumber)
ItemNewRoom(itemNumber, probe.RoomNumber);
break;
if (TestEnvironment(ENV_FLAG_WATER, item->RoomNumber) && aboveWater)
SetupRipple(item->Pose.Position.x, g_Level.Rooms[item->RoomNumber].minfloor, item->Pose.Position.z, (GetRandomControl() & 7) + 8, 0);
case CENTAUR_STATE_SHOOT:
if (!item->Animation.RequiredState)
GetCollidedObjects(item, HARPOON_HIT_RADIUS, true, &CollidedItems[0], &CollidedMeshes[0], 0);
if (!CollidedItems[0] && !CollidedMeshes[0])
return;
if (CollidedItems[0])
{
auto* currentItem = CollidedItems[0];
int k = 0;
do
{
item->Animation.RequiredState = CENTAUR_STATE_AIM;
RocketGun(item);
}
auto* currentObject = &Objects[currentItem->ObjectNumber];
break;
if (currentItem->Status == ITEM_ACTIVE &&
currentObject->intelligent && !currentObject->undead &&
currentObject->collision)
{
DoExplosiveDamageOnBaddy(LaraItem, currentItem, item, LaraWeaponType::Crossbow);
}
case CENTAUR_STATE_WARNING:
if (!item->Animation.RequiredState && item->TouchBits & CENTAUR_TOUCH)
{
CreatureEffect(item, &CentaurRearBite, DoBloodSplat);
item->Animation.RequiredState = CENTAUR_STATE_IDLE;
k++;
currentItem = CollidedItems[k];
LaraItem->HitPoints -= CENTAUR_REAR_DAMAGE;
LaraItem->HitStatus = 1;
}
break;
} while (currentItem);
}
}
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
if (item->Status == ITEM_DEACTIVATED)
static void RocketGun(ItemInfo* centaurItem)
{
SoundEffect(SFX_TR1_ATLANTEAN_DEATH, &item->Pose);
ExplodingDeath(itemNumber, 0xffffffff, FLYER_PART_DAMAGE);
KillItem(itemNumber);
item->Status = ITEM_DEACTIVATED;
short itemNumber;
itemNumber = CreateItem();
if (itemNumber != NO_ITEM)
{
auto* projectileItem = &g_Level.Items[itemNumber];
projectileItem->ObjectNumber = ID_PROJ_BOMB;
projectileItem->Shade = 16 * 256;
projectileItem->RoomNumber = centaurItem->RoomNumber;
auto pos = Vector3Int(11, 415, 41);
GetJointAbsPosition(centaurItem, &pos, 13);
projectileItem->Pose.Position = pos;
InitialiseItem(itemNumber);
projectileItem->Pose.Orientation.x = 0;
projectileItem->Pose.Orientation.y = centaurItem->Pose.Orientation.y;
projectileItem->Pose.Orientation.z = 0;
projectileItem->Animation.Velocity = BOMB_SPEED * phd_cos(projectileItem->Pose.Orientation.x);
projectileItem->Animation.VerticalVelocity = -BOMB_SPEED * phd_cos(projectileItem->Pose.Orientation.x);
projectileItem->ItemFlags[0] = 1;
AddActiveItem(itemNumber);
}
}
void CentaurControl(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
if (!CreatureActive(itemNumber))
return;
short head = 0;
short angle = 0;
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != CENTAUR_STATE_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
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
CreatureMood(item, &AI, VIOLENT);
angle = CreatureTurn(item, CENTAUR_TURN);
switch (item->Animation.ActiveState)
{
case CENTAUR_STATE_IDLE:
CreatureJoint(item, 17, 0);
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (AI.bite && AI.distance < CENTAUR_REAR_RANGE)
item->Animation.TargetState = CENTAUR_STATE_RUN;
else if (Targetable(item, &AI))
item->Animation.TargetState = CENTAUR_STATE_AIM;
else
item->Animation.TargetState = CENTAUR_STATE_RUN;
break;
case CENTAUR_STATE_RUN:
if (AI.bite && AI.distance < CENTAUR_REAR_RANGE)
{
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
item->Animation.TargetState = CENTAUR_STATE_IDLE;
}
else if (Targetable(item, &AI))
{
item->Animation.RequiredState = CENTAUR_STATE_AIM;
item->Animation.TargetState = CENTAUR_STATE_IDLE;
}
else if (GetRandomControl() < CENTAUR_REAR_CHANCE)
{
item->Animation.RequiredState = CENTAUR_STATE_WARNING;
item->Animation.TargetState = CENTAUR_STATE_IDLE;
}
break;
case CENTAUR_STATE_AIM:
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (Targetable(item, &AI))
item->Animation.TargetState = CENTAUR_STATE_SHOOT;
else
item->Animation.TargetState = CENTAUR_STATE_IDLE;
break;
case CENTAUR_STATE_SHOOT:
if (!item->Animation.RequiredState)
{
item->Animation.RequiredState = CENTAUR_STATE_AIM;
RocketGun(item);
}
break;
case CENTAUR_STATE_WARNING:
if (!item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, CentaurAttackJoints))
{
CreatureEffect(item, &CentaurRearBite, DoBloodSplat);
item->Animation.RequiredState = CENTAUR_STATE_IDLE;
LaraItem->HitPoints -= CENTAUR_REAR_DAMAGE;
LaraItem->HitStatus = 1;
}
break;
}
}
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0);
if (item->Status == ITEM_DEACTIVATED)
{
SoundEffect(SFX_TR1_ATLANTEAN_DEATH, &item->Pose);
ExplodingDeath(itemNumber, ALL_JOINT_BITS, FLYER_PART_DAMAGE);
KillItem(itemNumber);
item->Status = ITEM_DEACTIVATED;
}
}
}

View file

@ -1,4 +1,7 @@
#pragma once
void CentaurControl(short itemNumber);
void ControlCentaurBomb(short itemNumber);
namespace TEN::Entities::TR1
{
void CentaurControl(short itemNumber);
void ControlCentaurBomb(short itemNumber);
}

View file

@ -14,129 +14,132 @@
// - Bacon Lara cannot be targeted.
// - Bacon Lara cannot move like Lara.
// Original:
void InitialiseDoppelganger(short itemNumber)
namespace TEN::Entities::TR1
{
ClearItem(itemNumber);
}
ItemInfo* FindReference(ItemInfo* item, short objectNumber)
{
bool found = false;
int itemNumber;
for (int i = 0; i < g_Level.NumItems; i++)
// Original:
void InitialiseDoppelganger(short itemNumber)
{
auto* currentItem = &g_Level.Items[i];
if (currentItem->ObjectNumber == objectNumber && currentItem->RoomNumber == item->RoomNumber)
{
itemNumber = i;
found = true;
}
ClearItem(itemNumber);
}
if (!found)
itemNumber = NO_ITEM;
return (itemNumber == NO_ITEM ? NULL : &g_Level.Items[itemNumber]);
}
static short GetWeaponDamage(LaraWeaponType weaponType)
{
return short(Weapons[(int)weaponType].Damage) * 25;
}
void DoppelgangerControl(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
if (item->HitPoints < 1000)
ItemInfo* FindReference(ItemInfo* item, short objectNumber)
{
item->HitPoints = 1000;
LaraItem->HitPoints -= GetWeaponDamage(Lara.Control.Weapon.GunType);
bool found = false;
int itemNumber;
for (int i = 0; i < g_Level.NumItems; i++)
{
auto* currentItem = &g_Level.Items[i];
if (currentItem->ObjectNumber == objectNumber && currentItem->RoomNumber == item->RoomNumber)
{
itemNumber = i;
found = true;
}
}
if (!found)
itemNumber = NO_ITEM;
return (itemNumber == NO_ITEM ? NULL : &g_Level.Items[itemNumber]);
}
auto* reference = FindReference(item, ID_BACON_REFERENCE);
if (item->Data == NULL)
static short GetWeaponDamage(LaraWeaponType weaponType)
{
Vector3Int pos;
if (reference == nullptr)
{
pos.x = item->Pose.Position.x;
pos.y = LaraItem->Pose.Position.y;
pos.z = item->Pose.Position.z;
}
else
{
pos.x = 2 * reference->Pose.Position.x - LaraItem->Pose.Position.x;
pos.y = LaraItem->Pose.Position.y;
pos.z = 2 * reference->Pose.Position.z - LaraItem->Pose.Position.z;
}
// Get floor heights for comparison.
item->Floor = GetCollision(item).Position.Floor;
int laraFloorHeight = GetCollision(LaraItem).Position.Floor;
// Animate bacon Lara, mirroring Lara's position.
item->Animation.FrameNumber = LaraItem->Animation.FrameNumber;
item->Animation.AnimNumber = LaraItem->Animation.AnimNumber;
item->Pose.Position.x = pos.x;
item->Pose.Position.y = pos.y;
item->Pose.Position.z = pos.z;
item->Pose.Orientation.x = LaraItem->Pose.Orientation.x;
item->Pose.Orientation.y = LaraItem->Pose.Orientation.y - ANGLE(180.0f);
item->Pose.Orientation.z = LaraItem->Pose.Orientation.z;
ItemNewRoom(itemNumber, LaraItem->RoomNumber);
// Compare floor heights.
if (item->Floor >= laraFloorHeight + SECTOR(1) + 1 && // Add 1 to avoid bacon Lara dying when exiting water.
!LaraItem->Animation.Airborne)
{
SetAnimation(item, LA_JUMP_WALL_SMASH_START);
item->Animation.Velocity = 0;
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = true;
item->Data = -1;
item->Pose.Position.y += 50;
}
return short(Weapons[(int)weaponType].Damage) * 25;
}
if (item->Data)
void DoppelgangerControl(short itemNumber)
{
AnimateItem(item);
TestTriggers(item, true);
auto* item = &g_Level.Items[itemNumber];
item->Floor = GetCollision(item).Position.Floor;
if (item->Pose.Position.y >= item->Floor)
if (item->HitPoints < LARA_HEALTH_MAX)
{
item->Pose.Position.y = item->Floor;
item->HitPoints = LARA_HEALTH_MAX;
LaraItem->HitPoints -= GetWeaponDamage(Lara.Control.Weapon.GunType);
}
auto* reference = FindReference(item, ID_BACON_REFERENCE);
if (item->Data == NULL)
{
Vector3Int pos;
if (reference == nullptr)
{
pos.x = item->Pose.Position.x;
pos.y = LaraItem->Pose.Position.y;
pos.z = item->Pose.Position.z;
}
else
{
pos.x = 2 * reference->Pose.Position.x - LaraItem->Pose.Position.x;
pos.y = LaraItem->Pose.Position.y;
pos.z = 2 * reference->Pose.Position.z - LaraItem->Pose.Position.z;
}
// Get floor heights for comparison.
item->Floor = GetCollision(item).Position.Floor;
int laraFloorHeight = GetCollision(LaraItem).Position.Floor;
// Animate bacon Lara, mirroring Lara's position.
item->Animation.FrameNumber = LaraItem->Animation.FrameNumber;
item->Animation.AnimNumber = LaraItem->Animation.AnimNumber;
item->Pose.Position.x = pos.x;
item->Pose.Position.y = pos.y;
item->Pose.Position.z = pos.z;
item->Pose.Orientation.x = LaraItem->Pose.Orientation.x;
item->Pose.Orientation.y = LaraItem->Pose.Orientation.y - ANGLE(180.0f);
item->Pose.Orientation.z = LaraItem->Pose.Orientation.z;
ItemNewRoom(itemNumber, LaraItem->RoomNumber);
// Compare floor heights.
if (item->Floor >= laraFloorHeight + SECTOR(1) + 1 && // Add 1 to avoid bacon Lara dying when exiting water.
!LaraItem->Animation.Airborne)
{
SetAnimation(item, LA_JUMP_WALL_SMASH_START);
item->Animation.Velocity = 0;
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = true;
item->Data = -1;
item->Pose.Position.y += 50;
}
}
if (item->Data)
{
AnimateItem(item);
TestTriggers(item, true);
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = false;
item->Animation.TargetState = LS_DEATH;
item->Animation.RequiredState = LS_DEATH;
item->Floor = GetCollision(item).Position.Floor;
if (item->Pose.Position.y >= item->Floor)
{
item->Pose.Position.y = item->Floor;
TestTriggers(item, true);
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = false;
item->Animation.TargetState = LS_DEATH;
item->Animation.RequiredState = LS_DEATH;
}
}
}
}
// TODO: DrawLara not exist ! use Renderer11.cpp DrawLara instead or create DrawLara() function with old behaviour.
void DrawEvilLara(ItemInfo* item)
{
/*
short* meshstore[15];
short** meshpp;
int i;
meshpp = &Meshes[Objects[item->objectNumber].meshIndex]; // Save Laras Mesh Pointers
for (i = 0; i < 15; i++)
// TODO: DrawLara not exist ! use Renderer11.cpp DrawLara instead or create DrawLara() function with old behaviour.
void DrawEvilLara(ItemInfo* item)
{
meshstore[i] = Lara.meshPtrs[i];
Lara.meshPtrs[i] = *(meshpp++);
/*
short* meshstore[15];
short** meshpp;
int i;
meshpp = &Meshes[Objects[item->objectNumber].meshIndex]; // Save Laras Mesh Pointers
for (i = 0; i < 15; i++)
{
meshstore[i] = Lara.meshPtrs[i];
Lara.meshPtrs[i] = *(meshpp++);
}
DrawLara(item);
for (i = 0; i < 15; i++)
Lara.meshPtrs[i] = meshstore[i];*/
}
DrawLara(item);
for (i = 0; i < 15; i++)
Lara.meshPtrs[i] = meshstore[i];*/
}

View file

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

View file

@ -14,239 +14,243 @@
#include "Specific/level.h"
#include "Specific/setup.h"
#define MUTANT_NEED_TURN ANGLE(45.0f)
#define MUTANT_TURN ANGLE(3.0f)
#define MUTANT_ATTACK_RANGE pow(2600, 2)
#define MUTANT_CLOSE_RANGE pow(2250, 2)
#define MUTANT_ATTACK_1_CHANCE 11000
#define MUTANT_ATTACK_2_CHANCE 22000
#define MUTANT_TLEFT 0x7ff0
#define MUTANT_TRIGHT 0x3ff8000
#define MUTANT_TOUCH (MUTANT_TLEFT | MUTANT_TRIGHT)
#define MUTANT_PART_DAMAGE 250
#define MUTANT_ATTACK_DAMAGE 500
#define MUTANT_TOUCH_DAMAGE 5
#define LARA_GIANT_MUTANT_DEATH 6
enum GiantMutantState
namespace TEN::Entities::TR1
{
MUTANT_STATE_NONE = 0,
MUTANT_STATE_IDLE = 1,
MUTANT_STATE_TURN_LEFT = 2,
MUTANT_STATE_TURN_RIGHT = 3,
MUTANT_STATE_ATTACK_1 = 4,
MUTANT_STATE_ATTACK_2 = 5,
MUTANT_STATE_ATTACK_3 = 6,
MUTANT_STATE_FORWARD = 7,
MUTANT_STATE_SET = 8,
MUTANT_STATE_FALL = 9,
MUTANT_STATE_DEATH = 10,
MUTANT_STATE_KILL = 11
};
const std::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 std::vector<int> MutantAttackLeftJoints = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
const std::vector<int> MutantAttackRightJoints = { 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
// TODO
enum GianMutantAnim
{
MUTANT_ANIM_DEATH = 13,
};
#define MUTANT_NEED_TURN ANGLE(45.0f)
#define MUTANT_TURN ANGLE(3.0f)
#define MUTANT_ATTACK_RANGE pow(2600, 2)
#define MUTANT_CLOSE_RANGE pow(2250, 2)
#define MUTANT_ATTACK_1_CHANCE 11000
#define MUTANT_ATTACK_2_CHANCE 22000
#define MUTANT_PART_DAMAGE 250
#define MUTANT_ATTACK_DAMAGE 500
#define MUTANT_TOUCH_DAMAGE 5
#define LARA_GIANT_MUTANT_DEATH 6
void GiantMutantControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0;
if (item->HitPoints <= 0)
enum GiantMutantState
{
if (item->Animation.ActiveState != MUTANT_STATE_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
MUTANT_STATE_NONE = 0,
MUTANT_STATE_IDLE = 1,
MUTANT_STATE_TURN_LEFT = 2,
MUTANT_STATE_TURN_RIGHT = 3,
MUTANT_STATE_ATTACK_1 = 4,
MUTANT_STATE_ATTACK_2 = 5,
MUTANT_STATE_ATTACK_3 = 6,
MUTANT_STATE_FORWARD = 7,
MUTANT_STATE_SET = 8,
MUTANT_STATE_FALL = 9,
MUTANT_STATE_DEATH = 10,
MUTANT_STATE_KILL = 11
};
// TODO
enum GianMutantAnim
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
MUTANT_ANIM_DEATH = 13,
};
if (AI.ahead)
head = AI.angle;
void GiantMutantControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
angle = (short)phd_atan(creature->Target.z - item->Pose.Position.z, creature->Target.x - item->Pose.Position.x) - item->Pose.Orientation.y;
short head = 0;
short angle = 0;
if (item->TouchBits)
if (item->HitPoints <= 0)
{
LaraItem->HitPoints -= MUTANT_TOUCH_DAMAGE;
LaraItem->HitStatus = true;
if (item->Animation.ActiveState != MUTANT_STATE_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;
}
}
switch (item->Animation.ActiveState)
else
{
case MUTANT_STATE_SET:
item->Animation.TargetState = MUTANT_STATE_FALL;
item->Animation.Airborne = true;
break;
AI_INFO AI;
CreatureAIInfo(item, &AI);
case MUTANT_STATE_IDLE:
if (LaraItem->HitPoints <= 0)
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
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)
{
LaraItem->HitPoints -= MUTANT_TOUCH_DAMAGE;
LaraItem->HitStatus = true;
}
switch (item->Animation.ActiveState)
{
case MUTANT_STATE_SET:
item->Animation.TargetState = MUTANT_STATE_FALL;
item->Animation.Airborne = true;
break;
creature->Flags = 0;
case MUTANT_STATE_IDLE:
if (LaraItem->HitPoints <= 0)
break;
if (angle > MUTANT_NEED_TURN)
item->Animation.TargetState = MUTANT_STATE_TURN_RIGHT;
else if (angle < -MUTANT_NEED_TURN)
item->Animation.TargetState = MUTANT_STATE_TURN_LEFT;
else if (AI.distance < MUTANT_ATTACK_RANGE)
{
if (LaraItem->HitPoints <= MUTANT_ATTACK_DAMAGE)
creature->Flags = 0;
if (angle > MUTANT_NEED_TURN)
item->Animation.TargetState = MUTANT_STATE_TURN_RIGHT;
else if (angle < -MUTANT_NEED_TURN)
item->Animation.TargetState = MUTANT_STATE_TURN_LEFT;
else if (AI.distance < MUTANT_ATTACK_RANGE)
{
if (AI.distance < MUTANT_CLOSE_RANGE)
item->Animation.TargetState = MUTANT_STATE_ATTACK_3;
if (LaraItem->HitPoints <= MUTANT_ATTACK_DAMAGE)
{
if (AI.distance < MUTANT_CLOSE_RANGE)
item->Animation.TargetState = MUTANT_STATE_ATTACK_3;
else
item->Animation.TargetState = MUTANT_STATE_FORWARD;
}
else if (GetRandomControl() < 0x4000)
item->Animation.TargetState = MUTANT_STATE_ATTACK_1;
else
item->Animation.TargetState = MUTANT_STATE_FORWARD;
item->Animation.TargetState = MUTANT_STATE_ATTACK_2;
}
else if (GetRandomControl() < 0x4000)
item->Animation.TargetState = MUTANT_STATE_ATTACK_1;
else
item->Animation.TargetState = MUTANT_STATE_ATTACK_2;
}
else
item->Animation.TargetState = MUTANT_STATE_FORWARD;
item->Animation.TargetState = MUTANT_STATE_FORWARD;
break;
break;
case MUTANT_STATE_FORWARD:
if (angle < -MUTANT_TURN)
item->Animation.TargetState -= MUTANT_TURN;
else if (angle > MUTANT_TURN)
item->Animation.TargetState += MUTANT_TURN;
else
item->Animation.TargetState += angle;
case MUTANT_STATE_FORWARD:
if (angle < -MUTANT_TURN)
item->Animation.TargetState -= MUTANT_TURN;
else if (angle > MUTANT_TURN)
item->Animation.TargetState += MUTANT_TURN;
else
item->Animation.TargetState += angle;
if (angle > MUTANT_NEED_TURN || angle < -MUTANT_NEED_TURN)
item->Animation.TargetState = MUTANT_STATE_IDLE;
else if (AI.distance < MUTANT_ATTACK_RANGE)
item->Animation.TargetState = MUTANT_STATE_IDLE;
if (angle > MUTANT_NEED_TURN || angle < -MUTANT_NEED_TURN)
item->Animation.TargetState = MUTANT_STATE_IDLE;
else if (AI.distance < MUTANT_ATTACK_RANGE)
item->Animation.TargetState = MUTANT_STATE_IDLE;
break;
break;
case MUTANT_STATE_TURN_RIGHT:
if (!creature->Flags)
creature->Flags = item->Animation.FrameNumber;
else if (item->Animation.FrameNumber - creature->Flags > 16 &&
item->Animation.FrameNumber - creature->Flags < 23)
{
item->Pose.Orientation.y += ANGLE(14.0f);
}
case MUTANT_STATE_TURN_RIGHT:
if (!creature->Flags)
creature->Flags = item->Animation.FrameNumber;
else if (item->Animation.FrameNumber - creature->Flags > 16 &&
item->Animation.FrameNumber - creature->Flags < 23)
{
item->Pose.Orientation.y += ANGLE(14.0f);
}
if (angle < MUTANT_NEED_TURN)
item->Animation.TargetState = MUTANT_STATE_IDLE;
if (angle < MUTANT_NEED_TURN)
item->Animation.TargetState = MUTANT_STATE_IDLE;
break;
break;
case MUTANT_STATE_TURN_LEFT:
if (!creature->Flags)
creature->Flags = item->Animation.FrameNumber;
else if (item->Animation.FrameNumber - creature->Flags > 13 &&
item->Animation.FrameNumber - creature->Flags < 23)
{
item->Pose.Orientation.y -= ANGLE(9.0f);
}
case MUTANT_STATE_TURN_LEFT:
if (!creature->Flags)
creature->Flags = item->Animation.FrameNumber;
else if (item->Animation.FrameNumber - creature->Flags > 13 &&
item->Animation.FrameNumber - creature->Flags < 23)
{
item->Pose.Orientation.y -= ANGLE(9.0f);
}
if (angle > -MUTANT_NEED_TURN)
item->Animation.TargetState = MUTANT_STATE_IDLE;
if (angle > -MUTANT_NEED_TURN)
item->Animation.TargetState = MUTANT_STATE_IDLE;
break;
break;
case MUTANT_STATE_ATTACK_1:
if (!creature->Flags && item->TouchBits & MUTANT_TRIGHT)
{
creature->Flags = 1;
case MUTANT_STATE_ATTACK_1:
if (!creature->Flags && item->TestBits(JointBitType::Touch, MutantAttackRightJoints))
{
creature->Flags = 1;
LaraItem->HitPoints -= MUTANT_ATTACK_DAMAGE;
LaraItem->HitStatus = true;
}
LaraItem->HitPoints -= MUTANT_ATTACK_DAMAGE;
LaraItem->HitStatus = true;
}
break;
break;
case MUTANT_STATE_ATTACK_2:
if (!creature->Flags && item->TouchBits & MUTANT_TOUCH)
{
creature->Flags = 1;
case MUTANT_STATE_ATTACK_2:
if (!creature->Flags && item->TestBits(JointBitType::Touch, MutantAttackJoints))
{
creature->Flags = 1;
LaraItem->HitPoints -= MUTANT_ATTACK_DAMAGE;
LaraItem->HitStatus = true;
}
LaraItem->HitPoints -= MUTANT_ATTACK_DAMAGE;
LaraItem->HitStatus = true;
}
break;
break;
case MUTANT_STATE_ATTACK_3:
if (item->TouchBits & MUTANT_TRIGHT || LaraItem->HitPoints <= 0)
{
item->Animation.TargetState = MUTANT_STATE_KILL;
case MUTANT_STATE_ATTACK_3:
if (item->TestBits(JointBitType::Touch, MutantAttackRightJoints) || LaraItem->HitPoints <= 0)
{
item->Animation.TargetState = MUTANT_STATE_KILL;
Camera.targetDistance = SECTOR(2);
Camera.flags = CF_FOLLOW_CENTER;
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.ActiveState = LaraItem->Animation.TargetState = 46;
LaraItem->RoomNumber = item->RoomNumber;
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.Airborne = false;
LaraItem->HitPoints = -1;
Lara.Air = -1;
Lara.Control.HandStatus = HandStatus::Busy;
Lara.Control.Weapon.GunType = LaraWeaponType::None;
}
break;
case MUTANT_STATE_KILL:
Camera.targetDistance = SECTOR(2);
Camera.flags = CF_FOLLOW_CENTER;
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.ActiveState = LaraItem->Animation.TargetState = 46;
LaraItem->RoomNumber = item->RoomNumber;
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.Airborne = false;
LaraItem->HitPoints = -1;
Lara.Air = -1;
Lara.Control.HandStatus = HandStatus::Busy;
Lara.Control.Weapon.GunType = LaraWeaponType::None;
break;
}
break;
case MUTANT_STATE_KILL:
Camera.targetDistance = SECTOR(2);
Camera.flags = CF_FOLLOW_CENTER;
break;
}
}
CreatureJoint(item, 0, head);
CreatureJoint(item, 0, head);
if (item->Animation.ActiveState == MUTANT_STATE_FALL)
{
AnimateItem(item);
if (item->Pose.Position.y > item->Floor)
if (item->Animation.ActiveState == MUTANT_STATE_FALL)
{
item->Animation.TargetState = MUTANT_STATE_IDLE;
item->Animation.Airborne = false;
item->Pose.Position.y = item->Floor;
Camera.bounce = 500;
AnimateItem(item);
if (item->Pose.Position.y > item->Floor)
{
item->Animation.TargetState = MUTANT_STATE_IDLE;
item->Animation.Airborne = false;
item->Pose.Position.y = item->Floor;
Camera.bounce = 500;
}
}
}
else
CreatureAnimation(itemNumber, 0, 0);
else
CreatureAnimation(itemNumber, 0, 0);
if (item->Status == ITEM_DEACTIVATED)
{
SoundEffect(SFX_TR1_ATLANTEAN_DEATH, &item->Pose);
ExplodingDeath(itemNumber, UINT_MAX, MUTANT_PART_DAMAGE);
if (item->Status == ITEM_DEACTIVATED)
{
SoundEffect(SFX_TR1_ATLANTEAN_DEATH, &item->Pose);
ExplodingDeath(itemNumber, ALL_JOINT_BITS, MUTANT_PART_DAMAGE);
TestTriggers(item, true);
TestTriggers(item, true);
KillItem(itemNumber);
item->Status = ITEM_DEACTIVATED;
KillItem(itemNumber);
item->Status = ITEM_DEACTIVATED;
}
}
}

View file

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

View file

@ -12,21 +12,23 @@
#include "Specific/level.h"
#include "Specific/trmath.h"
BITE_INFO NatlaGunBite = { 5, 220, 7, 4 };
enum NatlaState
namespace TEN::Entities::TR1
{
NATLA_STATE_NONE,
NATLA_STATE_IDLE,
NATLA_STATE_FLY,
NATLA_STATE_RUN,
NATLA_STATE_AIM,
NATLA_STATE_SEMI_DEATH,
NATLA_STATE_SHOOT,
NATLA_STATE_FALL,
NATLA_STATE_STAND,
NATLA_STATE_DEATH
};
BITE_INFO NatlaGunBite = { 5, 220, 7, 4 };
enum NatlaState
{
NATLA_STATE_NONE,
NATLA_STATE_IDLE,
NATLA_STATE_FLY,
NATLA_STATE_RUN,
NATLA_STATE_AIM,
NATLA_STATE_SEMI_DEATH,
NATLA_STATE_SHOOT,
NATLA_STATE_FALL,
NATLA_STATE_STAND,
NATLA_STATE_DEATH
};
#define NATLA_NEAR_DEATH 200
#define NATLA_FLYMODE 0x8000
@ -35,262 +37,263 @@ enum NatlaState
#define NATLA_FLY_TURN ANGLE(5.0f)
#define NATLA_RUN_TURN ANGLE(6.0f)
#define NATLA_LAND_CHANCE 0x100
#define NATLA_DEATH_TIME (30 * 16)
#define NATLA_DEATH_TIME (FPS * 16)
#define NATLA_SHOT_DAMAGE 100
void NatlaControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0;
short tilt = 0;
short facing = 0;
short gun = creature->JointRotation[0] * 7 / 8;
int shoot;
short timer = creature->Flags & NATLA_TIMER;
AI_INFO AI;
if (item->HitPoints <= 0 && item->HitPoints != NOT_TARGETABLE)
item->Animation.TargetState = NATLA_STATE_DEATH;
else if (item->HitPoints <= NATLA_NEAR_DEATH)
void NatlaControl(short itemNumber)
{
creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1);
creature->LOT.Fly = NO_FLYING;
CreatureAIInfo(item, &AI);
if (!CreatureActive(itemNumber))
return;
if (AI.ahead)
head = AI.angle;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
short head = 0;
short angle = 0;
short tilt = 0;
short facing = 0;
short gun = creature->JointRotation[0] * 7 / 8;
angle = CreatureTurn(item, NATLA_RUN_TURN);
shoot = (AI.angle > -NATLA_FIRE_ARC && AI.angle < NATLA_FIRE_ARC && Targetable(item, &AI));
int shoot;
short timer = creature->Flags & NATLA_TIMER;
if (facing)
AI_INFO AI;
if (item->HitPoints <= 0 && item->HitPoints != NOT_TARGETABLE)
item->Animation.TargetState = NATLA_STATE_DEATH;
else if (item->HitPoints <= NATLA_NEAR_DEATH)
{
item->Pose.Orientation.y += facing;
facing = 0;
}
switch (item->Animation.ActiveState)
{
case NATLA_STATE_FALL:
if (item->Pose.Position.y < item->Floor)
{
item->Animation.Velocity = 0;
item->Animation.Airborne = true;
}
else
{
item->Animation.Airborne = 0;
item->Animation.TargetState = NATLA_STATE_SEMI_DEATH;
item->Pose.Position.y = item->Floor;
timer = 0;
}
break;
case NATLA_STATE_STAND:
if (!shoot)
item->Animation.TargetState = NATLA_STATE_RUN;
if (timer >= 20)
{
short FXNumber = CreatureEffect(item, &NatlaGunBite, ShardGun);
if (FXNumber != NO_ITEM)
{
auto* fx = &EffectList[FXNumber];
gun = fx->pos.Orientation.x;
SoundEffect(SFX_TR1_ATLANTEAN_BALL, &fx->pos);
}
timer = 0;
}
break;
case NATLA_STATE_RUN:
tilt = angle;
if (timer >= 20)
{
short FXNumber = CreatureEffect(item, &NatlaGunBite, ShardGun);
if (FXNumber != NO_ITEM)
{
auto* fx = &EffectList[FXNumber];
gun = fx->pos.Orientation.x;
SoundEffect(SFX_TR1_ATLANTEAN_BALL, &fx->pos);
}
timer = 0;
}
if (shoot)
item->Animation.TargetState = NATLA_STATE_STAND;
break;
case NATLA_STATE_SEMI_DEATH:
if (timer == NATLA_DEATH_TIME)
{
item->Animation.TargetState = NATLA_STATE_STAND;
creature->Flags = 0;
timer = 0;
item->HitPoints = NATLA_NEAR_DEATH;
}
else
item->HitPoints = NOT_TARGETABLE;
break;
case NATLA_STATE_FLY:
item->Animation.TargetState = NATLA_STATE_FALL;
timer = 0;
break;
case NATLA_STATE_IDLE:
case NATLA_STATE_SHOOT:
case NATLA_STATE_AIM:
item->Animation.TargetState = NATLA_STATE_SEMI_DEATH;
item->Flags = 0;
timer = 0;
break;
}
}
else
{
creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1);
creature->LOT.Fly = NO_FLYING;
CreatureAIInfo(item, &AI);
shoot = (AI.angle > -NATLA_FIRE_ARC && AI.angle < NATLA_FIRE_ARC && Targetable(item, &AI));
if (item->Animation.ActiveState == NATLA_STATE_FLY && (creature->Flags & NATLA_FLYMODE))
{
if (creature->Flags & NATLA_FLYMODE && shoot && GetRandomControl() < NATLA_LAND_CHANCE)
creature->Flags -= NATLA_FLYMODE;
if (!(creature->Flags & NATLA_FLYMODE))
CreatureMood(item, &AI, VIOLENT);
creature->LOT.Step = SECTOR(20);
creature->LOT.Drop = -SECTOR(20);
creature->LOT.Fly = CLICK(0.25f) / 2;
creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1);
creature->LOT.Fly = NO_FLYING;
CreatureAIInfo(item, &AI);
}
else if (!shoot)
creature->Flags |= NATLA_FLYMODE;
if (AI.ahead)
head = AI.angle;
if (AI.ahead)
head = AI.angle;
if (item->Animation.ActiveState != NATLA_STATE_FLY || (creature->Flags & NATLA_FLYMODE))
CreatureMood(item, &AI, TIMID);
GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, VIOLENT);
item->Pose.Orientation.y -= facing;
angle = CreatureTurn(item, NATLA_FLY_TURN);
angle = CreatureTurn(item, NATLA_RUN_TURN);
shoot = (AI.angle > -NATLA_FIRE_ARC && AI.angle < NATLA_FIRE_ARC&& Targetable(item, &AI));
if (item->Animation.ActiveState == NATLA_STATE_FLY)
{
if (AI.angle > NATLA_FLY_TURN)
facing += NATLA_FLY_TURN;
else if (AI.angle < -NATLA_FLY_TURN)
facing -= NATLA_FLY_TURN;
else
facing += AI.angle;
if (facing)
{
item->Pose.Orientation.y += facing;
facing = 0;
}
item->Pose.Orientation.y += facing;
switch (item->Animation.ActiveState)
{
case NATLA_STATE_FALL:
if (item->Pose.Position.y < item->Floor)
{
item->Animation.Velocity = 0;
item->Animation.Airborne = true;
}
else
{
item->Animation.Airborne = 0;
item->Animation.TargetState = NATLA_STATE_SEMI_DEATH;
item->Pose.Position.y = item->Floor;
timer = 0;
}
break;
case NATLA_STATE_STAND:
if (!shoot)
item->Animation.TargetState = NATLA_STATE_RUN;
if (timer >= 20)
{
short FXNumber = CreatureEffect(item, &NatlaGunBite, ShardGun);
if (FXNumber != NO_ITEM)
{
auto* fx = &EffectList[FXNumber];
gun = fx->pos.Orientation.x;
SoundEffect(SFX_TR1_ATLANTEAN_BALL, &fx->pos);
}
timer = 0;
}
break;
case NATLA_STATE_RUN:
tilt = angle;
if (timer >= 20)
{
short FXNumber = CreatureEffect(item, &NatlaGunBite, ShardGun);
if (FXNumber != NO_ITEM)
{
auto* fx = &EffectList[FXNumber];
gun = fx->pos.Orientation.x;
SoundEffect(SFX_TR1_ATLANTEAN_BALL, &fx->pos);
}
timer = 0;
}
if (shoot)
item->Animation.TargetState = NATLA_STATE_STAND;
break;
case NATLA_STATE_SEMI_DEATH:
if (timer == NATLA_DEATH_TIME)
{
item->Animation.TargetState = NATLA_STATE_STAND;
creature->Flags = 0;
timer = 0;
item->HitPoints = NATLA_NEAR_DEATH;
}
else
item->HitPoints = NOT_TARGETABLE;
break;
case NATLA_STATE_FLY:
item->Animation.TargetState = NATLA_STATE_FALL;
timer = 0;
break;
case NATLA_STATE_IDLE:
case NATLA_STATE_SHOOT:
case NATLA_STATE_AIM:
item->Animation.TargetState = NATLA_STATE_SEMI_DEATH;
item->Flags = 0;
timer = 0;
break;
}
}
else
{
item->Pose.Orientation.y += facing - angle;
facing = 0;
}
creature->LOT.Step = CLICK(1);
creature->LOT.Drop = -CLICK(1);
creature->LOT.Fly = NO_FLYING;
CreatureAIInfo(item, &AI);
switch (item->Animation.ActiveState)
{
case NATLA_STATE_IDLE:
timer = 0;
shoot = (AI.angle > -NATLA_FIRE_ARC && AI.angle < NATLA_FIRE_ARC&& Targetable(item, &AI));
if (creature->Flags & NATLA_FLYMODE)
item->Animation.TargetState = NATLA_STATE_FLY;
else
item->Animation.TargetState = NATLA_STATE_AIM;
break;
case NATLA_STATE_FLY:
if (!(creature->Flags & NATLA_FLYMODE) && item->Pose.Position.y == item->Floor)
item->Animation.TargetState = NATLA_STATE_IDLE;
if (timer >= 30)
if (item->Animation.ActiveState == NATLA_STATE_FLY && (creature->Flags & NATLA_FLYMODE))
{
short FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun);
if (FXNumber != NO_ITEM)
if (creature->Flags & NATLA_FLYMODE && shoot && GetRandomControl() < NATLA_LAND_CHANCE)
creature->Flags -= NATLA_FLYMODE;
if (!(creature->Flags & NATLA_FLYMODE))
CreatureMood(item, &AI, VIOLENT);
creature->LOT.Step = SECTOR(20);
creature->LOT.Drop = -SECTOR(20);
creature->LOT.Fly = CLICK(0.25f) / 2;
CreatureAIInfo(item, &AI);
}
else if (!shoot)
creature->Flags |= NATLA_FLYMODE;
if (AI.ahead)
head = AI.angle;
if (item->Animation.ActiveState != NATLA_STATE_FLY || (creature->Flags & NATLA_FLYMODE))
CreatureMood(item, &AI, TIMID);
item->Pose.Orientation.y -= facing;
angle = CreatureTurn(item, NATLA_FLY_TURN);
if (item->Animation.ActiveState == NATLA_STATE_FLY)
{
if (AI.angle > NATLA_FLY_TURN)
facing += NATLA_FLY_TURN;
else if (AI.angle < -NATLA_FLY_TURN)
facing -= NATLA_FLY_TURN;
else
facing += AI.angle;
item->Pose.Orientation.y += facing;
}
else
{
item->Pose.Orientation.y += facing - angle;
facing = 0;
}
switch (item->Animation.ActiveState)
{
case NATLA_STATE_IDLE:
timer = 0;
if (creature->Flags & NATLA_FLYMODE)
item->Animation.TargetState = NATLA_STATE_FLY;
else
item->Animation.TargetState = NATLA_STATE_AIM;
break;
case NATLA_STATE_FLY:
if (!(creature->Flags & NATLA_FLYMODE) && item->Pose.Position.y == item->Floor)
item->Animation.TargetState = NATLA_STATE_IDLE;
if (timer >= 30)
{
auto* fx = &EffectList[FXNumber];
gun = fx->pos.Orientation.x;
SoundEffect(SFX_TR1_ATLANTEAN_WINGS, &fx->pos);
short FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun);
if (FXNumber != NO_ITEM)
{
auto* fx = &EffectList[FXNumber];
gun = fx->pos.Orientation.x;
SoundEffect(SFX_TR1_ATLANTEAN_WINGS, &fx->pos);
}
timer = 0;
}
timer = 0;
break;
case NATLA_STATE_AIM:
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (shoot)
item->Animation.TargetState = NATLA_STATE_SHOOT;
else
item->Animation.TargetState = NATLA_STATE_IDLE;
break;
case NATLA_STATE_SHOOT:
if (!item->Animation.RequiredState)
{
short FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun);
if (FXNumber != NO_ITEM)
gun = EffectList[FXNumber].pos.Orientation.x;
FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun);
if (FXNumber != NO_ITEM)
EffectList[FXNumber].pos.Orientation.y += (short)((GetRandomControl() - 0x4000) / 4);
FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun);
if (FXNumber != NO_ITEM)
EffectList[FXNumber].pos.Orientation.y += (short)((GetRandomControl() - 0x4000) / 4);
item->Animation.RequiredState = NATLA_STATE_IDLE;
}
break;
}
break;
case NATLA_STATE_AIM:
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (shoot)
item->Animation.TargetState = NATLA_STATE_SHOOT;
else
item->Animation.TargetState = NATLA_STATE_IDLE;
break;
case NATLA_STATE_SHOOT:
if (!item->Animation.RequiredState)
{
short FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun);
if (FXNumber != NO_ITEM)
gun = EffectList[FXNumber].pos.Orientation.x;
FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun);
if (FXNumber != NO_ITEM)
EffectList[FXNumber].pos.Orientation.y += (short)((GetRandomControl() - 0x4000) / 4);
FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun);
if (FXNumber != NO_ITEM)
EffectList[FXNumber].pos.Orientation.y += (short)((GetRandomControl() - 0x4000) / 4);
item->Animation.RequiredState = NATLA_STATE_IDLE;
}
break;
}
CreatureTilt(item, tilt);
CreatureJoint(item, 0, -head);
if (gun)
CreatureJoint(item, 0, gun);
timer++;
creature->Flags = (creature->Flags & NATLA_FLYMODE) + timer;
item->Pose.Orientation.y -= facing;
CreatureAnimation(itemNumber, angle, tilt);
item->Pose.Orientation.y += facing;
}
CreatureTilt(item, tilt);
CreatureJoint(item, 0, -head);
if (gun)
CreatureJoint(item, 0, gun);
timer++;
creature->Flags = (creature->Flags & NATLA_FLYMODE) + timer;
item->Pose.Orientation.y -= facing;
CreatureAnimation(itemNumber, angle, tilt);
item->Pose.Orientation.y += facing;
}

View file

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

View file

@ -11,232 +11,234 @@
#include "Specific/level.h"
#include "Specific/setup.h"
BITE_INFO WolfBite = { 0, -14, 174, 6 };
enum WolfState
namespace TEN::Entities::TR1
{
WOLF_STATE_NONE = 0,
WOLF_STATE_IDLE = 1,
WOLF_STATE_WALK = 2,
WOLF_STATE_RUN = 3,
WOLF_STATE_JUMP = 4,
WOLF_STATE_STALK = 5,
WOLF_STATE_ATTACK = 6,
WOLF_STATE_HOWL = 7,
WOLF_STATE_SLEEP = 8,
WOLF_STATE_CROUCH = 9,
WOLF_STATE_FAST_TURN = 10,
WOLF_STATE_DEATH = 11,
WOLF_STATE_BITE = 12
};
BITE_INFO WolfBite = { 0, -14, 174, 6 };
const std::vector<int> WolfAttackJoints = { 0, 1, 2, 3, 6, 8, 9, 10, 12, 13, 14 };
// TODO
enum WolfAnim
{
WOLF_ANIM_DEATH = 20,
};
#define SLEEP_FRAME 96
#define TOUCH 0x774f
#define ATTACK_RANGE pow(SECTOR(1.5f), 2)
#define STALK_RANGE pow(SECTOR(2), 2)
#define SLEEP_FRAME 96
#define BITE_DAMAGE 100
#define LUNGE_DAMAGE 50
#define ATTACK_RANGE pow(SECTOR(1.5f), 2)
#define STALK_RANGE pow(SECTOR(2), 2)
#define WAKE_CHANCE 0x20
#define SLEEP_CHANCE 0x20
#define HOWL_CHANCE 0x180
#define BITE_DAMAGE 100
#define LUNGE_DAMAGE 50
#define WALK_TURN ANGLE(2.0f)
#define RUN_TURN ANGLE(5.0f)
#define STALK_TURN ANGLE(2.0f)
#define WAKE_CHANCE 0x20
#define SLEEP_CHANCE 0x20
#define HOWL_CHANCE 0x180
#define WALK_TURN ANGLE(2.0f)
#define RUN_TURN ANGLE(5.0f)
#define STALK_TURN ANGLE(2.0f)
void InitialiseWolf(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
ClearItem(itemNumber);
item->Animation.FrameNumber = SLEEP_FRAME;
}
void WolfControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
short head = 0;
short angle = 0;
short tilt = 0;
if (item->HitPoints <= 0)
enum WolfState
{
if (item->Animation.ActiveState != WOLF_STATE_DEATH)
{
item->Animation.AnimNumber = Objects[ID_WOLF].animIndex + WOLF_ANIM_DEATH + (short)(GetRandomControl() / 11000);
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = WOLF_STATE_DEATH;
}
WOLF_STATE_NONE = 0,
WOLF_STATE_IDLE = 1,
WOLF_STATE_WALK = 2,
WOLF_STATE_RUN = 3,
WOLF_STATE_JUMP = 4,
WOLF_STATE_STALK = 5,
WOLF_STATE_ATTACK = 6,
WOLF_STATE_HOWL = 7,
WOLF_STATE_SLEEP = 8,
WOLF_STATE_CROUCH = 9,
WOLF_STATE_FAST_TURN = 10,
WOLF_STATE_DEATH = 11,
WOLF_STATE_BITE = 12
};
// TODO
enum WolfAnim
{
WOLF_ANIM_DEATH = 20,
};
void InitialiseWolf(short itemNumber)
{
auto* item = &g_Level.Items[itemNumber];
ClearItem(itemNumber);
item->Animation.FrameNumber = SLEEP_FRAME;
}
else
void WolfControl(short itemNumber)
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (!CreatureActive(itemNumber))
return;
if (AI.ahead)
head = AI.angle;
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
GetCreatureMood(item, &AI, TIMID);
CreatureMood(item, &AI, TIMID);
short head = 0;
short angle = 0;
short tilt = 0;
angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState)
if (item->HitPoints <= 0)
{
case WOLF_STATE_SLEEP:
head = 0;
if (creature->Mood == MoodType::Escape || AI.zoneNumber == AI.enemyZone)
if (item->Animation.ActiveState != WOLF_STATE_DEATH)
{
item->Animation.RequiredState = WOLF_STATE_CROUCH;
item->Animation.TargetState = WOLF_STATE_IDLE;
item->Animation.AnimNumber = Objects[ID_WOLF].animIndex + WOLF_ANIM_DEATH + (short)(GetRandomControl() / 11000);
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = WOLF_STATE_DEATH;
}
else if (GetRandomControl() < WAKE_CHANCE)
}
else
{
AI_INFO AI;
CreatureAIInfo(item, &AI);
if (AI.ahead)
head = AI.angle;
GetCreatureMood(item, &AI, TIMID);
CreatureMood(item, &AI, TIMID);
angle = CreatureTurn(item, creature->MaxTurn);
switch (item->Animation.ActiveState)
{
item->Animation.RequiredState = WOLF_STATE_WALK;
item->Animation.TargetState = WOLF_STATE_IDLE;
}
case WOLF_STATE_SLEEP:
head = 0;
break;
case WOLF_STATE_IDLE:
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else
item->Animation.TargetState = WOLF_STATE_WALK;
break;
case WOLF_STATE_WALK:
creature->MaxTurn = WALK_TURN;
if (creature->Mood != MoodType::Bored)
{
item->Animation.TargetState = WOLF_STATE_STALK;
item->Animation.RequiredState = WOLF_STATE_NONE;
}
else if (GetRandomControl() < SLEEP_CHANCE)
{
item->Animation.RequiredState = WOLF_STATE_SLEEP;
item->Animation.TargetState = WOLF_STATE_IDLE;
}
break;
case WOLF_STATE_CROUCH:
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = WOLF_STATE_RUN;
else if (AI.distance < pow(345, 2) && AI.bite)
item->Animation.TargetState = WOLF_STATE_BITE;
else if (creature->Mood == MoodType::Stalk)
item->Animation.TargetState = WOLF_STATE_STALK;
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = WOLF_STATE_IDLE;
else
item->Animation.TargetState = WOLF_STATE_RUN;
break;
case WOLF_STATE_STALK:
creature->MaxTurn = STALK_TURN;
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = WOLF_STATE_RUN;
else if (AI.distance < pow(345, 2) && AI.bite)
item->Animation.TargetState = WOLF_STATE_BITE;
else if (AI.distance > pow(SECTOR(3), 2))
item->Animation.TargetState = WOLF_STATE_RUN;
else if (creature->Mood == MoodType::Attack)
{
if (!AI.ahead || AI.distance > pow(SECTOR(1.5f), 2) ||
(AI.enemyFacing < FRONT_ARC && AI.enemyFacing > -FRONT_ARC))
if (creature->Mood == MoodType::Escape || AI.zoneNumber == AI.enemyZone)
{
item->Animation.TargetState = WOLF_STATE_RUN;
item->Animation.RequiredState = WOLF_STATE_CROUCH;
item->Animation.TargetState = WOLF_STATE_IDLE;
}
else if (GetRandomControl() < WAKE_CHANCE)
{
item->Animation.RequiredState = WOLF_STATE_WALK;
item->Animation.TargetState = WOLF_STATE_IDLE;
}
}
else if (GetRandomControl() < HOWL_CHANCE)
{
item->Animation.RequiredState = WOLF_STATE_HOWL;
item->Animation.TargetState = WOLF_STATE_CROUCH;
}
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = WOLF_STATE_CROUCH;
break;
break;
case WOLF_STATE_RUN:
creature->MaxTurn = RUN_TURN;
tilt = angle;
case WOLF_STATE_IDLE:
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else
item->Animation.TargetState = WOLF_STATE_WALK;
break;
if (AI.ahead && AI.distance < ATTACK_RANGE)
{
if (AI.distance > (ATTACK_RANGE / 2) &&
(AI.enemyFacing > FRONT_ARC || AI.enemyFacing < -FRONT_ARC))
case WOLF_STATE_WALK:
creature->MaxTurn = WALK_TURN;
if (creature->Mood != MoodType::Bored)
{
item->Animation.TargetState = WOLF_STATE_STALK;
item->Animation.RequiredState = WOLF_STATE_NONE;
}
else if (GetRandomControl() < SLEEP_CHANCE)
{
item->Animation.RequiredState = WOLF_STATE_SLEEP;
item->Animation.TargetState = WOLF_STATE_IDLE;
}
break;
case WOLF_STATE_CROUCH:
if (item->Animation.RequiredState)
item->Animation.TargetState = item->Animation.RequiredState;
else if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = WOLF_STATE_RUN;
else if (AI.distance < pow(345, 2) && AI.bite)
item->Animation.TargetState = WOLF_STATE_BITE;
else if (creature->Mood == MoodType::Stalk)
item->Animation.TargetState = WOLF_STATE_STALK;
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = WOLF_STATE_IDLE;
else
item->Animation.TargetState = WOLF_STATE_RUN;
break;
case WOLF_STATE_STALK:
creature->MaxTurn = STALK_TURN;
if (creature->Mood == MoodType::Escape)
item->Animation.TargetState = WOLF_STATE_RUN;
else if (AI.distance < pow(345, 2) && AI.bite)
item->Animation.TargetState = WOLF_STATE_BITE;
else if (AI.distance > pow(SECTOR(3), 2))
item->Animation.TargetState = WOLF_STATE_RUN;
else if (creature->Mood == MoodType::Attack)
{
if (!AI.ahead || AI.distance > pow(SECTOR(1.5f), 2) ||
(AI.enemyFacing < FRONT_ARC && AI.enemyFacing > -FRONT_ARC))
{
item->Animation.TargetState = WOLF_STATE_RUN;
}
}
else if (GetRandomControl() < HOWL_CHANCE)
{
item->Animation.RequiredState = WOLF_STATE_HOWL;
item->Animation.TargetState = WOLF_STATE_CROUCH;
}
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = WOLF_STATE_CROUCH;
break;
case WOLF_STATE_RUN:
creature->MaxTurn = RUN_TURN;
tilt = angle;
if (AI.ahead && AI.distance < ATTACK_RANGE)
{
if (AI.distance > (ATTACK_RANGE / 2) &&
(AI.enemyFacing > FRONT_ARC || AI.enemyFacing < -FRONT_ARC))
{
item->Animation.RequiredState = WOLF_STATE_STALK;
item->Animation.TargetState = WOLF_STATE_CROUCH;
}
else
{
item->Animation.TargetState = WOLF_STATE_ATTACK;
item->Animation.RequiredState = WOLF_STATE_NONE;
}
}
else if (creature->Mood == MoodType::Stalk && AI.distance < STALK_RANGE)
{
item->Animation.RequiredState = WOLF_STATE_STALK;
item->Animation.TargetState = WOLF_STATE_CROUCH;
}
else
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = WOLF_STATE_CROUCH;
break;
case WOLF_STATE_ATTACK:
tilt = angle;
if (!item->Animation.RequiredState && item->TestBits(JointBitType::Touch, WolfAttackJoints))
{
item->Animation.TargetState = WOLF_STATE_ATTACK;
item->Animation.RequiredState = WOLF_STATE_NONE;
CreatureEffect(item, &WolfBite, DoBloodSplat);
LaraItem->HitPoints -= LUNGE_DAMAGE;
LaraItem->HitStatus = true;
item->Animation.RequiredState = WOLF_STATE_RUN;
}
item->Animation.TargetState = WOLF_STATE_RUN;
break;
case WOLF_STATE_BITE:
if (AI.ahead && !item->Animation.RequiredState &&
item->TestBits(JointBitType::Touch, WolfAttackJoints))
{
CreatureEffect(item, &WolfBite, DoBloodSplat);
LaraItem->HitPoints -= BITE_DAMAGE;
LaraItem->HitStatus = true;
item->Animation.RequiredState = WOLF_STATE_CROUCH;
}
break;
}
else if (creature->Mood == MoodType::Stalk && AI.distance < STALK_RANGE)
{
item->Animation.RequiredState = WOLF_STATE_STALK;
item->Animation.TargetState = WOLF_STATE_CROUCH;
}
else if (creature->Mood == MoodType::Bored)
item->Animation.TargetState = WOLF_STATE_CROUCH;
break;
case WOLF_STATE_ATTACK:
tilt = angle;
if (!item->Animation.RequiredState && item->TouchBits & TOUCH)
{
CreatureEffect(item, &WolfBite, DoBloodSplat);
LaraItem->HitPoints -= LUNGE_DAMAGE;
LaraItem->HitStatus = true;
item->Animation.RequiredState = WOLF_STATE_RUN;
}
item->Animation.TargetState = WOLF_STATE_RUN;
break;
case WOLF_STATE_BITE:
if (!item->Animation.RequiredState &&
item->TouchBits & TOUCH && AI.ahead)
{
CreatureEffect(item, &WolfBite, DoBloodSplat);
LaraItem->HitPoints -= BITE_DAMAGE;
LaraItem->HitStatus = true;
item->Animation.RequiredState = WOLF_STATE_CROUCH;
}
break;
}
}
CreatureTilt(item, tilt);
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, tilt);
CreatureTilt(item, tilt);
CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, tilt);
}
}

View file

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

View file

@ -19,6 +19,8 @@
#include "Objects/TR1/Entity/tr1_centaur.h"
#include "Objects/Utils/object_helper.h"
using namespace TEN::Entities::TR1;
static void StartEntity(ObjectInfo* obj)
{
obj = &Objects[ID_WOLF];

View file

@ -558,7 +558,7 @@ void BartoliControl(short itemNumber)
frontItem = (short)back->Data;
front = &g_Level.Items[frontItem];
front->TouchBits = back->TouchBits = 0;
front->TouchBits = back->TouchBits = NO_JOINT_BITS;
EnableBaddyAI(frontItem, 1);
AddActiveItem(frontItem);
AddActiveItem(backItem);

View file

@ -208,21 +208,21 @@ void SkidooManControl(short riderItemNumber)
if (creatureInfo->Flags)
{
SoundEffect(SFX_TR4_BAD_TROOP_UZI, &item->Pose);
SoundEffect(SFX_TR4_BADDY_UZI, &item->Pose);
creatureInfo->Flags--;
}
}
if (item->Animation.ActiveState == SMAN_STATE_WAIT)
{
SoundEffect(SFX_TR2_SNOWMOBILE_IDLE, &item->Pose);
SoundEffect(SFX_TR2_VEHICLE_SNOWMOBILE_IDLE, &item->Pose);
creatureInfo->JointRotation[0] = 0;
}
else
{
creatureInfo->JointRotation[0] = (creatureInfo->JointRotation[0] == 1) ? 2 : 1;
DoSnowEffect(item);
SoundEffect(SFX_TR2_SNOWMOBILE_IDLE, &item->Pose, SoundEnvironment::Land, 0.5f + item->Animation.Velocity / 100.0f); // SKIDOO_MAX_VELOCITY. TODO: Check actual sound!
SoundEffect(SFX_TR2_VEHICLE_SNOWMOBILE_IDLE, &item->Pose, SoundEnvironment::Land, 0.5f + item->Animation.Velocity / 100.0f); // SKIDOO_MAX_VELOCITY. TODO: Check actual sound!
}
CreatureAnimation(itemNumber, angle, 0);

View file

@ -64,7 +64,7 @@ void SmallSpiderControl(short itemNumber)
{
if (item->Animation.ActiveState != 7)
{
ExplodingDeath(itemNumber, -1, 256);
ExplodingDeath(itemNumber, ALL_JOINT_BITS, 256);
DisableEntityAI(itemNumber);
item->Animation.ActiveState = 7;
KillItem(itemNumber);

View file

@ -56,7 +56,7 @@ void SwordGuardianControl(short itemNumber)
SoundEffect(SFX_TR4_EXPLOSION2, &LaraItem->Pose);
//item->meshBits = 0xFFFFFFFF;
//item->objectNumber = ID_SAS;
ExplodingDeath(itemNumber, -1, 256);
ExplodingDeath(itemNumber, ALL_JOINT_BITS, 256);
//item->objectNumber = ID_SWAT;
DisableEntityAI(itemNumber);
KillItem(itemNumber);

View file

@ -2,6 +2,7 @@
#include "Objects/TR2/Entity/tr2_worker_flamethrower.h"
#include "Game/animation.h"
#include "Game/camera.h"
#include "Game/control/box.h"
#include "Game/control/control.h"
#include "Game/effects/effects.h"
@ -16,6 +17,7 @@
#include "Specific/trmath.h"
BITE_INFO WorkerFlamethrowerBite = { 0, 250, 32, 9 };
Vector3Int WorkerFlamethrowerOffset = { 0, 140, 0 };
// TODO
enum WorkerFlamethrowerState
@ -96,10 +98,13 @@ void WorkerFlamethrower(short itemNumber)
if (item->Animation.ActiveState != 5 && item->Animation.ActiveState != 6)
{
TriggerDynamicLight(pos.x, pos.y, pos.z, (GetRandomControl() & 4) + 10, (GetRandomControl() & 7) + 128, (GetRandomControl() & 7) + 64, GetRandomControl() & 7);
AddFire(pos.x, pos.y, pos.z, 0, item->RoomNumber, 0);
TriggerPilotFlame(itemNumber, WorkerFlamethrowerBite.meshNum);
}
else
{
TriggerDynamicLight(pos.x, pos.y, pos.z, (GetRandomControl() & 4) + 14, (GetRandomControl() & 7) + 128, (GetRandomControl() & 7) + 64, GetRandomControl() & 7);
ThrowFire(itemNumber, WorkerFlamethrowerBite.meshNum, WorkerFlamethrowerOffset, WorkerFlamethrowerOffset);
}
AI_INFO AI;
CreatureAIInfo(item, &AI);

View file

@ -32,12 +32,12 @@
#define BOAT_SLIP 10
#define BOAT_SIDE_SLIP 30
#define BOAT_FRONT 750
#define BOAT_BACK -700
#define BOAT_SIDE 300
#define BOAT_RADIUS 500
#define BOAT_SNOW 500
#define BOAT_MAX_HEIGHT CLICK(1)
#define DISMOUNT_DISTANCE SECTOR(1)
#define BOAT_WAKE 700
#define DISMOUNT_DISTANCE SECTOR(1)
#define BOAT_SOUND_CEILING SECTOR(5)
#define BOAT_TIP (BOAT_FRONT + 250)
@ -99,83 +99,6 @@ void InitialiseSpeedBoat(short itemNumber)
sBoat->Pitch = 0;
}
void DoBoatWakeEffect(ItemInfo* sBoatItem)
{
//SetupRipple(sBoatItem->Pose.Position.x, sBoatItem->Pose.Position.y, sBoatItem->Pose.Position.z, 512, RIPPLE_FLAG_RAND_POS, Objects[1368].meshIndex, TO_RAD(sBoatItem->Pose.Orientation.y));
TEN::Effects::TriggerSpeedboatFoam(sBoatItem);
// OLD WAKE EFFECT
/*int c = phd_cos(boat->pos.Orientation.y);
int s = phd_sin(boat->pos.Orientation.y);
int c = phd_cos(boat->pos.Orientation.y);
for (int i = 0; i < 3; i++)
{
int h = BOAT_WAKE;
int w = (1 - i) * BOAT_SIDE;
int x = boat->pos.Position.x + (-(c * w) - (h * s) >> W2V_SHIFT);
int y = boat->pos.Position.y;
int z = boat->pos.Position.z + ((s * w) - (h * c) >> W2V_SHIFT);
SPARKS* spark = &Sparks[GetFreeSpark()];
spark->on = 1;
spark->sR = 64;
spark->sG = 64;
spark->sB = 64;
spark->dR = 64;
spark->dG = 64;
spark->dB = 64;
spark->colFadeSpeed = 1;
spark->transType = TransTypeEnum::COLADD;
spark->life = spark->sLife = (GetRandomControl() & 3) + 6;
spark->fadeToBlack = spark->life - 4;
spark->x = (BOAT_SIDE * phd_sin(boat->pos.Orientation.y) >> W2V_SHIFT) + (GetRandomControl() & 128) + x - 8;
spark->y = (GetRandomControl() & 0xF) + y - 8;
spark->z = (BOAT_SIDE * phd_cos(boat->pos.Orientation.y) >> W2V_SHIFT) + (GetRandomControl() & 128) + z - 8;
spark->xVel = 0;
spark->zVel = 0;
spark->friction = 0;
spark->flags = 538;
spark->yVel = (GetRandomControl() & 0x7F) - 256;
spark->rotAng = GetRandomControl() & 0xFFF;
spark->scalar = 3;
spark->maxYvel = 0;
spark->rotAdd = (GetRandomControl() & 0x1F) - 16;
spark->gravity = -spark->yVel >> 2;
spark->sSize = spark->size = ((GetRandomControl() & 3) + 16) * 16;
spark->dSize = 2 * spark->size;
spark = &Sparks[GetFreeSpark()];
spark->on = 1;
spark->sR = 64;
spark->sG = 64;
spark->sB = 64;
spark->dR = 64;
spark->dG = 64;
spark->dB = 64;
spark->colFadeSpeed = 1;
spark->transType = TransTypeEnum::COLADD;
spark->life = spark->sLife = (GetRandomControl() & 3) + 6;
spark->fadeToBlack = spark->life - 4;
spark->x = (BOAT_SIDE * phd_sin(boat->pos.Orientation.y) >> W2V_SHIFT) + (GetRandomControl() & 128) + x - 8;
spark->y = (GetRandomControl() & 0xF) + y - 8;
spark->z = (BOAT_SIDE * phd_cos(boat->pos.Orientation.y) >> W2V_SHIFT) + (GetRandomControl() & 128) + z - 8;
spark->xVel = 0;
spark->zVel = 0;
spark->friction = 0;
spark->flags = 538;
spark->yVel = (GetRandomControl() & 0x7F) - 256;
spark->rotAng = GetRandomControl() & 0xFFF;
spark->scalar = 3;
spark->maxYvel = 0;
spark->rotAdd = (GetRandomControl() & 0x1F) - 16;
spark->gravity = -spark->yVel >> 2;
spark->sSize = spark->size = ((GetRandomControl() & 3) + 16) * 4;
spark->dSize = 2 * spark->size;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex + 17;
}*/
}
BoatMountType GetSpeedBoatMountType(ItemInfo* laraItem, ItemInfo* sBoatItem, CollisionInfo* coll)
{
auto* lara = GetLaraInfo(laraItem);
@ -1030,6 +953,12 @@ void SpeedBoatControl(short itemNumber)
Camera.targetElevation = -ANGLE(20.0f);
Camera.targetDistance = SECTOR(2);
auto pitch = sBoatItem->Animation.Velocity;
sBoat->Pitch += (pitch - sBoat->Pitch) / 4;
int fx = (sBoatItem->Animation.Velocity > 8) ? SFX_TR2_VEHICLE_SPEEDBOAT_MOVING : (drive ? SFX_TR2_VEHICLE_SPEEDBOAT_IDLE : SFX_TR2_VEHICLE_SPEEDBOAT_ACCELERATE);
SoundEffect(fx, &sBoatItem->Pose, SoundEnvironment::Land, 1.0f + sBoat->Pitch / (float)BOAT_MAX_VELOCITY / 4.0f);
}
else
{
@ -1039,14 +968,12 @@ void SpeedBoatControl(short itemNumber)
sBoatItem->Pose.Orientation.z += sBoat->LeanAngle;
}
auto pitch = sBoatItem->Animation.Velocity;
sBoat->Pitch += (pitch - sBoat->Pitch) / 4;
int fx = (sBoatItem->Animation.Velocity > 8) ? SFX_TR2_SPEEDBOAT_MOVING : (drive ? SFX_TR2_SPEEDBOAT_IDLE : SFX_TR2_SPEEDBOAT_ACCELERATE);
SoundEffect(fx, &sBoatItem->Pose, SoundEnvironment::Land, 1.0f + sBoat->Pitch / (float)BOAT_MAX_VELOCITY / 4.0f);
if (sBoatItem->Animation.Velocity && (water - 5) == sBoatItem->Pose.Position.y)
DoBoatWakeEffect(sBoatItem);
{
auto room = probe.Block->RoomBelow(sBoatItem->Pose.Position.x, sBoatItem->Pose.Position.z).value_or(NO_ROOM);
if (room != NO_ROOM && (TestEnvironment(RoomEnvFlags::ENV_FLAG_WATER, room) || TestEnvironment(RoomEnvFlags::ENV_FLAG_SWAMP, room)))
TEN::Effects::TriggerSpeedboatFoam(sBoatItem, Vector3(0, 0, -BOAT_BACK));
}
if (lara->Vehicle != itemNumber)
return;

View file

@ -12,7 +12,6 @@ enum class BoatMountType
};
void InitialiseSpeedBoat(short itemNumber);
void DoBoatWakeEffect(ItemInfo* sBoatItem);
BoatMountType GetSpeedBoatMountType(ItemInfo* laraItem, ItemInfo* sBoatItem, CollisionInfo* coll);
bool TestSpeedBoatDismount(ItemInfo* sBoatItem, int direction);
void DoSpeedBoatDismount(ItemInfo* laraItem, ItemInfo* sBoatItem);

View file

@ -439,7 +439,7 @@ void SkidooExplode(ItemInfo* laraItem, ItemInfo* skidooItem)
pos.Orientation.z = 0;
TriggerShockwave(&pos, 50, 180, 40, GenerateFloat(160, 200), 60, 60, 64, GenerateFloat(0, 359), 0);
//ExplodingDeath(lara->Vehicle, -1, 256);
//ExplodingDeath(lara->Vehicle, ALL_JOINT_BITS, 256);
KillItem(lara->Vehicle);
skidooItem->Status = ITEM_DEACTIVATED;
@ -523,13 +523,13 @@ bool SkidooControl(ItemInfo* laraItem, CollisionInfo* coll)
skidoo->TrackMesh = ((skidoo->TrackMesh & 3) == 1) ? 2 : 1;
skidoo->Pitch += (pitch - skidoo->Pitch) / 4;
SoundEffect(skidoo->Pitch ? SFX_TR2_SNOWMOBILE_MOVING : SFX_TR2_SNOWMOBILE_ACCELERATE, &skidooItem->Pose, SoundEnvironment::Land, 0.5f + skidoo->Pitch / (float)SKIDOO_MAX_VELOCITY);
SoundEffect(skidoo->Pitch ? SFX_TR2_VEHICLE_SNOWMOBILE_MOVING : SFX_TR2_VEHICLE_SNOWMOBILE_ACCELERATE, &skidooItem->Pose, SoundEnvironment::Land, 0.5f + skidoo->Pitch / (float)SKIDOO_MAX_VELOCITY);
}
else
{
skidoo->TrackMesh = 0;
if (!drive)
SoundEffect(SFX_TR2_SNOWMOBILE_IDLE, &skidooItem->Pose);
SoundEffect(SFX_TR2_VEHICLE_SNOWMOBILE_IDLE, &skidooItem->Pose);
skidoo->Pitch = 0;
}
skidooItem->Floor = height;
@ -716,9 +716,9 @@ void SkidooAnimation(ItemInfo* laraItem, ItemInfo* skidooItem, int collide, bool
if (laraItem->Animation.ActiveState != SKIDOO_STATE_HIT)
{
if (collide == SKIDOO_ANIM_HIT_FRONT)
SoundEffect(SFX_TR2_VEHICLE_IMPACT_1, &skidooItem->Pose);
SoundEffect(SFX_TR2_VEHICLE_IMPACT1, &skidooItem->Pose);
else
SoundEffect(SFX_TR2_VEHICLE_IMPACT_2, &skidooItem->Pose);
SoundEffect(SFX_TR2_VEHICLE_IMPACT2, &skidooItem->Pose);
laraItem->Animation.AnimNumber = Objects[ID_SNOWMOBILE_LARA_ANIMS].animIndex + collide;
laraItem->Animation.FrameNumber = g_Level.Anims[laraItem->Animation.AnimNumber].frameBase;
@ -794,7 +794,7 @@ void SkidooAnimation(ItemInfo* laraItem, ItemInfo* skidooItem, int collide, bool
skidoo->RightVerticalVelocity <= 0)
{
laraItem->Animation.TargetState = SKIDOO_STATE_SIT;
SoundEffect(SFX_TR2_VEHICLE_IMPACT_3, &skidooItem->Pose);
SoundEffect(SFX_TR2_VEHICLE_IMPACT3, &skidooItem->Pose);
}
else if (skidooItem->Animation.VerticalVelocity > (DAMAGE_START + DAMAGE_LENGTH))
laraItem->Animation.TargetState = SKIDOO_STATE_JUMP_OFF;

View file

@ -2,9 +2,11 @@
#include "Objects/TR3/Entity/tr3_flamethrower.h"
#include "Game/animation.h"
#include "Game/camera.h"
#include "Game/control/box.h"
#include "Game/control/lot.h"
#include "Game/effects/effects.h"
#include "Game/effects/tomb4fx.h"
#include "Game/items.h"
#include "Game/itemdata/creature_info.h"
#include "Game/Lara/lara.h"
@ -15,6 +17,7 @@
#include "Specific/setup.h"
BITE_INFO FlamethrowerBite = { 0, 340, 64, 7 };
Vector3Int FlamethrowerOffset = { 0, 340, 0 };
// TODO
enum FlamethrowerState
@ -28,186 +31,6 @@ enum FlamethrowerAnim
};
static void TriggerPilotFlame(int itemNumber)
{
int dx = LaraItem->Pose.Position.x - g_Level.Items[itemNumber].Pose.Position.x;
int dz = LaraItem->Pose.Position.z - g_Level.Items[itemNumber].Pose.Position.z;
if (dx < -SECTOR(16) || dx > SECTOR(16) || dz < -SECTOR(16) || dz > SECTOR(16))
return;
auto* spark = &Sparks[GetFreeSpark()];
spark->on = 1;
spark->sR = 48 + (GetRandomControl() & 31);
spark->sG = spark->sR;
spark->sB = 192 + (GetRandomControl() & 63);
spark->dR = 192 + (GetRandomControl() & 63);
spark->dG = 128 + (GetRandomControl() & 63);
spark->dB = 32;
spark->colFadeSpeed = 12 + (GetRandomControl() & 3);
spark->fadeToBlack = 4;
spark->sLife = spark->life = (GetRandomControl() & 3) + 20;
spark->transType = TransTypeEnum::COLADD;
spark->extras = 0;
spark->dynamic = -1;
spark->x = (GetRandomControl() & 31) - 16;
spark->y = (GetRandomControl() & 31) - 16;
spark->z = (GetRandomControl() & 31) - 16;
spark->xVel = (GetRandomControl() & 31) - 16;
spark->yVel = -(GetRandomControl() & 3);
spark->zVel = (GetRandomControl() & 31) - 16;
spark->flags = SP_SCALE | SP_DEF | SP_EXPDEF | SP_ITEM | SP_NODEATTACH;
spark->fxObj = itemNumber;
spark->nodeNumber = 0;
spark->friction = 4;
spark->gravity = -(GetRandomControl() & 3) - 2;
spark->maxYvel = -(GetRandomControl() & 3) - 4;
//spark->def = Objects[EXPLOSION1].mesh_index;
spark->scalar = 0;
int size = (GetRandomControl() & 7) + 32;
spark->size = size / 2;
spark->dSize = size;
}
static void TriggerFlamethrowerFlame(int x, int y, int z, int xv, int yv, int zv, int fxNumber)
{
auto* spark = &Sparks[GetFreeSpark()];
spark->on = true;
spark->sR = 48 + (GetRandomControl() & 31);
spark->sG = spark->sR;
spark->sB = 192 + (GetRandomControl() & 63);
spark->dR = 192 + (GetRandomControl() & 63);
spark->dG = 128 + (GetRandomControl() & 63);
spark->dB = 32;
if (xv || yv || zv)
{
spark->colFadeSpeed = 6;
spark->fadeToBlack = 2;
spark->sLife = spark->life = (GetRandomControl() & 1) + 12;
}
else
{
spark->colFadeSpeed = 8;
spark->fadeToBlack = 16;
spark->sLife = spark->life = (GetRandomControl() & 3) + 20;
}
spark->transType = TransTypeEnum::COLADD;
spark->extras = 0;
spark->dynamic = -1;
spark->x = x + ((GetRandomControl() & 31) - 16);
spark->y = y;
spark->z = z + ((GetRandomControl() & 31) - 16);
spark->xVel = ((GetRandomControl() & 15) - 16) + xv;
spark->yVel = yv;
spark->zVel = ((GetRandomControl() & 15) - 16) + zv;
spark->friction = 0;
if (GetRandomControl() & 1)
{
if (fxNumber >= 0)
spark->flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF | SP_FX;
else
spark->flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF;
spark->rotAng = GetRandomControl() & 4095;
if (GetRandomControl() & 1)
spark->rotAdd = -(GetRandomControl() & 15) - 16;
else
spark->rotAdd = (GetRandomControl() & 15) + 16;
}
else
{
if (fxNumber >= 0)
spark->flags = SP_SCALE | SP_DEF | SP_EXPDEF | SP_FX;
else
spark->flags = SP_SCALE | SP_DEF | SP_EXPDEF;
}
spark->fxObj = fxNumber;
spark->gravity = 0;
spark->maxYvel = 0;
int size = (GetRandomControl() & 31) + 64;
if (xv || yv || zv)
{
spark->size = size / 32;
if (fxNumber == -2)
spark->scalar = 2;
else
spark->scalar = 3;
}
else
{
spark->size = size / 16;
spark->scalar = 4;
}
spark->dSize = size / 2;
}
static short TriggerFlameThrower(ItemInfo* item, BITE_INFO* bite, short speed)
{
short effectNumber = CreateNewEffect(item->RoomNumber);
if (effectNumber != NO_ITEM)
{
auto* fx = &EffectList[effectNumber];
auto pos1 = Vector3Int(bite->x, bite->y, bite->z);
GetJointAbsPosition(item, &pos1, bite->meshNum);
auto pos2 = Vector3Int(bite->x, bite->y / 2, bite->z);
GetJointAbsPosition(item, &pos2, bite->meshNum);
auto angles = GetVectorAngles(pos2.x - pos1.x, pos2.y - pos1.y, pos2.z - pos1.z);
fx->pos.Position = pos1;
fx->pos.Orientation = angles;
fx->roomNumber = item->RoomNumber;
fx->speed = speed * 4;
fx->counter = 20;
fx->flag1 = 0;
TriggerFlamethrowerFlame(0, 0, 0, 0, 0, 0, effectNumber);
int velocity;
int xv, yv, zv;
for (int i = 0; i < 2; i++)
{
speed = (GetRandomControl() % (speed * 4)) + 32;
velocity = speed * phd_cos(fx->pos.Orientation.x);
xv = velocity * phd_sin(fx->pos.Orientation.y);
yv = -speed * phd_sin(fx->pos.Orientation.x);
zv = velocity * phd_cos(fx->pos.Orientation.y);
TriggerFlamethrowerFlame(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, xv * 32, yv * 32, zv * 32, -1);
}
velocity = (speed * 2) * phd_cos(fx->pos.Orientation.x);
zv = velocity * phd_cos(fx->pos.Orientation.y);
xv = velocity * phd_sin(fx->pos.Orientation.y);
yv = -(speed * 2) * phd_sin(fx->pos.Orientation.x);
TriggerFlamethrowerFlame(fx->pos.Position.x, fx->pos.Position.y, fx->pos.Position.z, xv * 32, yv * 32, zv * 32, -2);
}
return effectNumber;
}
void FlameThrowerControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
@ -229,10 +52,12 @@ void FlameThrowerControl(short itemNumber)
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);
TriggerPilotFlame(itemNumber);
TriggerPilotFlame(itemNumber, 9);
}
else
{
TriggerDynamicLight(pos.x, pos.y, pos.z, (random & 3) + 10, 31 - ((random / 16) & 3), 24 - ((random / 64) & 3), random & 7);
}
if (item->HitPoints <= 0)
{
@ -466,10 +291,10 @@ void FlameThrowerControl(short itemNumber)
item->Animation.TargetState = 1;
if (creature->Flags < 40)
TriggerFlameThrower(item, &FlamethrowerBite, creature->Flags);
ThrowFire(itemNumber, FlamethrowerBite.meshNum, FlamethrowerOffset, Vector3Int(0, creature->Flags * 1.5f, 0));
else
{
TriggerFlameThrower(item, &FlamethrowerBite, (GetRandomControl() & 31) + 12);
ThrowFire(itemNumber, FlamethrowerBite.meshNum, FlamethrowerOffset, Vector3Int(0, (GetRandomControl() & 63) + 12, 0));
if (realEnemy)
{
/*code*/
@ -501,10 +326,10 @@ void FlameThrowerControl(short itemNumber)
item->Animation.TargetState = 2;
if (creature->Flags < 40)
TriggerFlameThrower(item, &FlamethrowerBite, creature->Flags);
ThrowFire(itemNumber, FlamethrowerBite.meshNum, FlamethrowerOffset, Vector3Int(0, creature->Flags * 1.5f, 0));
else
{
TriggerFlameThrower(item, &FlamethrowerBite, (GetRandomControl() & 31) + 12);
ThrowFire(itemNumber, FlamethrowerBite.meshNum, FlamethrowerOffset, Vector3Int(0, (GetRandomControl() & 63) + 12, 0));
if (realEnemy)
{
/*code*/

View file

@ -44,7 +44,7 @@ void MonkeyControl(short itemNumber)
{
if (item->Animation.ActiveState != 11)
{
item->MeshBits = -1;
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;
@ -91,7 +91,7 @@ void MonkeyControl(short itemNumber)
if (item->CarriedItem != NO_ITEM)
item->MeshBits = 0xFFFFFEFF;
else
item->MeshBits = -1;
item->MeshBits = ALL_JOINT_BITS;
}
else
{

View file

@ -102,6 +102,7 @@ void MPGunControl(short itemNumber)
torsoY = AI.angle;
ShotLara(item, &AI, &MPGunBite, torsoY, 32);
SoundEffect(SFX_TR3_OIL_SMG_FIRE, &item->Pose, SoundEnvironment::Land, 1.0f, 0.7f);
creature->FiredWeapon = 1;
}
}
}

View file

@ -12,7 +12,8 @@
#include "Sound/sound.h"
#include "Specific/level.h"
#include "Specific/setup.h"
#include "camera.h"
#include "Game/camera.h"
#include "Renderer/Renderer11Enums.h"
BITE_INFO ShivaBiteLeft = { 0, 0, 920, 13 };
BITE_INFO ShivaBiteRight = { 0, 0, 920, 22 };
@ -71,7 +72,7 @@ static void TriggerShivaSmoke(long x, long y, long z, long uw)
return;
}
auto* sptr = &Sparks[GetFreeSpark()];
auto* sptr = GetFreeParticle();
sptr->on = 1;
if (uw)
@ -98,9 +99,9 @@ static void TriggerShivaSmoke(long x, long y, long z, long uw)
sptr->sLife = sptr->life = (GetRandomControl() & 31) + 96;
if (uw)
sptr->transType = TransTypeEnum::COLADD;
sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
else
sptr->transType = TransTypeEnum::COLADD;
sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = 0;
sptr->dynamic = -1;

View file

@ -16,6 +16,7 @@
#include "Sound/sound.h"
#include "Specific/level.h"
#include "Specific/setup.h"
#include "Renderer/Renderer11Enums.h"
using namespace TEN::Effects::Lara;
@ -62,7 +63,7 @@ enum TonyAnim
static BOSS_STRUCT BossData;
#define TONY_TURN ANGLE(2.0f)
#define TONY_HITS 1 // Tony Harder To Kill, was 100 (6 shotgun shots)
#define TONY_HITS 100
#define MAX_TONY_TRIGGER_RANGE 0x4000
void InitialiseTony(short itemNumber)
@ -119,7 +120,7 @@ static void TriggerTonyFlame(short itemNumber, int hand)
if (dx < -MAX_TONY_TRIGGER_RANGE || dx > MAX_TONY_TRIGGER_RANGE || dz < -MAX_TONY_TRIGGER_RANGE || dz > MAX_TONY_TRIGGER_RANGE)
return;
auto* sptr = &Sparks[GetFreeSpark()];
auto* sptr = GetFreeParticle();
sptr->on = true;
sptr->sR = 255;
@ -131,7 +132,7 @@ static void TriggerTonyFlame(short itemNumber, int hand)
sptr->colFadeSpeed = 12 + (GetRandomControl() & 3);
sptr->fadeToBlack = 8;
sptr->sLife = sptr->life = (GetRandomControl() & 7) + 24;
sptr->transType = TransTypeEnum::COLADD;
sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = NULL;
sptr->dynamic = -1;
sptr->x = ((GetRandomControl() & 15) - 8);
@ -158,7 +159,7 @@ static void TriggerTonyFlame(short itemNumber, int hand)
sptr->maxYvel = -(GetRandomControl() & 7) - 16;
sptr->fxObj = itemNumber;
sptr->nodeNumber = hand;
sptr->def = Objects[ID_DEFAULT_SPRITES].meshIndex;
sptr->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex;
sptr->scalar = 1;
unsigned char size = (GetRandomControl() & 31) + 32;
sptr->size = size;
@ -173,7 +174,7 @@ static void TriggerFireBallFlame(short fxNumber, long type, long xv, long yv, lo
if (dx < -MAX_TONY_TRIGGER_RANGE || dx > MAX_TONY_TRIGGER_RANGE || dz < -MAX_TONY_TRIGGER_RANGE || dz > MAX_TONY_TRIGGER_RANGE)
return;
auto* sptr = &Sparks[GetFreeSpark()];
auto* sptr = GetFreeParticle();
sptr->on = true;
sptr->sR = 255;
@ -185,7 +186,7 @@ static void TriggerFireBallFlame(short fxNumber, long type, long xv, long yv, lo
sptr->colFadeSpeed = 12 + (GetRandomControl() & 3);
sptr->fadeToBlack = 8;
sptr->sLife = sptr->life = (GetRandomControl() & 7) + 24;
sptr->transType = TransTypeEnum::COLADD;
sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = 0;
sptr->dynamic = -1;
sptr->x = ((GetRandomControl() & 15) - 8);
@ -209,7 +210,7 @@ static void TriggerFireBallFlame(short fxNumber, long type, long xv, long yv, lo
sptr->flags = SP_SCALE | SP_DEF | SP_EXPDEF | SP_FX;
sptr->fxObj = (unsigned char)fxNumber;
sptr->def = (unsigned char)Objects[ID_DEFAULT_SPRITES].meshIndex;
sptr->spriteIndex = (unsigned char)Objects[ID_DEFAULT_SPRITES].meshIndex;
sptr->scalar = 1;
unsigned char size = (GetRandomControl() & 31) + 64;
sptr->size = size;

View file

@ -264,7 +264,7 @@ static void CartToEntityCollision(ItemInfo* laraItem, ItemInfo* minecartItem)
int frame = laraItem->Animation.FrameNumber - g_Level.Anims[laraItem->Animation.AnimNumber].frameBase;
if (frame >= 12 && frame <= 22)
{
SoundEffect(SFX_TR3_SPANNER, &item->Pose, SoundEnvironment::Always);
SoundEffect(SFX_TR3_VEHICLE_MINECART_WRENCH, &item->Pose, SoundEnvironment::Always);
TestTriggers(item, true);
item->Animation.FrameNumber++;
}
@ -392,21 +392,21 @@ static void MoveCart(ItemInfo* laraItem, ItemInfo* minecartItem)
if (minecartItem->Animation.Velocity < CART_MIN_VEL)
{
minecartItem->Animation.Velocity = CART_MIN_VEL;
StopSoundEffect(SFX_TR3_MINE_CART_TRACK_LOOP);
StopSoundEffect(SFX_TR3_VEHICLE_MINECART_TRACK_LOOP);
if (minecart->VerticalVelocity)
StopSoundEffect(SFX_TR3_MINE_CART_PULLY_LOOP);
StopSoundEffect(SFX_TR3_VEHICLE_MINECART_PULLY_LOOP);
else
SoundEffect(SFX_TR3_MINE_CART_PULLY_LOOP, &minecartItem->Pose, SoundEnvironment::Always);
SoundEffect(SFX_TR3_VEHICLE_MINECART_PULLY_LOOP, &minecartItem->Pose, SoundEnvironment::Always);
}
else
{
StopSoundEffect(SFX_TR3_MINE_CART_PULLY_LOOP);
StopSoundEffect(SFX_TR3_VEHICLE_MINECART_PULLY_LOOP);
if (minecart->VerticalVelocity)
StopSoundEffect(SFX_TR3_MINE_CART_TRACK_LOOP);
StopSoundEffect(SFX_TR3_VEHICLE_MINECART_TRACK_LOOP);
else
SoundEffect(SFX_TR3_MINE_CART_TRACK_LOOP, &minecartItem->Pose, SoundEnvironment::Land, 1.0f + ((float)minecartItem->Animation.Velocity / SECTOR(8))); // TODO: check actual sound!
SoundEffect(SFX_TR3_VEHICLE_MINECART_TRACK_LOOP, &minecartItem->Pose, SoundEnvironment::Land, 1.0f + ((float)minecartItem->Animation.Velocity / SECTOR(8))); // TODO: check actual sound!
}
if (minecart->Flags & (CART_FLAG_TURNING_LEFT | CART_FLAG_TURNING_RIGHT))
@ -487,7 +487,7 @@ static void MoveCart(ItemInfo* laraItem, ItemInfo* minecartItem)
if (minecartItem->Pose.Position.y > minecart->FloorHeightMiddle)
{
if (minecart->VerticalVelocity > 0)
SoundEffect(SFX_TR3_QUADBIKE_FRONT_IMPACT, &minecartItem->Pose, SoundEnvironment::Always);
SoundEffect(SFX_TR3_VEHICLE_QUADBIKE_FRONT_IMPACT, &minecartItem->Pose, SoundEnvironment::Always);
minecartItem->Pose.Position.y = minecart->FloorHeightMiddle;
minecart->VerticalVelocity = 0;
@ -597,7 +597,7 @@ static void DoUserInput(ItemInfo* minecartItem, ItemInfo* laraItem, MinecartInfo
case CART_STATE_IDLE:
if (!(minecart->Flags & CART_FLAG_CONTROL))
{
SoundEffect(SFX_TR3_MINE_CART_START, &minecartItem->Pose, SoundEnvironment::Always);
SoundEffect(SFX_TR3_VEHICLE_MINECART_START, &minecartItem->Pose, SoundEnvironment::Always);
minecart->Flags |= CART_FLAG_CONTROL;
minecart->StopDelay = 64;
}
@ -641,17 +641,17 @@ static void DoUserInput(ItemInfo* minecartItem, ItemInfo* laraItem, MinecartInfo
if (TrInput & CART_IN_DUCK)
{
laraItem->Animation.TargetState = CART_STATE_DUCK;
StopSoundEffect(SFX_TR3_MINE_CART_BRAKE);
StopSoundEffect(SFX_TR3_VEHICLE_MINECART_BRAKE);
}
else if (!(TrInput & CART_IN_BRAKE) || minecart->Flags & CART_FLAG_STOPPED)
{
laraItem->Animation.TargetState = CART_STATE_MOVE;
StopSoundEffect(SFX_TR3_MINE_CART_BRAKE);
StopSoundEffect(SFX_TR3_VEHICLE_MINECART_BRAKE);
}
else
{
minecart->Velocity += CART_DEC;
SoundEffect(SFX_TR3_MINE_CART_BRAKE, &laraItem->Pose, SoundEnvironment::Always);
SoundEffect(SFX_TR3_VEHICLE_MINECART_BRAKE, &laraItem->Pose, SoundEnvironment::Always);
}
break;
@ -682,16 +682,11 @@ static void DoUserInput(ItemInfo* minecartItem, ItemInfo* laraItem, MinecartInfo
if (laraItem->Animation.AnimNumber == Objects[ID_MINECART_LARA_ANIMS].animIndex + 1 &&
laraItem->Animation.FrameNumber == g_Level.Anims[laraItem->Animation.AnimNumber].frameEnd)
{
Vector3Int pos = { 0, 640, 0 };
auto pos = Vector3Int(0, 640, 0);
GetLaraJointPosition(&pos, LM_HIPS);
laraItem->Pose.Position.x = pos.x;
laraItem->Pose.Position.y = pos.y;
laraItem->Pose.Position.z = pos.z;
laraItem->Pose.Orientation.x = 0;
laraItem->Pose.Orientation.y = ANGLE(90.0f);
laraItem->Pose.Orientation.z = 0;
minecartItem->Pose.Orientation.y + ANGLE(90.0f);
laraItem->Pose.Position = pos;
laraItem->Pose.Orientation = Vector3Shrt(0, minecartItem->Pose.Orientation.y + ANGLE(90.0f), 0);
SetAnimation(laraItem, LA_STAND_SOLID);
lara->Control.HandStatus = HandStatus::Free;
@ -704,16 +699,11 @@ static void DoUserInput(ItemInfo* minecartItem, ItemInfo* laraItem, MinecartInfo
if (laraItem->Animation.AnimNumber == Objects[ID_MINECART_LARA_ANIMS].animIndex + 47 &&
laraItem->Animation.FrameNumber == g_Level.Anims[laraItem->Animation.AnimNumber].frameEnd)
{
Vector3Int pos = { 0, 640, 0 };
auto pos = Vector3Int(0, 640, 0);
GetLaraJointPosition(&pos, LM_HIPS);
laraItem->Pose.Position.x = pos.x;
laraItem->Pose.Position.y = pos.y;
laraItem->Pose.Position.z = pos.z;
laraItem->Pose.Orientation.x = 0;
laraItem->Pose.Orientation.y = ANGLE(90.0f);
laraItem->Pose.Orientation.z = 0;
minecartItem->Pose.Orientation.y + ANGLE(90.0f);
laraItem->Pose.Position = pos;
laraItem->Pose.Orientation = Vector3Shrt(0, minecartItem->Pose.Orientation.y - ANGLE(90.0f), 0);
SetAnimation(laraItem, LA_STAND_SOLID);
lara->Control.HandStatus = HandStatus::Free;
@ -752,7 +742,7 @@ static void DoUserInput(ItemInfo* minecartItem, ItemInfo* laraItem, MinecartInfo
floorHeight < CLICK(1))
{
if (Wibble & 7 == 0)
SoundEffect(SFX_TR3_QUADBIKE_FRONT_IMPACT, &minecartItem->Pose, SoundEnvironment::Always);
SoundEffect(SFX_TR3_VEHICLE_QUADBIKE_FRONT_IMPACT, &minecartItem->Pose, SoundEnvironment::Always);
minecartItem->Pose.Position.x += TURN_DEATH_VEL * phd_sin(minecartItem->Pose.Orientation.y);
minecartItem->Pose.Position.z += TURN_DEATH_VEL * phd_cos(minecartItem->Pose.Orientation.y);

View file

@ -19,6 +19,7 @@
#include "Specific/input.h"
#include "Specific/setup.h"
#include "Specific/prng.h"
#include "Game/particle/SimpleParticle.h"
using std::vector;
using namespace TEN::Math::Random;
@ -60,6 +61,12 @@ using namespace TEN::Math::Random;
#define MAX_MOMENTUM_TURN ANGLE(1.5f)
#define QUAD_MAX_MOM_TURN ANGLE(150.0f)
#define QUAD_MAX_WATER_HEIGHT CLICK(2)
#define QUAD_WATER_VEL_COEFFICIENT 16.0f
#define QUAD_WATER_TURN_COEFFICIENT 10.0f
#define QUAD_SWAMP_VEL_COEFFICIENT 8.0f
#define QUAD_SWAMP_TURN_COEFFICIENT 6.0f
#define QUAD_MAX_HEIGHT CLICK(1)
#define QUAD_MIN_BOUNCE ((MAX_VELOCITY / 2) / CLICK(1))
@ -260,10 +267,10 @@ static bool QuadCheckGetOff(ItemInfo* laraItem, ItemInfo* quadItem)
GetJointAbsPosition(laraItem, &pos, LM_HIPS);
laraItem->Pose.Position = pos;
laraItem->Pose.Orientation.x = 0;
laraItem->Pose.Orientation.z = 0;
laraItem->Animation.Airborne = true;
laraItem->Animation.VerticalVelocity = quadItem->Animation.VerticalVelocity;
laraItem->Pose.Orientation.x = 0;
laraItem->Pose.Orientation.z = 0;
laraItem->HitPoints = 0;
lara->Control.HandStatus = HandStatus::Free;
quadItem->Flags |= ONESHOT;
@ -273,8 +280,8 @@ static bool QuadCheckGetOff(ItemInfo* laraItem, ItemInfo* quadItem)
else if (laraItem->Animation.ActiveState == QUAD_STATE_FALL_DEATH)
{
laraItem->Animation.TargetState = LS_DEATH;
laraItem->Animation.VerticalVelocity = DAMAGE_START + DAMAGE_LENGTH;
laraItem->Animation.Velocity = 0;
laraItem->Animation.VerticalVelocity = DAMAGE_START + DAMAGE_LENGTH;
quad->Flags |= QUAD_FLAG_DEAD;
return false;
@ -841,7 +848,7 @@ static void AnimateQuadBike(ItemInfo* laraItem, ItemInfo* quadItem, int collide,
}
laraItem->Animation.FrameNumber = GetFrameNumber(laraItem, laraItem->Animation.AnimNumber);
SoundEffect(SFX_TR3_QUADBIKE_FRONT_IMPACT, &quadItem->Pose);
SoundEffect(SFX_TR3_VEHICLE_QUADBIKE_FRONT_IMPACT, &quadItem->Pose);
}
else
{
@ -963,11 +970,43 @@ static void AnimateQuadBike(ItemInfo* laraItem, ItemInfo* quadItem, int collide,
if (TestEnvironment(ENV_FLAG_WATER, quadItem) ||
TestEnvironment(ENV_FLAG_SWAMP, quadItem))
{
laraItem->Animation.TargetState = QUAD_STATE_FALL_OFF;
laraItem->Pose.Position.y = quadItem->Pose.Position.y + 700;
laraItem->RoomNumber = quadItem->RoomNumber;
laraItem->HitPoints = 0;
QuadbikeExplode(laraItem, quadItem);
auto waterDepth = (float)GetWaterDepth(quadItem);
// HACK: Sometimes quadbike test position may end up under non-portal ceiling block.
// GetWaterDepth returns DEEP_WATER constant in that case, which is too large for our needs.
if (waterDepth == DEEP_WATER)
waterDepth = QUAD_MAX_WATER_HEIGHT;
if (waterDepth <= QUAD_MAX_WATER_HEIGHT)
{
bool isWater = TestEnvironment(ENV_FLAG_WATER, quadItem);
if (quad->Velocity != 0)
{
auto coeff = isWater ? QUAD_WATER_VEL_COEFFICIENT : QUAD_SWAMP_VEL_COEFFICIENT;
quad->Velocity -= std::copysign(quad->Velocity * ((waterDepth / QUAD_MAX_WATER_HEIGHT) / coeff), quad->Velocity);
if (GenerateInt(0, 32) > 28)
SoundEffect(SFX_TR4_LARA_WADE, &PHD_3DPOS(quadItem->Pose.Position), SoundEnvironment::Land, isWater ? 0.8f : 0.7f);
if (isWater)
TEN::Effects::TriggerSpeedboatFoam(quadItem, Vector3(0, -waterDepth / 2.0f, QUAD_BACK));
}
if (quad->TurnRate != 0)
{
auto coeff = isWater ? QUAD_WATER_TURN_COEFFICIENT : QUAD_SWAMP_TURN_COEFFICIENT;
quad->TurnRate -= quad->TurnRate * ((waterDepth / QUAD_MAX_WATER_HEIGHT) / coeff);
}
}
else
{
laraItem->Animation.TargetState = QUAD_STATE_FALL_OFF;
laraItem->Pose.Position.y = quadItem->Pose.Position.y + 700;
laraItem->RoomNumber = quadItem->RoomNumber;
laraItem->HitPoints = 0;
QuadbikeExplode(laraItem, quadItem);
}
}
}
}
@ -1222,7 +1261,7 @@ void QuadBikeCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll
static void TriggerQuadExhaustSmoke(int x, int y, int z, short angle, int speed, int moving)
{
auto* spark = &Sparks[GetFreeSpark()];
auto* spark = GetFreeParticle();
spark->on = true;
spark->sR = 0;
@ -1270,7 +1309,7 @@ static void TriggerQuadExhaustSmoke(int x, int y, int z, short angle, int speed,
else
spark->flags = SP_SCALE | SP_DEF | SP_EXPDEF;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex;
spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex;
spark->scalar = 2;
spark->gravity = -(GetRandomControl() & 3) - 4;
spark->maxYvel = -(GetRandomControl() & 7) - 8;
@ -1338,12 +1377,12 @@ bool QuadBikeControl(ItemInfo* laraItem, CollisionInfo* coll)
else if (quad->Pitch > 0xA000)
quad->Pitch = 0xA000;
SoundEffect(SFX_TR3_QUADBIKE_MOVE, &quadItem->Pose, SoundEnvironment::Land, 0.5f + (float)abs(quad->Pitch) / (float)MAX_VELOCITY);
SoundEffect(SFX_TR3_VEHICLE_QUADBIKE_MOVE, &quadItem->Pose, SoundEnvironment::Land, 0.5f + (float)abs(quad->Pitch) / (float)MAX_VELOCITY);
}
else
{
if (drive != -1)
SoundEffect(SFX_TR3_QUADBIKE_IDLE, &quadItem->Pose);
SoundEffect(SFX_TR3_VEHICLE_QUADBIKE_IDLE, &quadItem->Pose);
quad->Pitch = 0;
}
@ -1388,7 +1427,7 @@ bool QuadBikeControl(ItemInfo* laraItem, CollisionInfo* coll)
{
if (quadItem->Pose.Position.y == quadItem->Floor)
{
ExplodingDeath(lara->ItemNumber, 0xffffffff, 1);
ExplodingDeath(lara->ItemNumber, ALL_JOINT_BITS, 1);
laraItem->HitPoints = 0;
laraItem->Flags |= ONESHOT;
QuadbikeExplode(laraItem, quadItem);

View file

@ -14,6 +14,7 @@
#include "Specific/input.h"
#include "Specific/level.h"
#include "Specific/setup.h"
#include "Renderer/Renderer11Enums.h"
#define RBOAT_SLIP 10
#define RBOAT_SIDE_SLIP 30
@ -796,7 +797,7 @@ void RubberBoatAnimation(ItemInfo* laraItem, ItemInfo* rBoatItem, int collide)
static void TriggerRubberBoatMist(long x, long y, long z, long velocity, short angle, long snow)
{
auto* sptr = &Sparks[GetFreeSpark()];
auto* sptr = GetFreeParticle();
sptr->on = 1;
sptr->sR = 0;
@ -819,7 +820,7 @@ static void TriggerRubberBoatMist(long x, long y, long z, long velocity, short a
sptr->colFadeSpeed = 4 + (GetRandomControl() & 3);
sptr->fadeToBlack = 12 - (snow * 8);
sptr->sLife = sptr->life = (GetRandomControl() & 3) + 20;
sptr->transType = TransTypeEnum::COLADD;
sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = 0;
sptr->dynamic = -1;
@ -846,7 +847,7 @@ static void TriggerRubberBoatMist(long x, long y, long z, long velocity, short a
else
sptr->flags = SP_SCALE | SP_DEF | SP_EXPDEF;
sptr->def = Objects[ID_EXPLOSION_SPRITES].meshIndex;
sptr->spriteIndex = Objects[ID_EXPLOSION_SPRITES].meshIndex;
if (!snow)
{
@ -1032,9 +1033,9 @@ void RubberBoatControl(short itemNumber)
rBoat->Pitch += ((pitch - rBoat->Pitch) / 4);
if (rBoatItem->Animation.Velocity > 8)
SoundEffect(SFX_TR3_RUBBERBOAT_MOVING, &rBoatItem->Pose, SoundEnvironment::Land, 0.5f + (float)abs(rBoat->Pitch) / (float)RBOAT_MAX_VELOCITY);
SoundEffect(SFX_TR3_VEHICLE_RUBBERBOAT_MOVING, &rBoatItem->Pose, SoundEnvironment::Land, 0.5f + (float)abs(rBoat->Pitch) / (float)RBOAT_MAX_VELOCITY);
else if (drive)
SoundEffect(SFX_TR3_RUBBERBOAT_IDLE, &rBoatItem->Pose, SoundEnvironment::Land, 0.5f + (float)abs(rBoat->Pitch) / (float)RBOAT_MAX_VELOCITY);
SoundEffect(SFX_TR3_VEHICLE_RUBBERBOAT_IDLE, &rBoatItem->Pose, SoundEnvironment::Land, 0.5f + (float)abs(rBoat->Pitch) / (float)RBOAT_MAX_VELOCITY);
if (lara->Vehicle != itemNumber)
return;

View file

@ -102,7 +102,8 @@ enum UPVState
// TODO
enum UPVAnim
{
UPV_ANIM_DEATH = 0,
UPV_ANIM_DEATH_MOVING = 0,
UPV_ANIM_DEATH = 1,
UPV_ANIM_IDLE = 5,
@ -172,7 +173,7 @@ static void FireUPVHarpoon(ItemInfo* laraItem, ItemInfo* UPVItem)
AddActiveItem(itemNumber);
SoundEffect(SFX_TR4_LARA_HARPOON_FIRE_WATER, &laraItem->Pose, SoundEnvironment::Always);
SoundEffect(SFX_TR4_HARPOON_FIRE_UNDERWATER, &laraItem->Pose, SoundEnvironment::Always);
Statistics.Game.AmmoUsed++;
UPV->HarpoonLeft = !UPV->HarpoonLeft;
@ -181,7 +182,7 @@ static void FireUPVHarpoon(ItemInfo* laraItem, ItemInfo* UPVItem)
static void TriggerUPVMist(long x, long y, long z, long velocity, short angle)
{
auto* sptr = &Sparks[GetFreeSpark()];
auto* sptr = GetFreeParticle();
sptr->on = 1;
sptr->sR = 0;
@ -195,7 +196,7 @@ static void TriggerUPVMist(long x, long y, long z, long velocity, short angle)
sptr->colFadeSpeed = 4 + (GetRandomControl() & 3);
sptr->fadeToBlack = 12;
sptr->sLife = sptr->life = (GetRandomControl() & 3) + 20;
sptr->transType = TransTypeEnum::COLADD;
sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = 0;
sptr->dynamic = -1;
@ -630,8 +631,8 @@ static void UPVControl(ItemInfo* laraItem, ItemInfo* UPVItem)
//sub->Flags &= ~UPV_CONTROL; having this here causes the UPV glitch, moving it directly to the states' code is better
StopSoundEffect(SFX_TR3_UPV_LOOP);
SoundEffect(SFX_TR3_UPV_STOP, (PHD_3DPOS*)&UPVItem->Pose.Position.x, SoundEnvironment::Always);
StopSoundEffect(SFX_TR3_VEHICLE_UPV_LOOP);
SoundEffect(SFX_TR3_VEHICLE_UPV_STOP, (PHD_3DPOS*)&UPVItem->Pose.Position.x, SoundEnvironment::Always);
}
}
@ -656,7 +657,7 @@ static void UPVControl(ItemInfo* laraItem, ItemInfo* UPVItem)
UPVItem->Pose.Orientation.x += ANGLE(1.0f);
if (frame == MOUNT_SURFACE_SOUND_FRAME)
SoundEffect(SFX_TR3_UPV_LOOP, (PHD_3DPOS*)&UPVItem->Pose.Position.x, SoundEnvironment::Always);
SoundEffect(SFX_TR3_VEHICLE_UPV_LOOP, (PHD_3DPOS*)&UPVItem->Pose.Position.x, SoundEnvironment::Always);
if (frame == MOUNT_SURFACE_CONTROL_FRAME)
UPV->Flags |= UPV_CONTROL;
@ -665,7 +666,7 @@ static void UPVControl(ItemInfo* laraItem, ItemInfo* UPVItem)
else if (anim == UPV_ANIM_MOUNT_UNDERWATER)
{
if (frame == MOUNT_UNDERWATER_SOUND_FRAME)
SoundEffect(SFX_TR3_UPV_LOOP, (PHD_3DPOS*)&UPVItem->Pose.Position.x, SoundEnvironment::Always);
SoundEffect(SFX_TR3_VEHICLE_UPV_LOOP, (PHD_3DPOS*)&UPVItem->Pose.Position.x, SoundEnvironment::Always);
if (frame == MOUNT_UNDERWATER_CONTROL_FRAME)
UPV->Flags |= UPV_CONTROL;
@ -760,7 +761,8 @@ static void UPVControl(ItemInfo* laraItem, ItemInfo* UPVItem)
break;
case UPV_STATE_DEATH:
if (anim == UPV_ANIM_DEATH && (frame == DEATH_FRAME_1 || frame == DEATH_FRAME_2))
if ((anim == UPV_ANIM_DEATH || anim == UPV_ANIM_DEATH_MOVING) &&
(frame == DEATH_FRAME_1 || frame == DEATH_FRAME_2))
{
auto vec = Vector3Int();
GetLaraJointPosition(&vec, LM_HIPS);
@ -958,8 +960,8 @@ bool UPVControl(ItemInfo* laraItem, CollisionInfo* coll)
UPV->Flags |= UPV_SURFACE;
}
else if ((waterHeight - UPVItem->Pose.Position.y) >= -SURFACE_DIST && waterHeight != NO_HEIGHT)
else if ((waterHeight - UPVItem->Pose.Position.y) >= -SURFACE_DIST && waterHeight != NO_HEIGHT &&
(laraItem->Pose.Position.y - probe.Position.Ceiling) >= CLICK(1))
{
UPVItem->Pose.Position.y = waterHeight + SURFACE_DIST;
@ -971,7 +973,6 @@ bool UPVControl(ItemInfo* laraItem, CollisionInfo* coll)
UPV->Flags |= UPV_SURFACE;
}
else
UPV->Flags &= ~UPV_SURFACE;
@ -1031,7 +1032,7 @@ bool UPVControl(ItemInfo* laraItem, CollisionInfo* coll)
BackgroundCollision(laraItem, UPVItem);
if (UPV->Flags & UPV_CONTROL)
SoundEffect(SFX_TR3_UPV_LOOP, (PHD_3DPOS*)&UPVItem->Pose.Position.x, SoundEnvironment::Always, 1.0f + (float)UPVItem->Animation.Velocity / 96.0f);
SoundEffect(SFX_TR3_VEHICLE_UPV_LOOP, (PHD_3DPOS*)&UPVItem->Pose.Position.x, SoundEnvironment::Always, 1.0f + (float)UPVItem->Animation.Velocity / 96.0f);
UPVItem->Animation.AnimNumber = Objects[ID_UPV].animIndex + (laraItem->Animation.AnimNumber - Objects[ID_UPV_LARA_ANIMS].animIndex);
UPVItem->Animation.FrameNumber = g_Level.Anims[UPVItem->Animation.AnimNumber].frameBase + (laraItem->Animation.FrameNumber - g_Level.Anims[laraItem->Animation.AnimNumber].frameBase);

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