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/Release
TombEngine/Legacy Engine Objects TombEngine/Legacy Engine Objects
x64/ x64/
packages/
.vs/ .vs/
*.dll *.dll
*.dmp *.dmp
@ -23,4 +24,3 @@ x64/
*.wav *.wav
*.trc *.trc
*.str *.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 = { grenade_launcher_ammo1 = {
"Grenadegun (Normal Ammo)", "Grenade Gun Normal Ammo",
"", "",
"", "",
"", "",
@ -500,7 +500,7 @@ local strings = {
"" ""
}, },
grenade_launcher_ammo2 = { grenade_launcher_ammo2 = {
"Grenadegun (Super Ammo)", "Grenade Gun Super Ammo",
"", "",
"", "",
"", "",
@ -510,7 +510,7 @@ local strings = {
"" ""
}, },
grenade_launcher_ammo3 = { grenade_launcher_ammo3 = {
"Grenadegun (Flash Ammo)", "Grenade Gun Flash Ammo",
"", "",
"", "",
"", "",
@ -519,8 +519,8 @@ local strings = {
"", "",
"" ""
}, },
harpoon_item = { harpoon_gun = {
"Harpoon Launcher", "Harpoon Gun",
"", "",
"", "",
"", "",
@ -530,7 +530,7 @@ local strings = {
"" ""
}, },
harpoon_ammo = { harpoon_ammo = {
"Harpoon Launcher Ammo", "Harpoon Gun Ammo",
"", "",
"", "",
"", "",
@ -780,7 +780,7 @@ local strings = {
"" ""
}, },
savegame_timestamp = { savegame_timestamp = {
"%d Days %d:%d:%d", "%02d Days %02d:%02d:%02d",
"", "",
"", "",
"", "",
@ -889,6 +889,16 @@ local strings = {
"", "",
"" ""
}, },
options = {
"Options",
"",
"",
"",
"",
"",
"",
""
},
sound = { sound = {
"Sound settings", "Sound settings",
"", "",

View file

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

View file

@ -1,4 +1,5 @@
#pragma once #pragma once
#include "Game/control/control.h"
#include "Game/Lara/lara_struct.h" #include "Game/Lara/lara_struct.h"
struct ItemInfo; struct ItemInfo;
@ -67,14 +68,14 @@ constexpr auto LARA_DEATH_VELOCITY = 155;
constexpr auto LARA_DIVE_DEATH_VELOCITY = 134; constexpr auto LARA_DIVE_DEATH_VELOCITY = 134;
constexpr auto LARA_TERMINAL_VELOCITY = CLICK(10); 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_POSITION_ADJUST_MAX_TIME = FPS * 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_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_RUN_JUMP_TIME = 22; // Frames to count before a running jump is possible.
constexpr auto LARA_HEALTH_MAX = 1000.0f; constexpr auto LARA_HEALTH_MAX = 1000.0f;
constexpr auto LARA_AIR_MAX = 1800.0f; constexpr auto LARA_AIR_MAX = 1800.0f;
constexpr auto LARA_SPRINT_ENERGY_MAX = 120.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 LaraInfo Lara;
extern ItemInfo* LaraItem; extern ItemInfo* LaraItem;

View file

@ -1419,7 +1419,7 @@ void lara_as_death(ItemInfo* item, CollisionInfo* coll)
BinocularRange = 0; BinocularRange = 0;
LaserSight = false; LaserSight = false;
AlterFOV(ANGLE(80.0f)); AlterFOV(ANGLE(80.0f));
item->MeshBits = -1; item->MeshBits = ALL_JOINT_BITS;
lara->Inventory.IsBusy = false; 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) 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) short GetClimbFlags(FloorInfo* floor)

View file

@ -64,7 +64,7 @@ bool LaraDeflectEdgeJump(ItemInfo* item, CollisionInfo* coll)
SetAnimation(item, LA_LAND); SetAnimation(item, LA_LAND);
LaraSnapToHeight(item, coll); 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); SetAnimation(item, LA_JUMP_WALL_SMASH_START, 1);
item->Animation.Velocity /= 4; item->Animation.Velocity /= 4;
@ -670,10 +670,10 @@ bool TestLaraHitCeiling(CollisionInfo* coll)
void SetLaraHitCeiling(ItemInfo* item, CollisionInfo* coll) void SetLaraHitCeiling(ItemInfo* item, CollisionInfo* coll)
{ {
item->Pose.Position = coll->Setup.OldPosition;
item->Animation.Airborne = false; item->Animation.Airborne = false;
item->Animation.Velocity = 0; item->Animation.Velocity = 0;
item->Animation.VerticalVelocity = 0; item->Animation.VerticalVelocity = 0;
item->Pose.Position = coll->Setup.OldPosition;
} }
bool TestLaraObjectCollision(ItemInfo* item, short angle, int distance, int height, int side) 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) if (TrInput & IN_LOOK)
LookUpDown(item); LookUpDown(item);
// HACK.
if (BinocularOn)
return;
if (TrInput & IN_LEFT) if (TrInput & IN_LEFT)
lara->Control.TurnRate.y = -LARA_CRAWL_TURN_MAX; lara->Control.TurnRate.y = -LARA_CRAWL_TURN_MAX;
else if (TrInput & IN_RIGHT) else if (TrInput & IN_RIGHT)
@ -397,6 +401,10 @@ void lara_as_crawl_idle(ItemInfo* item, CollisionInfo* coll)
if (TrInput & IN_LOOK) if (TrInput & IN_LOOK)
LookUpDown(item); LookUpDown(item);
// HACK.
if (BinocularOn)
return;
if (TrInput & IN_LEFT) if (TrInput & IN_LEFT)
lara->Control.TurnRate.y = -LARA_CRAWL_TURN_MAX; lara->Control.TurnRate.y = -LARA_CRAWL_TURN_MAX;
else if (TrInput & IN_RIGHT) else if (TrInput & IN_RIGHT)

View file

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

View file

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

View file

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

View file

@ -1,11 +1,12 @@
#pragma once #pragma once
#include "Game/control/control.h"
struct ItemInfo; struct ItemInfo;
struct CollisionInfo; struct CollisionInfo;
struct Vector3Int; struct Vector3Int;
enum GAME_OBJECT_ID : short; 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 FlareControl(short itemNumber);
void ReadyFlare(ItemInfo* laraItem); 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 mul1 = (float)abs(lara->Control.Subsuit.Velocity[0]) / SECTOR(8);
auto mul2 = (float)abs(lara->Control.Subsuit.Velocity[1]) / SECTOR(8); auto mul2 = (float)abs(lara->Control.Subsuit.Velocity[1]) / SECTOR(8);
auto vol = ((mul1 + mul2) * 5.0f) + 0.5f; 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) 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.Velocity = 0;
item->Animation.VerticalVelocity = 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); LaraSnapToHeight(item, coll);
} }
@ -674,15 +674,15 @@ void SetLaraLand(ItemInfo* item, CollisionInfo* coll)
void SetLaraFallAnimation(ItemInfo* item) void SetLaraFallAnimation(ItemInfo* item)
{ {
SetAnimation(item, LA_FALL_START); SetAnimation(item, LA_FALL_START);
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = true; item->Animation.Airborne = true;
item->Animation.VerticalVelocity = 0;
} }
void SetLaraFallBackAnimation(ItemInfo* item) void SetLaraFallBackAnimation(ItemInfo* item)
{ {
SetAnimation(item, LA_FALL_BACK); SetAnimation(item, LA_FALL_BACK);
item->Animation.VerticalVelocity = 0;
item->Animation.Airborne = true; item->Animation.Airborne = true;
item->Animation.VerticalVelocity = 0;
} }
void SetLaraMonkeyFallAnimation(ItemInfo* item) void SetLaraMonkeyFallAnimation(ItemInfo* item)
@ -750,7 +750,9 @@ void SetLaraSlideAnimation(ItemInfo* item, CollisionInfo* coll)
item->Pose.Orientation.y = angle; item->Pose.Orientation.y = angle;
} }
LaraSnapToHeight(item, coll);
lara->Control.MoveAngle = angle; lara->Control.MoveAngle = angle;
lara->Control.TurnRate.y = 0;
oldAngle = angle; oldAngle = angle;
} }
@ -828,11 +830,11 @@ void SetLaraCornerAnimation(ItemInfo* item, CollisionInfo* coll, bool flip)
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
SetAnimation(item, LA_FALL_START); 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.Airborne = true;
item->Animation.Velocity = 2; item->Animation.Velocity = 2;
item->Animation.VerticalVelocity = 1; item->Animation.VerticalVelocity = 1;
item->Pose.Position.y += CLICK(1);
item->Pose.Orientation.y += lara->NextCornerPos.Orientation.y / 2;
lara->Control.HandStatus = HandStatus::Free; lara->Control.HandStatus = HandStatus::Free;
return; return;
} }

View file

@ -809,7 +809,7 @@ void lara_as_swan_dive(ItemInfo* item, CollisionInfo* coll)
g_GameFlow->HasCrawlspaceSwandive()) g_GameFlow->HasCrawlspaceSwandive())
{ {
item->Animation.TargetState = LS_CROUCH_IDLE; 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]]) else USE_FEATURE_IF_CPP20([[likely]])
item->Animation.TargetState = LS_IDLE; 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? // HACK: Set position command can't move Lara laterally?
if (item->Animation.AnimNumber == LA_TIGHTROPE_FALL_LEFT) 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) 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; item->Animation.VerticalVelocity = 10;
} }
@ -895,7 +895,7 @@ void lara_as_pole_down(ItemInfo* item, CollisionInfo* coll)
} }
// TODO: In WAD. // TODO: In WAD.
SoundEffect(SFX_TR4_LARA_POLE_LOOP, &item->Pose); SoundEffect(SFX_TR4_LARA_POLE_SLIDE_LOOP, &item->Pose);
if (TrInput & IN_ACTION) if (TrInput & IN_ACTION)
{ {

View file

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

View file

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

View file

@ -928,7 +928,7 @@ enum class HandStatus
Special Special
}; };
enum class TorchState : int enum class TorchState
{ {
Holding, Holding,
Throwing, Throwing,
@ -1079,7 +1079,7 @@ struct ArmInfo
Vector3Shrt Orientation; Vector3Shrt Orientation;
bool Locked; bool Locked;
short FlashGun; int FlashGun;
}; };
struct FlareData struct FlareData
@ -1236,6 +1236,12 @@ struct LaraControlData
WaterStatus WaterStatus; WaterStatus WaterStatus;
LaraCountData Count; LaraCountData Count;
WeaponControlData Weapon;
RopeControlData Rope;
TightropeControlData Tightrope;
SubsuitControlData Subsuit;
MinecartControlData Minecart;
bool CanLook; bool CanLook;
bool IsMoving; bool IsMoving;
bool KeepLow; bool KeepLow;
@ -1245,12 +1251,6 @@ struct LaraControlData
bool CanMonkeySwing; bool CanMonkeySwing;
bool RunJumpQueued; bool RunJumpQueued;
bool Locked; bool Locked;
WeaponControlData Weapon;
RopeControlData Rope;
TightropeControlData Tightrope;
SubsuitControlData Subsuit;
MinecartControlData Minecart;
}; };
struct LaraInfo 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) if (LaraCeilingFront(item, lara->Control.MoveAngle, coll->Setup.Radius * 1.5f, 0) > -950)
stopped = true; stopped = true;
// Backup item pos to restore it after coll tests // Restore backup pos after coll tests
item->Pose = oldPose; item->Pose = oldPose;
// Setup coll lara // Setup coll lara
@ -337,12 +337,20 @@ bool TestLaraClimbIdle(ItemInfo* item, CollisionInfo* coll)
return true; 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) bool TestLaraHangOnClimbableWall(ItemInfo* item, CollisionInfo* coll)
{ {
auto* lara = GetLaraInfo(item); auto* lara = GetLaraInfo(item);
int shift, result; int shift, result;
if (!lara->Control.CanClimbLadder) if (!TestLaraNearClimbableWall(item))
return false; return false;
if (item->Animation.VerticalVelocity < 0) if (item->Animation.VerticalVelocity < 0)
@ -1174,7 +1182,7 @@ bool TestLaraMonkeyStep(ItemInfo* item, CollisionInfo* coll)
return false; 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 // 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) 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 TestLaraClimbIdle(ItemInfo* item, CollisionInfo* coll);
bool TestLaraHangOnClimbableWall(ItemInfo* item, CollisionInfo* coll); bool TestLaraHangOnClimbableWall(ItemInfo* item, CollisionInfo* coll);
bool TestLaraNearClimbableWall(ItemInfo* item, FloorInfo* floor = nullptr);
bool TestLaraValidHangPosition(ItemInfo* item, CollisionInfo* coll); bool TestLaraValidHangPosition(ItemInfo* item, CollisionInfo* coll);
CornerType TestLaraHangCorner(ItemInfo* item, CollisionInfo* coll, float testAngle); 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; 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); SoundEffect(weapon->SampleNum, &laraItem->Pose);
soundPlayed = true; soundPlayed = true;
@ -217,7 +217,7 @@ void AnimatePistols(ItemInfo* laraItem, LaraWeaponType weaponType)
if (!soundPlayed) 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); SoundEffect(weapon->SampleNum, &laraItem->Pose);
} }

View file

@ -87,8 +87,6 @@ void AnimateLara(ItemInfo* item)
if (anim->numberCommands > 0) if (anim->numberCommands > 0)
{ {
short* cmd = &g_Level.Commands[anim->commandIndex]; short* cmd = &g_Level.Commands[anim->commandIndex];
int flags;
int effectID = 0;
for (int i = anim->numberCommands; i > 0; i--) for (int i = anim->numberCommands; i > 0; i--)
{ {
@ -110,14 +108,20 @@ void AnimateLara(ItemInfo* item)
} }
else else
{ {
flags = cmd[1] & 0xC000; bool inWater = (cmd[1] & 0x8000) != 0;
auto condition = flags ? (flags == (1 << 14) ? SoundEnvironment::Land : SoundEnvironment::Water) : SoundEnvironment::Always; 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 || if (condition == SoundEnvironment::Always ||
(condition == SoundEnvironment::Land && (lara->WaterSurfaceDist >= 0 || lara->WaterSurfaceDist == NO_HEIGHT)) || (condition == SoundEnvironment::Land && (lara->WaterSurfaceDist >= -SHALLOW_WATER_START_LEVEL || lara->WaterSurfaceDist == NO_HEIGHT)) ||
(condition == SoundEnvironment::Water && lara->WaterSurfaceDist < -CLICK(0.5f) && lara->WaterSurfaceDist != NO_HEIGHT && !TestEnvironment(ENV_FLAG_SWAMP, item))) (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; break;
} }
effectID = cmd[1] & 0x3FFF; DoFlipEffect((cmd[1] & 0x3FFF), item);
DoFlipEffect(effectID, item);
cmd += 2; cmd += 2;
break; break;
@ -210,7 +213,7 @@ void AnimateLara(ItemInfo* item)
void AnimateItem(ItemInfo* item) void AnimateItem(ItemInfo* item)
{ {
item->TouchBits = 0; item->TouchBits = NO_JOINT_BITS;
item->HitStatus = false; item->HitStatus = false;
item->Animation.FrameNumber++; item->Animation.FrameNumber++;
@ -280,8 +283,6 @@ void AnimateItem(ItemInfo* item)
if (anim->numberCommands > 0) if (anim->numberCommands > 0)
{ {
short* cmd = &g_Level.Commands[anim->commandIndex]; short* cmd = &g_Level.Commands[anim->commandIndex];
int flags;
int effectID = 0;
for (int i = anim->numberCommands; i > 0; i--) for (int i = anim->numberCommands; i > 0; i--)
{ {
@ -302,10 +303,12 @@ void AnimateItem(ItemInfo* item)
break; break;
} }
flags = cmd[1] & 0xC000;
if (!Objects[item->ObjectNumber].waterCreature) 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) if (item->RoomNumber == NO_ROOM)
{ {
item->Pose.Position.x = LaraItem->Pose.Position.x; item->Pose.Position.x = LaraItem->Pose.Position.x;
@ -316,15 +319,15 @@ void AnimateItem(ItemInfo* item)
} }
else if (TestEnvironment(ENV_FLAG_WATER, 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); 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); SoundEffect(cmd[1] & 0x3FFF, &item->Pose, SoundEnvironment::Always);
} }
else 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; break;
@ -336,8 +339,7 @@ void AnimateItem(ItemInfo* item)
break; break;
} }
effectID = cmd[1] & 0x3FFF; DoFlipEffect((cmd[1] & 0x3FFF), item);
DoFlipEffect(effectID, item);
cmd += 2; cmd += 2;
break; break;
@ -369,7 +371,7 @@ void AnimateItem(ItemInfo* item)
lateral >>= 16; lateral >>= 16;
} }
TranslateItem(item, item->Pose.Orientation.y, item->Animation.Velocity, 0, lateral); TranslateItem(item, item->Pose.Orientation.y, item->Animation.Velocity, 0, lateral);
// Update matrices. // Update matrices.
@ -418,9 +420,9 @@ bool TestLastFrame(ItemInfo* item, int animNumber)
return (item->Animation.FrameNumber >= anim->frameEnd); 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) void TranslateItem(ItemInfo* item, Vector3Shrt orient, float distance)
@ -542,7 +544,8 @@ int GetCurrentRelativeFrameNumber(ItemInfo* item)
int GetFrameNumber(ItemInfo* item, int frameToStart) 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) 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 HasStateDispatch(ItemInfo* item, int targetState = -1);
bool TestLastFrame(ItemInfo* item, int animNumber = -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 TranslateItem(ItemInfo* item, Vector3Shrt orient, float distance);
void SetAnimation(ItemInfo* item, int animIndex, int frameToStart = 0); 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 target = Vector3(cam->target.x, cam->target.y, cam->target.z);
Vector3 up = Vector3(0.0f, -1.0f, 0.0f); Vector3 up = Vector3(0.0f, -1.0f, 0.0f);
float fov = TO_RAD(CurrentFOV / 1.333333f); float fov = TO_RAD(CurrentFOV / 1.333333f);
float r = 0; TO_RAD(roll); float r = TO_RAD(roll);
g_Renderer.UpdateCameraMatrices(cam, r, fov); 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 // 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)) 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->Inventory.IsBusy = false;
lara->ExtraHeadRot = Vector3Shrt(); lara->ExtraHeadRot = Vector3Shrt();
lara->ExtraTorsoRot = 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)); AlterFOV(7 * (ANGLE(11.5f) - BinocularRange));
short headXRot = lara->ExtraHeadRot.x * 2; short headXRot = lara->ExtraHeadRot.x * 2;
@ -1250,11 +1250,11 @@ void BinocularCamera(ItemInfo* item)
firing = true; firing = true;
if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer) if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer)
SoundEffect(SFX_TR4_LARA_HK_SILENCED, nullptr); SoundEffect(SFX_TR4_HK_SILENCED, nullptr);
else else
{ {
SoundEffect(SFX_TR4_EXPLOSION1, nullptr, SoundEnvironment::Land, 1.0f, 0.4f); 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) else if (lara->Weapons[(int)LaraWeaponType::HK].SelectedAmmo == WeaponAmmoType::Ammo2)
@ -1271,11 +1271,11 @@ void BinocularCamera(ItemInfo* item)
firing = true; firing = true;
if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer) if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer)
SoundEffect(SFX_TR4_LARA_HK_SILENCED, nullptr); SoundEffect(SFX_TR4_HK_SILENCED, nullptr);
else else
{ {
SoundEffect(SFX_TR4_EXPLOSION1, nullptr, SoundEnvironment::Land, 1.0f, 0.4f); 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 else
@ -1283,11 +1283,11 @@ void BinocularCamera(ItemInfo* item)
Camera.bounce = -16 - (GetRandomControl() & 0x1F); Camera.bounce = -16 - (GetRandomControl() & 0x1F);
if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer) if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer)
SoundEffect(SFX_TR4_LARA_HK_SILENCED, nullptr); SoundEffect(SFX_TR4_HK_SILENCED, nullptr);
else else
{ {
SoundEffect(SFX_TR4_EXPLOSION1, nullptr, SoundEnvironment::Land, 1.0f, 0.4f); 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 (LSHKTimer)
{ {
if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer) if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer)
SoundEffect(SFX_TR4_LARA_HK_SILENCED, nullptr); SoundEffect(SFX_TR4_HK_SILENCED, nullptr);
else else
{ {
SoundEffect(SFX_TR4_EXPLOSION1, nullptr, SoundEnvironment::Land, 1.0f, 0.4f); 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 else
@ -1309,11 +1309,11 @@ void BinocularCamera(ItemInfo* item)
firing = true; firing = true;
if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer) if (lara->Weapons[(int)LaraWeaponType::HK].HasSilencer)
SoundEffect(SFX_TR4_LARA_HK_SILENCED, nullptr); SoundEffect(SFX_TR4_HK_SILENCED, nullptr);
else else
{ {
SoundEffect(SFX_TR4_EXPLOSION1, nullptr, SoundEnvironment::Land, 1.0f, 0.4f); 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 numItems = 0;
short numMeshes = 0; short numMeshes = 0;
@ -157,7 +157,7 @@ bool GetCollidedObjects(ItemInfo* collidingItem, int radius, bool onlyVisible, I
if (item == collidingItem || if (item == collidingItem ||
item->ObjectNumber == ID_LARA && ignoreLara || item->ObjectNumber == ID_LARA && ignoreLara ||
item->Flags & 0x8000 || item->Flags & 0x8000 ||
item->MeshBits == 0 || item->MeshBits == NO_JOINT_BITS ||
(Objects[item->ObjectNumber].drawRoutine == NULL && item->ObjectNumber != ID_LARA) || (Objects[item->ObjectNumber].drawRoutine == NULL && item->ObjectNumber != ID_LARA) ||
(Objects[item->ObjectNumber].collision == NULL && item->ObjectNumber != ID_LARA) || (Objects[item->ObjectNumber].collision == NULL && item->ObjectNumber != ID_LARA) ||
onlyVisible && item->Status == ITEM_INVISIBLE || onlyVisible && item->Status == ITEM_INVISIBLE ||
@ -1744,7 +1744,7 @@ void AIPickupCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll
item->Status = ITEM_INVISIBLE; 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]; auto* item = &g_Level.Items[itemNumber];

View file

@ -26,7 +26,7 @@ struct OBJECT_COLLISION_BOUNDS
}; };
void GenericSphereBoxCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll); 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); bool TestWithGlobalCollisionBounds(ItemInfo* item, ItemInfo* laraItem, CollisionInfo* coll);
void TestForObjectOnLedge(ItemInfo* item, 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) auto test = [item](short x, short y, short z, bool floor)
{ {
CollisionPosition pos = GetCollision(x, y, z, item->RoomNumber).Position; auto collPos = GetCollision(x, y, z, item->RoomNumber).Position;
if (floor) return y > pos.Floor; if (floor) return y > collPos.Floor;
return y < pos.Ceiling; return y < collPos.Ceiling;
}; };
bool collided = bool collided =
test(box.X1, minY, box.Z1, true) test(box.X1, minY, box.Z1, true) ||
|| test(box.X2, minY, box.Z1, true) test(box.X2, minY, box.Z1, true) ||
|| test(box.X1, minY, box.Z2, true) test(box.X1, minY, box.Z2, true) ||
|| test(box.X2, minY, box.Z2, true) test(box.X2, minY, box.Z2, true) ||
|| test(box.X1, maxY, box.Z1, false) test(box.X1, maxY, box.Z1, false) ||
|| test(box.X2, maxY, box.Z1, false) test(box.X2, maxY, box.Z1, false) ||
|| test(box.X1, maxY, box.Z2, false) test(box.X1, maxY, box.Z2, false) ||
|| test(box.X2, maxY, box.Z2, false); test(box.X2, maxY, box.Z2, false) ;
return collided; return collided;
} }
CollisionResult GetCollision(ItemInfo* item, short orient, int forward, int vertical, int lateral) // Overload used to quickly get point/room collision parameters at a given item's position.
{
// 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);
}
CollisionResult GetCollision(ItemInfo* item) CollisionResult GetCollision(ItemInfo* item)
{ {
auto room = item->RoomNumber; auto room = item->RoomNumber;
@ -138,6 +124,30 @@ CollisionResult GetCollision(ItemInfo* item)
return result; 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) CollisionResult GetCollision(int x, int y, int z, short roomNumber)
{ {
auto room = roomNumber; auto room = roomNumber;
@ -148,14 +158,16 @@ CollisionResult GetCollision(int x, int y, int z, short roomNumber)
return result; 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 GetCollision(FloorInfo* floor, int x, int y, int z)
{ {
CollisionResult result = {}; CollisionResult result = {};
// Record coordinates. // Record coordinates.
result.Coordinates.x = x; result.Coordinates = Vector3(x, y, z);
result.Coordinates.y = y;
result.Coordinates.z = z;
// Return provided block into result as itself. // Return provided block into result as itself.
result.Block = floor; result.Block = floor;
@ -701,7 +713,7 @@ void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, Vector3Int offset, bo
if (coll->TriangleAtLeft() && !coll->MiddleLeft.FloorSlope) if (coll->TriangleAtLeft() && !coll->MiddleLeft.FloorSlope)
{ {
// HACK: Force slight push-out to the left side to avoid stucking // 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.x = coll->Setup.OldPosition.x - xPos;
coll->Shift.z = coll->Setup.OldPosition.z - zPos; 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) if (coll->TriangleAtRight() && !coll->MiddleRight.FloorSlope)
{ {
// HACK: Force slight push-out to the right side to avoid stucking // 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.x = coll->Setup.OldPosition.x - xPos;
coll->Shift.z = coll->Setup.OldPosition.z - zPos; 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) 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)]; room = &g_Level.Rooms[floor->RoomAbove(x, y, z).value_or(floor->Room)];
if (!TestEnvironment(ENV_FLAG_WATER, room) && if (!TestEnvironment(ENV_FLAG_WATER, room) &&
!TestEnvironment(ENV_FLAG_SWAMP, room)) !TestEnvironment(ENV_FLAG_SWAMP, room))
{ {
int waterHeight = floor->CeilingHeight(x, z); int waterHeight = floor->CeilingHeight(x, z);
floor = GetFloor(x, y, z, &roomNumber); int floorHeight = GetCollision(floor, x, y, z).BottomBlock->FloorHeight(x, z);
return (GetFloorHeight(floor, x, y, z) - waterHeight); return (floorHeight - waterHeight);
} }
floor = GetSector(room, x - room->x, z - room->z); 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 BlockFloorSlopeDown; // Treat steep slopes as pits
bool BlockCeilingSlope; // Treat steep slopes on ceilings as walls bool BlockCeilingSlope; // Treat steep slopes on ceilings as walls
bool BlockDeathFloorDown; // Treat death sectors as pits 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 EnableObjectPush; // Can be pushed by objects
bool EnableSpasm; // Convulse when pushed bool EnableSpasm; // Convulse when pushed
// Preserve old parameters to restore later // Preserve old parameters to restore later
Vector3Int OldPosition; Vector3Int OldPosition;
int OldState;
int OldAnimNumber; int OldAnimNumber;
int OldFrameNumber; int OldFrameNumber;
int OldState;
}; };
struct CollisionInfo struct CollisionInfo
@ -121,11 +121,12 @@ struct CollisionInfo
}; };
[[nodiscard]] bool TestItemRoomCollisionAABB(ItemInfo* item); [[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);
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, Vector3Int offset, bool resetRoom = false);
void GetCollisionInfo(CollisionInfo* coll, ItemInfo* item, 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; return num;
} }
int TestCollision(ItemInfo* item, ItemInfo* l) int TestCollision(ItemInfo* item, ItemInfo* laraItem)
{ {
int flags = 0; int flags = 0;
int creatureSphereCount = GetSpheres(item, CreatureSpheres, SPHERES_SPACE_WORLD, Matrix::Identity); 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) if (creatureSphereCount <= 0)
{ {
item->TouchBits = 0; item->TouchBits = NO_JOINT_BITS;
return 0; return 0;
} }
else else
{ {
for (int i = 0; i < creatureSphereCount; i++) for (int i = 0; i < creatureSphereCount; i++)
{ {
SPHERE* ptr1 = &CreatureSpheres[i]; auto* ptr1 = &CreatureSpheres[i];
int x1 = item->Pose.Position.x + ptr1->x; int x1 = item->Pose.Position.x + ptr1->x;
int y1 = item->Pose.Position.y + ptr1->y; 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++) for (int j = 0; j < laraSphereCount; j++)
{ {
SPHERE* ptr2 = &LaraSpheres[j]; auto* ptr2 = &LaraSpheres[j];
int x2 = item->Pose.Position.x + ptr2->x; int x2 = item->Pose.Position.x + ptr2->x;
int y2 = item->Pose.Position.y + ptr2->y; int y2 = item->Pose.Position.y + ptr2->y;
@ -78,10 +78,11 @@ int TestCollision(ItemInfo* item, ItemInfo* l)
int dz = z1 - z2; int dz = z1 - z2;
int r = r1 + r2; 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); item->SetBits(JointBitType::Touch, i);
flags |= (1 << i); laraItem->SetBits(JointBitType::Touch, j);
flags |= 1 << i;
break; break;
} }
} }
@ -89,7 +90,6 @@ int TestCollision(ItemInfo* item, ItemInfo* l)
} }
} }
item->TouchBits = flags;
return flags; return flags;
} }
} }
@ -100,7 +100,7 @@ void GetJointAbsPosition(ItemInfo* item, Vector3Int* vec, int joint)
short itemNumber = item - g_Level.Items.data(); short itemNumber = item - g_Level.Items.data();
// Use matrices done in the renderer and transform the input vector // 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); g_Renderer.GetItemAbsBonePosition(itemNumber, &p, joint);
// Store the result // Store the result

View file

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

View file

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

View file

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

View file

@ -41,4 +41,4 @@ void MeshSwapToPour(ItemInfo* item);
void MeshSwapFromPour(ItemInfo* item); void MeshSwapFromPour(ItemInfo* item);
void FlashOrange(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; Lara.Control.Weapon.Fired = true;
if (Lara.Control.Weapon.GunType == LaraWeaponType::Revolver) if (Lara.Control.Weapon.GunType == LaraWeaponType::Revolver)
SoundEffect(SFX_TR4_DESSERT_EAGLE_FIRE, nullptr); SoundEffect(SFX_TR4_REVOLVER_FIRE, nullptr);
} }
bool hit = false; bool hit = false;

View file

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

View file

@ -11,6 +11,8 @@
#include "Game/Lara/lara.h" #include "Game/Lara/lara.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/prng.h" #include "Specific/prng.h"
#include "Specific/setup.h"
#include "Renderer/Renderer11Enums.h"
#define MAX_TRIGGER_RANGE 0x4000 #define MAX_TRIGGER_RANGE 0x4000
using namespace TEN::Math::Random; 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); 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 world
= Matrix::CreateTranslation(-6, 6, 32) = Matrix::CreateTranslation(-6, 6, 32)
@ -62,10 +64,10 @@ void TriggerChaffEffects(ItemInfo* Item,int age)
vel.y = world.Translation().y; vel.y = world.Translation().y;
vel.z = world.Translation().z; 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); int numSparks = (int)GenerateFloat(2, 5);
for (int i = 0; i < numSparks; i++) 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.g = (GetRandomDraw() & 127) + 64;
color.b = 192 - color.g; color.b = 192 - color.g;
TriggerChaffSparkles(pos, vel, &color,age,item); TriggerChaffSparkles(pos, vel, &color, age, item);
if (isUnderwater) if (isUnderwater)
{ {
TriggerChaffBubbles(pos, item->RoomNumber); 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; SPARKS* sparkle;
@ -119,7 +121,7 @@ void TriggerChaffSparkles (Vector3Int* pos, Vector3Int* vel, CVECTOR* color,int
sparkle->colFadeSpeed = 3; sparkle->colFadeSpeed = 3;
sparkle->fadeToBlack = 5; sparkle->fadeToBlack = 5;
sparkle->sLife = sparkle->life = 10; sparkle->sLife = sparkle->life = 10;
sparkle->transType = TransTypeEnum::COLADD; sparkle->transType = BLEND_MODES::BLENDMODE_ADDITIVE;
sparkle->dynamic = true; sparkle->dynamic = true;
sparkle->x = pos->x + (GetRandomDraw() & 7) - 3; 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->sLife = rnd;
} }
smoke->transType = TransTypeEnum::COLADD; smoke->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
smoke->x = pos->x + (GetRandomControl() & 7) - 3; smoke->x = pos->x + (GetRandomControl() & 7) - 3;
smoke->y = pos->y + (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::Effects::Environment;
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
// New particle class
Particle Particles[MAX_PARTICLES];
ParticleDynamic ParticleDynamics[MAX_PARTICLE_DYNAMICS];
FX_INFO EffectList[NUM_EFFECTS]; FX_INFO EffectList[NUM_EFFECTS];
int NextSpark;
int DeadlyBounds[6]; int DeadlyBounds[6];
SPLASH_SETUP SplashSetup; SPLASH_SETUP SplashSetup;
SPLASH_STRUCT Splashes[MAX_SPLASHES]; SPLASH_STRUCT Splashes[MAX_SPLASHES];
RIPPLE_STRUCT Ripples[MAX_RIPPLES]; RIPPLE_STRUCT Ripples[MAX_RIPPLES];
SPARKS Sparks[MAX_SPARKS];
SP_DYNAMIC SparkDynamics[MAX_SPARKS_DYNAMICS];
LaraWeaponType SmokeWeapon; LaraWeaponType SmokeWeapon;
byte SmokeCountL; byte SmokeCountL;
byte SmokeCountR; byte SmokeCountR;
@ -73,9 +74,9 @@ NODEOFFSET_INFO NodeOffsets[MAX_NODE] =
void DetatchSpark(int number, SpriteEnumFlag type) 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) 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; result = i;
break;
spark->extras = 0;
spark->dynamic = -1;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex;
return sparkNumber;
}
else if (sparkNumber == 1023)
sparkNumber = 0;
else
{
spark++;
sparkNumber++;
} }
} }
int life = 4095; // No free sparks left, hijack existing one with less possible life
for (int i = 0; i < MAX_SPARKS; i++)
{
auto* spark = &Sparks[i];
if (spark->life < life && int life = INT_MAX;
spark->dynamic == -1 && if (result == -1)
!(spark->flags & SP_EXPLOSION)) {
for (int i = 0; i < MAX_PARTICLES; i++)
{ {
sparkNumber = i; auto* particle = &Particles[i];
life = spark->life;
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->extras = 0;
spark->dynamic = -1; spark->dynamic = -1;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex; spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex;
return sparkNumber; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
return spark;
} }
void UpdateSparks() void UpdateSparks()
@ -174,17 +170,18 @@ void UpdateSparks()
DeadlyBounds[4] = LaraItem->Pose.Position.z + bounds->Z1; DeadlyBounds[4] = LaraItem->Pose.Position.z + bounds->Z1;
DeadlyBounds[5] = LaraItem->Pose.Position.z + bounds->Z2; 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) if (spark->on)
{ {
spark->life--; spark->life--;
if (!spark->life) if (!spark->life)
{ {
if (spark->dynamic != -1) if (spark->dynamic != -1)
SparkDynamics[spark->dynamic].On = false; ParticleDynamics[spark->dynamic].On = false;
spark->on = false; spark->on = false;
continue; continue;
@ -292,7 +289,9 @@ void UpdateSparks()
float alpha = (spark->sLife - spark->life) / (float)spark->sLife; float alpha = (spark->sLife - spark->life) / (float)spark->sLife;
spark->size = lerp(spark->sSize, spark->dSize, alpha); 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); ds = spark->size * (spark->scalar / 2.0);
@ -304,8 +303,12 @@ void UpdateSparks()
{ {
if (spark->flags & SP_FIRE) if (spark->flags & SP_FIRE)
LaraBurn(LaraItem); LaraBurn(LaraItem);
else
if (spark->flags & SP_DAMAGE)
LaraItem->HitPoints -= 2; 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) if (spark->on && spark->dynamic != -1)
{ {
auto* dynsp = &SparkDynamics[spark->dynamic]; auto* dynsp = &ParticleDynamics[spark->dynamic];
if (dynsp->Flags & 3) if (dynsp->Flags & 3)
{ {
int random = GetRandomControl(); 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) if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384)
{ {
auto* spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
int random = rand(); 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->dB = -64 - ((random & 0x7F) + 64);
spark->life = 10; spark->life = 10;
spark->sLife = 10; spark->sLife = 10;
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->friction = 34; spark->friction = 34;
spark->scalar = 1; spark->scalar = 1;
spark->x = (random & 7) + x - 3; 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)) 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->sR = -128;
spark->dR = -128; spark->dR = -128;
@ -458,7 +461,7 @@ void TriggerExplosionBubbles(int x, int y, int z, short roomNumber)
spark->sB = 0; spark->sB = 0;
spark->colFadeSpeed = 8; spark->colFadeSpeed = 8;
spark->fadeToBlack = 12; spark->fadeToBlack = 12;
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->x = x; spark->x = x;
spark->y = y; spark->y = y;
spark->z = z; spark->z = z;
@ -467,7 +470,7 @@ void TriggerExplosionBubbles(int x, int y, int z, short roomNumber)
spark->zVel = 0; spark->zVel = 0;
spark->friction = 0; spark->friction = 0;
spark->flags = SP_UNDERWEXP | SP_DEF | SP_SCALE; 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->scalar = 3;
spark->gravity = 0; spark->gravity = 0;
spark->maxYvel = 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) void TriggerExplosionSmokeEnd(int x, int y, int z, int uw)
{ {
auto* spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
spark->on = true; spark->on = true;
@ -518,9 +521,9 @@ void TriggerExplosionSmokeEnd(int x, int y, int z, int uw)
spark->life = spark->sLife= (GetRandomControl() & 0x1F) + 96; spark->life = spark->sLife= (GetRandomControl() & 0x1F) + 96;
if (uw) if (uw)
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
else else
spark->transType = TransTypeEnum::COLSUB; spark->blendMode = BLEND_MODES::BLENDMODE_SUBTRACTIVE;
spark->x = (GetRandomControl() & 0x1F) + x - 16; spark->x = (GetRandomControl() & 0x1F) + x - 16;
spark->y = (GetRandomControl() & 0x1F) + y - 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) if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384)
{ {
auto* spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
spark->sR = -112; spark->sR = -112;
spark->sG = -112; spark->sG = -112;
@ -582,7 +585,7 @@ void TriggerExplosionSmoke(int x, int y, int z, int uw)
spark->dB = 64; spark->dB = 64;
spark->colFadeSpeed = 2; spark->colFadeSpeed = 2;
spark->fadeToBlack = 8; spark->fadeToBlack = 8;
spark->transType = TransTypeEnum::COLSUB; spark->blendMode = BLEND_MODES::BLENDMODE_SUBTRACTIVE;
spark->life = spark->sLife = (GetRandomControl() & 3) + 10; spark->life = spark->sLife = (GetRandomControl() & 3) + 10;
spark->x = (GetRandomControl() & 0x1FF) + x - 256; spark->x = (GetRandomControl() & 0x1FF) + x - 256;
spark->y = (GetRandomControl() & 0x1FF) + y - 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) if (dx < -16384 || dx > 16384 || dz < -16384 || dz > 16384)
return; return;
auto* spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
spark->on = true; 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->sLife = spark->life = (GetRandomControl() & 3) + 28;
} }
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
if (fxObj == -1) if (fxObj == -1)
{ {
@ -811,7 +814,7 @@ void TriggerSuperJetFlame(ItemInfo* item, int yvel, int deadly)
if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384) if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384)
{ {
int size = (GetRandomControl() & 0x1FF) - yvel; int size = (GetRandomControl() & 0x1FF) - yvel;
auto* sptr = &Sparks[GetFreeSpark()]; auto* sptr = GetFreeParticle();
if (size < 512) if (size < 512)
size = 512; size = 512;
@ -824,7 +827,7 @@ void TriggerSuperJetFlame(ItemInfo* item, int yvel, int deadly)
sptr->dB = 32; sptr->dB = 32;
sptr->colFadeSpeed = 8; sptr->colFadeSpeed = 8;
sptr->fadeToBlack = 8; sptr->fadeToBlack = 8;
sptr->transType = TransTypeEnum::COLADD; sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->life = sptr->sLife = (size >> 9) + (GetRandomControl() & 7) + 16; sptr->life = sptr->sLife = (size >> 9) + (GetRandomControl() & 7) + 16;
sptr->x = (GetRandomControl() & 0x1F) + item->Pose.Position.x - 16; sptr->x = (GetRandomControl() & 0x1F) + item->Pose.Position.x - 16;
sptr->y = (GetRandomControl() & 0x1F) + item->Pose.Position.y - 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.y = pos->Position.y;
target.z = pos->Position.z; target.z = pos->Position.z;
TriggerRicochetSpark(&target, angle / 16, 3, 0); TriggerRicochetSpark(&target, angle / 16, 3, 0);
SoundEffect(SFX_TR4_LARA_RICOCHET, pos); SoundEffect(SFX_TR4_WEAPON_RICOCHET, pos);
} }
void ControlWaterfallMist(short itemNumber) // ControlWaterfallMist void ControlWaterfallMist(short itemNumber) // ControlWaterfallMist
@ -1103,7 +1106,6 @@ void ControlWaterfallMist(short itemNumber) // ControlWaterfallMist
void TriggerWaterfallMist(int x, int y, int z, int angle) void TriggerWaterfallMist(int x, int y, int z, int angle)
{ {
SPARKS* spark;
int dh = 0; int dh = 0;
int ang1 = angle; int ang1 = angle;
int ang2 = angle; int ang2 = angle;
@ -1112,7 +1114,7 @@ void TriggerWaterfallMist(int x, int y, int z, int angle)
// CHECK THIS LOOP CONDITIONS // CHECK THIS LOOP CONDITIONS
for (ang1 = angle; ; ang1 *= 2) for (ang1 = angle; ; ang1 *= 2)
{ {
spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
spark->on = 1; spark->on = 1;
spark->sR = 64; spark->sR = 64;
spark->sG = 64; spark->sG = 64;
@ -1121,7 +1123,7 @@ void TriggerWaterfallMist(int x, int y, int z, int angle)
spark->dG = 64; spark->dG = 64;
spark->dB = 64; spark->dB = 64;
spark->colFadeSpeed = 1; spark->colFadeSpeed = 1;
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 3) + 6; spark->life = spark->sLife = (GetRandomControl() & 3) + 6;
spark->fadeToBlack = spark->life - 4; spark->fadeToBlack = spark->life - 4;
dl = ((dh + (GlobalCounter << 6)) % 1536) + (GetRandomControl() & 0x3F) - 32; dl = ((dh + (GlobalCounter << 6)) % 1536) + (GetRandomControl() & 0x3F) - 32;
@ -1146,7 +1148,7 @@ void TriggerWaterfallMist(int x, int y, int z, int angle)
break; break;
} }
spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
spark->on = 1; spark->on = 1;
spark->sR = 96; spark->sR = 96;
spark->sG = 96; spark->sG = 96;
@ -1155,7 +1157,7 @@ void TriggerWaterfallMist(int x, int y, int z, int angle)
spark->dG = 96; spark->dG = 96;
spark->dB = 96; spark->dB = 96;
spark->colFadeSpeed = 1; spark->colFadeSpeed = 1;
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 3) + 6; spark->life = spark->sLife = (GetRandomControl() & 3) + 6;
spark->fadeToBlack = spark->life - 1; spark->fadeToBlack = spark->life - 1;
dl = GetRandomControl() % 1408 + 64; dl = GetRandomControl() % 1408 + 64;
@ -1168,7 +1170,7 @@ void TriggerWaterfallMist(int x, int y, int z, int angle)
spark->flags = 10; spark->flags = 10;
spark->yVel = GetRandomControl() & 0x100 + (GetRandomControl() & 0x7F) + 128; spark->yVel = GetRandomControl() & 0x100 + (GetRandomControl() & 0x7F) + 128;
spark->scalar = 2; spark->scalar = 2;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex + 17; spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + 17;
spark->gravity = 0; spark->gravity = 0;
spark->maxYvel = 0; spark->maxYvel = 0;
spark->sSize = spark->size = (GetRandomControl() & 7) + 8; 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) 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->on = true;
sptr->sR = 48 + (GetRandomControl() & 31); 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->colFadeSpeed = 12 + (GetRandomControl() & 3);
sptr->fadeToBlack = 12; sptr->fadeToBlack = 12;
sptr->sLife = sptr->life = (GetRandomControl() & 3) + 28; sptr->sLife = sptr->life = (GetRandomControl() & 3) + 28;
sptr->transType = TransTypeEnum::COLADD; sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = 0; sptr->extras = 0;
sptr->dynamic = -1; 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; sptr->maxYvel = 0;
// TODO: right sprite // TODO: right sprite
sptr->def = Objects[ID_DEFAULT_SPRITES].meshIndex; sptr->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex;
sptr->scalar = 2; sptr->scalar = 2;
int size = (GetRandomControl() & 7) + 32; 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) void TriggerRocketFire(int x, int y, int z)
{ {
auto* sptr = &Sparks[GetFreeSpark()]; auto* sptr = GetFreeParticle();
sptr->on = true; sptr->on = true;
@ -1350,7 +1352,7 @@ void TriggerRocketFire(int x, int y, int z)
sptr->colFadeSpeed = 4 + (GetRandomControl() & 3); sptr->colFadeSpeed = 4 + (GetRandomControl() & 3);
sptr->fadeToBlack = 12; sptr->fadeToBlack = 12;
sptr->sLife = sptr->life = (GetRandomControl() & 3) + 20; sptr->sLife = sptr->life = (GetRandomControl() & 3) + 20;
sptr->transType = TransTypeEnum::COLADD; sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = 0; sptr->extras = 0;
sptr->dynamic = -1; sptr->dynamic = -1;
@ -1376,7 +1378,7 @@ void TriggerRocketFire(int x, int y, int z)
sptr->flags = SP_SCALE | SP_DEF | SP_EXPDEF; sptr->flags = SP_SCALE | SP_DEF | SP_EXPDEF;
// TODO: right sprite // TODO: right sprite
sptr->def = Objects[ID_DEFAULT_SPRITES].meshIndex; sptr->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex;
sptr->scalar = 1; sptr->scalar = 1;
sptr->gravity = -(GetRandomControl() & 3) - 4; sptr->gravity = -(GetRandomControl() & 3) - 4;
sptr->maxYvel = -(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) void TriggerRocketSmoke(int x, int y, int z, int bodyPart)
{ {
/*SPARKS* sptr = &Sparks[GetFreeSpark()]; /*auto* sptr = GetFreeParticle();
sptr->on = true; sptr->on = true;
sptr->sR = 0; sptr->sR = 0;
@ -1403,7 +1405,7 @@ void TriggerRocketSmoke(int x, int y, int z, int bodyPart)
sptr->colFadeSpeed = 4 + (GetRandomControl() & 3); sptr->colFadeSpeed = 4 + (GetRandomControl() & 3);
sptr->fadeToBlack = 12; sptr->fadeToBlack = 12;
sptr->sLife = sptr->life = (GetRandomControl() & 3) + 20; sptr->sLife = sptr->life = (GetRandomControl() & 3) + 20;
sptr->transType = TransTypeEnum::COLADD; sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = 0; sptr->extras = 0;
sptr->dynamic = -1; sptr->dynamic = -1;
@ -1457,7 +1459,7 @@ void TriggerFlashSmoke(int x, int y, int z, short roomNumber)
spark->dShade = -128; spark->dShade = -128;
spark->colFadeSpeed = 4; spark->colFadeSpeed = 4;
spark->fadeToBlack = 16; spark->fadeToBlack = 16;
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 0xF) + 64; spark->life = spark->sLife = (GetRandomControl() & 0xF) + 64;
spark->x = (GetRandomControl() & 0x1F) + x - 16; spark->x = (GetRandomControl() & 0x1F) + x - 16;
spark->y = (GetRandomControl() & 0x1F) + y - 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) if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384)
{ {
auto* spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
spark->on = true; 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->life = spark->sLife = (GetRandomControl() & 3) + 18;
} }
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
if (fxObj != -1) 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(); int r = rand();
auto* spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
spark->dG = (r & 0x7F) + 64; spark->dG = (r & 0x7F) + 64;
spark->dB = -64 - (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->colFadeSpeed = 3;
spark->fadeToBlack = 5; spark->fadeToBlack = 5;
spark->y = ((r >> 3) & 7) + y - 3; spark->y = ((r >> 3) & 7) + y - 3;
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->friction = 34; spark->friction = 34;
spark->scalar = 1; spark->scalar = 1;
spark->z = ((r >> 6) & 7) + z - 3; 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) if (additional)
{ {
r = rand(); r = rand();
spark = &Sparks[GetFreeSpark()]; spark = GetFreeParticle();
spark->on = 1; spark->on = 1;
spark->sR = spark->dR >> 1; spark->sR = spark->dR >> 1;
spark->sG = spark->dG >> 1; spark->sG = spark->dG >> 1;
spark->fadeToBlack = 4; spark->fadeToBlack = 4;
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->colFadeSpeed = (r & 3) + 8; spark->colFadeSpeed = (r & 3) + 8;
spark->sB = spark->dB >> 1; spark->sB = spark->dB >> 1;
spark->dR = 32; spark->dR = 32;

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "Specific/phd_global.h" #include "Specific/phd_global.h"
#include "Renderer/Renderer11Enums.h"
enum class LaraWeaponType; enum class LaraWeaponType;
struct ItemInfo; struct ItemInfo;
@ -33,23 +34,8 @@ enum SpriteEnumFlag
SP_DAMAGE = 0x0400, SP_DAMAGE = 0x0400,
SP_UNDERWEXP = 0x0800, SP_UNDERWEXP = 0x0800,
SP_NODEATTACH = 0x1000, SP_NODEATTACH = 0x1000,
SP_PLASMAEXP = 0x2000 SP_PLASMAEXP = 0x2000,
}; SP_POISON = 0x4000
enum class TransTypeEnum
{
NOTRANS,
SEMITRANS,
COLADD,
COLSUB,
WEIRD
};
enum FireSizeEnum
{
SP_NORMALFIRE,
SP_SMALLFIRE,
SP_BIGFIRE
}; };
struct FX_INFO struct FX_INFO
@ -98,7 +84,7 @@ struct RIPPLE_STRUCT
unsigned char init; unsigned char init;
}; };
struct SPARKS struct Particle
{ {
int x; int x;
int y; int y;
@ -114,24 +100,24 @@ struct SPARKS
float size; float size;
unsigned char friction; unsigned char friction;
unsigned char scalar; unsigned char scalar;
unsigned char def; unsigned char spriteIndex;
signed char rotAdd; signed char rotAdd;
signed char maxYvel; signed char maxYvel;
bool on; bool on;
byte sR; unsigned char sR;
byte sG; unsigned char sG;
byte sB; unsigned char sB;
byte dR; unsigned char dR;
byte dG; unsigned char dG;
byte dB; unsigned char dB;
byte r; unsigned char r;
byte g; unsigned char g;
byte b; unsigned char b;
unsigned char colFadeSpeed; unsigned char colFadeSpeed;
unsigned char fadeToBlack; unsigned char fadeToBlack;
unsigned char sLife; int sLife;
unsigned char life; int life;
TransTypeEnum transType; BLEND_MODES blendMode;
unsigned char extras; unsigned char extras;
signed char dynamic; signed char dynamic;
unsigned char fxObj; unsigned char fxObj;
@ -160,7 +146,7 @@ struct SPLASH_STRUCT
bool isActive; bool isActive;
}; };
struct SP_DYNAMIC struct ParticleDynamic
{ {
byte On; byte On;
byte Falloff; byte Falloff;
@ -176,29 +162,37 @@ constexpr auto SD_UWEXPLOSION = 2;
#define MAX_NODE 23 #define MAX_NODE 23
#define MAX_DYNAMICS 64 #define MAX_DYNAMICS 64
#define MAX_SPARKS 1024
#define MAX_RIPPLES 256 #define MAX_RIPPLES 256
#define MAX_SPLASHES 8 #define MAX_SPLASHES 8
#define MAX_SPARKS_DYNAMICS 8
#define NUM_EFFECTS 256 #define NUM_EFFECTS 256
extern int NextSpark;
extern int DeadlyBounds[6]; 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_SETUP SplashSetup;
extern SPLASH_STRUCT Splashes[MAX_SPLASHES]; extern SPLASH_STRUCT Splashes[MAX_SPLASHES];
extern RIPPLE_STRUCT Ripples[MAX_RIPPLES]; extern RIPPLE_STRUCT Ripples[MAX_RIPPLES];
extern SPARKS Sparks[MAX_SPARKS];
extern SP_DYNAMIC SparkDynamics[MAX_SPARKS_DYNAMICS];
extern LaraWeaponType SmokeWeapon; extern LaraWeaponType SmokeWeapon;
extern byte SmokeCountL; extern byte SmokeCountL;
extern byte SmokeCountR; extern byte SmokeCountR;
extern int SplashCount; extern int SplashCount;
extern Vector3Int NodeVectors[MAX_NODE]; extern Vector3Int NodeVectors[MAX_NODE];
extern NODEOFFSET_INFO NodeOffsets[MAX_NODE]; extern NODEOFFSET_INFO NodeOffsets[MAX_NODE];
extern FX_INFO EffectList[NUM_EFFECTS]; extern FX_INFO EffectList[NUM_EFFECTS];
Particle* GetFreeParticle();
void DetatchSpark(int num, SpriteEnumFlag type); void DetatchSpark(int num, SpriteEnumFlag type);
int GetFreeSpark();
void UpdateSparks(); void UpdateSparks();
void TriggerRicochetSpark(GameVector* pos, short angle, int num, int unk); void TriggerRicochetSpark(GameVector* pos, short angle, int num, int unk);
void TriggerCyborgSpark(int x, int y, int z, short xv, short yv, short zv); 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) if (result.Position.Bridge >= 0)
return; return;
auto fx = SOUND_EFFECTS::SFX_TR4_LARA_FEET; auto fx = SOUND_EFFECTS::SFX_TR4_LARA_FOOTSTEPS;
// Choose material for footstep sound // Choose material for footstep sound
switch (floor->Material) switch (floor->Material)
{ {
case FLOOR_MATERIAL::Concrete: case FLOOR_MATERIAL::Concrete:
fx = SOUND_EFFECTS::SFX_TR4_LARA_FEET; fx = SOUND_EFFECTS::SFX_TR4_LARA_FOOTSTEPS;
break; break;
case FLOOR_MATERIAL::Grass: case FLOOR_MATERIAL::Grass:
@ -107,7 +107,7 @@ namespace Footprints {
break; break;
case FLOOR_MATERIAL::Stone: case FLOOR_MATERIAL::Stone:
fx = SOUND_EFFECTS::SFX_TR4_LARA_FEET; fx = SOUND_EFFECTS::SFX_TR4_LARA_FOOTSTEPS;
break; break;
case FLOOR_MATERIAL::Water: case FLOOR_MATERIAL::Water:
@ -152,7 +152,7 @@ namespace Footprints {
} }
// HACK: must be here until reference wad2 is revised // 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); SoundEffect(fx, &item->Pose);
if (floor->Material != FLOOR_MATERIAL::Sand && if (floor->Material != FLOOR_MATERIAL::Sand &&
@ -209,9 +209,9 @@ namespace Footprints {
footprint.Position[1] = p1; footprint.Position[1] = p1;
footprint.Position[2] = p2; footprint.Position[2] = p2;
footprint.Position[3] = p3; footprint.Position[3] = p3;
footprint.LifeStartFading = 30 * 10;
footprint.StartOpacity = 0.25f; footprint.StartOpacity = 0.25f;
footprint.Life = 30 * 20; footprint.LifeStartFading = FPS * 10;
footprint.Life = FPS * 20;
footprint.Active = true; footprint.Active = true;
footprint.RightFoot = rightFoot; footprint.RightFoot = rightFoot;

View file

@ -8,6 +8,7 @@
#include "Game/effects/smoke.h" #include "Game/effects/smoke.h"
#include "Game/items.h" #include "Game/items.h"
#include "Game/Lara/lara.h" #include "Game/Lara/lara.h"
#include "Game/Lara/lara_helpers.h"
#include "Specific/level.h" #include "Specific/level.h"
using namespace TEN::Effects::Smoke; using namespace TEN::Effects::Smoke;
@ -19,7 +20,7 @@ namespace TEN::Effects::Lara
if (!item->Data.is<LaraInfo*>()) if (!item->Data.is<LaraInfo*>())
return; return;
auto lara = (LaraInfo*&)item->Data; auto* lara = GetLaraInfo(item);
if (!lara->Burn && !lara->BurnSmoke) if (!lara->Burn && !lara->BurnSmoke)
{ {
@ -37,7 +38,7 @@ namespace TEN::Effects::Lara
if (!item->Data.is<LaraInfo*>()) if (!item->Data.is<LaraInfo*>())
return; return;
auto lara = (LaraInfo*&)item->Data; auto* lara = GetLaraInfo(item);
if (item->HitPoints >= 0 && lara->Control.WaterStatus != WaterStatus::FlyCheat) if (item->HitPoints >= 0 && lara->Control.WaterStatus != WaterStatus::FlyCheat)
{ {
@ -62,7 +63,7 @@ namespace TEN::Effects::Lara
if (!item->Data.is<LaraInfo*>()) if (!item->Data.is<LaraInfo*>())
return; return;
auto lara = (LaraInfo*&)item->Data; auto* lara = GetLaraInfo(item);
if (lara->Control.WaterStatus == WaterStatus::Underwater || item->HitPoints <= 0) if (lara->Control.WaterStatus == WaterStatus::Underwater || item->HitPoints <= 0)
return; 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) 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->dG = g;
spark->sG = g; spark->sG = g;
@ -389,7 +389,7 @@ namespace TEN::Effects::Lightning
spark->dR = r; spark->dR = r;
spark->sR = r; spark->sR = r;
spark->colFadeSpeed = 2; spark->colFadeSpeed = 2;
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->on = 1; spark->on = 1;
spark->dB = b; spark->dB = b;
spark->sB = b; spark->sB = b;
@ -403,7 +403,7 @@ namespace TEN::Effects::Lightning
spark->flags = SP_DEF | SP_SCALE; spark->flags = SP_DEF | SP_SCALE;
spark->scalar = 3; spark->scalar = 3;
spark->maxYvel = 0; spark->maxYvel = 0;
spark->def = Objects[ID_MISC_SPRITES].meshIndex; spark->spriteIndex = Objects[ID_MISC_SPRITES].meshIndex;
spark->gravity = 0; spark->gravity = 0;
spark->dSize = spark->sSize = spark->size = size + (GetRandomControl() & 3); spark->dSize = spark->sSize = spark->size = size + (GetRandomControl() & 3);
} }

View file

@ -201,7 +201,154 @@ void TriggerGlobalFireFlame()
spark->dSize = spark->size; 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(); TriggerGlobalStaticFlame();
if (!(Wibble & 0xF)) 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]; FIRE_LIST* fptr = &Fires[0];
int i = 0; int i = 0;
@ -221,10 +368,10 @@ void AddFire(int x, int y, int z, char size, short roomNum, short on)
fptr++; fptr++;
if (++i >= MAX_FIRE_LIST) if (++i >= MAX_FIRE_LIST)
return; return;
} }
if (on) if (fade)
fptr->on = on; fptr->on = fade;
else else
fptr->on = 1; 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->y = y;
fptr->z = z; fptr->z = z;
fptr->roomNumber = roomNum; fptr->roomNumber = roomNum;
fptr->size = size;
switch (size)
{
case SP_NORMALFIRE:
fptr->size = 1;
break;
case SP_SMALLFIRE:
fptr->size = 2;
break;
case SP_BIGFIRE:
fptr->size = 3;
break;
}
} }
void ClearFires() void ClearFires()
@ -255,7 +390,7 @@ void ClearFires()
void UpdateFireSparks() void UpdateFireSparks()
{ {
keep_those_fires_burning(); UpdateFireProgress();
for (int i = 0; i < MAX_SPARKS_FIRE; i++) 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->dShade = 64;
} }
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->x = x + (GetRandomControl() & 31) - 16; spark->x = x + (GetRandomControl() & 31) - 16;
spark->y = y + (GetRandomControl() & 31) - 16; spark->y = y + (GetRandomControl() & 31) - 16;
spark->z = z + (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->colFadeSpeed = 4;
spark->dShade = (GetRandomControl() & 0x1F) + 64; spark->dShade = (GetRandomControl() & 0x1F) + 64;
spark->fadeToBlack = 24 - (GetRandomControl() & 7); spark->fadeToBlack = 24 - (GetRandomControl() & 7);
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 7) + 48; spark->life = spark->sLife = (GetRandomControl() & 7) + 48;
spark->x = (GetRandomControl() & 0x1F) + x - 16; spark->x = (GetRandomControl() & 0x1F) + x - 16;
spark->y = (GetRandomControl() & 0x1F) + y - 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); int ceiling = GetCeiling(floor, gs->pos.Position.x, gs->pos.Position.y, gs->pos.Position.z);
if (gs->pos.Position.y < ceiling) if (gs->pos.Position.y < ceiling)
{ {
SoundEffect(SFX_TR4_LARA_SHOTGUN_SHELL, &gs->pos); SoundEffect(SFX_TR4_SHOTGUN_SHELL, &gs->pos);
gs->speed -= 4; gs->speed -= 4;
if (gs->speed < 8) 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); int height = GetFloorHeight(floor, gs->pos.Position.x, gs->pos.Position.y, gs->pos.Position.z);
if (gs->pos.Position.y >= height) if (gs->pos.Position.y >= height)
{ {
SoundEffect(SFX_TR4_LARA_SHOTGUN_SHELL, &gs->pos); SoundEffect(SFX_TR4_SHOTGUN_SHELL, &gs->pos);
gs->speed -= 8; gs->speed -= 8;
if (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++) for (int i = 0; i < num; i++)
{ {
SPARKS* spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
spark->on = 1; spark->on = 1;
spark->sR = 127; spark->sR = 127;
@ -1020,7 +1155,7 @@ void AddWaterSparks(int x, int y, int z, int num)
spark->sSize = 8; spark->sSize = 8;
spark->dSize = 32; spark->dSize = 32;
spark->scalar = 1; spark->scalar = 1;
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
int random = GetRandomControl() & 0xFFF; int random = GetRandomControl() & 0xFFF;
spark->xVel = -phd_sin(random << 4) * 128; spark->xVel = -phd_sin(random << 4) * 128;
spark->yVel = -GenerateInt(128, 256); 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]; ItemInfo* item = &g_Level.Items[itemNumber];
ObjectInfo* obj = &Objects[item->ObjectNumber]; 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() int GetFreeShockwave()
@ -1355,7 +1490,7 @@ void TriggerShockwave(PHD_3DPOS* pos, short innerRad, short outerRad, int speed,
sptr->b = b; sptr->b = b;
sptr->life = life; 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) if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384)
{ {
SPARKS* spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
spark->dB = b; spark->dB = b;
spark->on = true; spark->on = true;
spark->sR = 0; 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->dR = r;
spark->colFadeSpeed = 4; spark->colFadeSpeed = 4;
spark->fadeToBlack = 8; spark->fadeToBlack = 8;
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 3) + 16; spark->life = spark->sLife = (GetRandomControl() & 3) + 16;
int speed = (GetRandomControl() & 0xF) + vel; 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->rotAdd = (GetRandomControl() & 0xF) + 16;
spark->scalar = 1; 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->maxYvel = 0;
spark->gravity = (GetRandomControl() & 0x3F) + 64; spark->gravity = (GetRandomControl() & 0x3F) + 64;
spark->sSize = spark->size = (GetRandomControl() & 0x1F) + 32; 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) if (dx >= -16384 && dx <= 16384 && dz >= -16384 && dz <= 16384)
{ {
auto* spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
spark->sR = 128; spark->sR = 128;
spark->dR = 128; spark->dR = 128;
@ -1485,7 +1620,7 @@ void TriggerExplosionBubble(int x, int y, int z, short roomNumber)
spark->sB = 0; spark->sB = 0;
spark->colFadeSpeed = 8; spark->colFadeSpeed = 8;
spark->fadeToBlack = 12; spark->fadeToBlack = 12;
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->x = x; spark->x = x;
spark->y = y; spark->y = y;
spark->z = z; spark->z = z;
@ -1496,7 +1631,7 @@ void TriggerExplosionBubble(int x, int y, int z, short roomNumber)
spark->flags = 2058; spark->flags = 2058;
spark->scalar = 3; spark->scalar = 3;
spark->gravity = 0; spark->gravity = 0;
spark->def = Objects[ID_DEFAULT_SPRITES].meshIndex + 13; spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + 13;
spark->maxYvel = 0; spark->maxYvel = 0;
int size = (GetRandomControl() & 7) + 63; int size = (GetRandomControl() & 7) + 63;
spark->sSize = size >> 1; 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) /*void TriggerExplosionSmokeEnd(int x, int y, int z, int unk)
{ {
auto* spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
spark->on = 1; spark->on = 1;
if (unk) if (unk)
@ -1543,9 +1678,9 @@ void TriggerExplosionBubble(int x, int y, int z, short roomNumber)
spark->life = spark->sLife = (GetRandomControl() & 0x1F) + 96; spark->life = spark->sLife = (GetRandomControl() & 0x1F) + 96;
if (unk) if (unk)
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
else else
spark->transType = 3; spark->blendMode = 3;
spark->x = (GetRandomControl() & 0x1F) + x - 16; spark->x = (GetRandomControl() & 0x1F) + x - 16;
spark->y = (GetRandomControl() & 0x1F) + y - 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) void TriggerFenceSparks(int x, int y, int z, int kill, int crane)
{ {
auto* spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
spark->on = 1; spark->on = 1;
spark->sR = (GetRandomControl() & 0x3F) - 0x40; 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->life = (GetRandomControl() & 7) + 24;
spark->sLife = (GetRandomControl() & 7) + 24; spark->sLife = (GetRandomControl() & 7) + 24;
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->dynamic = -1; spark->dynamic = -1;
spark->x = x; spark->x = x;
@ -1643,7 +1778,7 @@ void TriggerSmallSplash(int x, int y, int z, int number)
{ {
for (int i = 0; i < number; i++) for (int i = 0; i < number; i++)
{ {
auto* sptr = &Sparks[GetFreeSpark()]; auto* sptr = GetFreeParticle();
sptr->on = 1; sptr->on = 1;
@ -1661,7 +1796,7 @@ void TriggerSmallSplash(int x, int y, int z, int number)
sptr->life = 24; sptr->life = 24;
sptr->sLife = 24; sptr->sLife = 24;
sptr->transType = TransTypeEnum::COLADD; sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
int angle = GetRandomControl() << 3; int angle = GetRandomControl() << 3;

View file

@ -2,6 +2,7 @@
#include "Game/effects/effects.h" #include "Game/effects/effects.h"
#include "Game/Lara/lara_struct.h" #include "Game/Lara/lara_struct.h"
#include "Specific/phd_global.h" #include "Specific/phd_global.h"
#include "Renderer/Renderer11Enums.h"
enum class LaraWeaponType; enum class LaraWeaponType;
struct ItemInfo; struct ItemInfo;
@ -33,7 +34,7 @@ struct SMOKE_SPARKS
byte fadeToBlack; byte fadeToBlack;
signed char sLife; signed char sLife;
signed char life; signed char life;
TransTypeEnum transType; BLEND_MODES blendMode;
byte fxObj; byte fxObj;
byte nodeNumber; byte nodeNumber;
byte mirror; byte mirror;
@ -96,7 +97,7 @@ struct FIRE_LIST
int y; int y;
int z; int z;
byte on; byte on;
byte size; float size;
short roomNumber; short roomNumber;
}; };
@ -179,14 +180,17 @@ extern int NextBlood;
extern int NextSpider; extern int NextSpider;
extern int NextGunShell; extern int NextGunShell;
#define MAX_SPARKS_FIRE 20 constexpr auto MAX_SPARKS_FIRE = 20;
#define MAX_FIRE_LIST 32 constexpr auto MAX_FIRE_LIST = 32;
#define MAX_SPARKS_SMOKE 32 constexpr auto MAX_SPARKS_SMOKE = 32;
#define MAX_SPARKS_BLOOD 32 constexpr auto MAX_SPARKS_BLOOD = 32;
#define MAX_GUNFLASH 4 constexpr auto MAX_GUNFLASH = 4;
#define MAX_GUNSHELL 24 constexpr auto MAX_GUNSHELL = 24;
#define MAX_DRIPS 32 constexpr auto MAX_DRIPS = 32;
#define MAX_SHOCKWAVE 16 constexpr auto MAX_SHOCKWAVE = 16;
constexpr auto EXPLODE_HIT_EFFECT = 258;
constexpr auto EXPLODE_NORMAL = 256;
extern GUNFLASH_STRUCT Gunflashes[MAX_GUNFLASH]; extern GUNFLASH_STRUCT Gunflashes[MAX_GUNFLASH];
extern FIRE_SPARKS FireSparks[MAX_SPARKS_FIRE]; 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 DRIP_STRUCT Drips[MAX_DRIPS];
extern SHOCKWAVE_STRUCT ShockWaves[MAX_SHOCKWAVE]; extern SHOCKWAVE_STRUCT ShockWaves[MAX_SHOCKWAVE];
extern FIRE_LIST Fires[MAX_FIRE_LIST]; 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 TriggerBlood(int x, int y, int z, int unk, int num);
void TriggerExplosionBubble(int x, int y, int z, short roomNumber); void TriggerExplosionBubble(int x, int y, int z, short roomNumber);
@ -204,9 +207,12 @@ int GetFreeFireSpark();
void TriggerGlobalStaticFlame(); void TriggerGlobalStaticFlame();
void TriggerGlobalFireSmoke(); void TriggerGlobalFireSmoke();
void TriggerGlobalFireFlame(); 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 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(); void UpdateFireSparks();
int GetFreeSmokeSpark(); int GetFreeSmokeSpark();
void UpdateSmoke(); void UpdateSmoke();
@ -227,14 +233,9 @@ void UpdateBubbles();
int GetFreeDrip(); int GetFreeDrip();
void UpdateDrips(); void UpdateDrips();
void TriggerLaraDrips(ItemInfo* item); void TriggerLaraDrips(ItemInfo* item);
int ExplodingDeath(short itemNumber, unsigned int meshBits, short flags); // EXPLODE_ flags
constexpr auto EXPLODE_HIT_EFFECT = 258;
constexpr auto EXPLODE_NORMAL = 256;
int ExplodingDeath(short itemNumber, int meshBits, short flags); // EXPLODE_ flags
int GetFreeShockwave(); 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 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 UpdateShockwaves();
void TriggerSmallSplash(int x, int y, int z, int number); 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 // 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_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_MESH_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_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_JOINT_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_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_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_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_MESH_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_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_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_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_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_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_MESH_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_MESH_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(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_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_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_MESH_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_MESH_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_MESH_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_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_JOINT_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_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_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_JOINT_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_ROCKET_LAUNCHER_AMMO_ITEM, 3, 0.5f, ANGLE(90), 0, ANGLE(15), OPT_USE, STRING_ROCKET_AMMO, NO_JOINT_BITS, INV_ROT_Y},
// Misc // 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_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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}, {ID_OPEN_DIARY_ITEM, 0, 0.9f, ANGLE(90), 0, 0, 0, 0, 0, 0},
// Puzzles // Puzzles
{ID_PUZZLE_ITEM1, 14, 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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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 // 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_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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 // Keys
{ID_KEY_ITEM1, 14, 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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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 // 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_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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 // Pickups
{ID_PICKUP_ITEM1, 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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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 // 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_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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 // Examines
{ID_EXAMINE1, 4, 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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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 // 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_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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_MESH_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() SettingsData GuiController::GetCurrentSettings()
@ -626,15 +626,27 @@ InventoryResult GuiController::TitleOptions()
DoDebouncedInput(); DoDebouncedInput();
if (menu_to_display == Menu::Title || menu_to_display == Menu::SelectLevel || if (menu_to_display == Menu::LoadGame)
menu_to_display == Menu::LoadGame || menu_to_display == Menu::Options) {
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 (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 else
selected_option--; selected_option = (selected_option <= 0) ? option_count : selected_option - 1;
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
} }
@ -705,13 +717,12 @@ InventoryResult GuiController::TitleOptions()
} }
else if (menu_to_display == Menu::LoadGame) else if (menu_to_display == Menu::LoadGame)
{ {
if (!SavegameInfos[selected_option].Present) if (!SavegameInfos[selected_save_slot].Present)
SayNo(); SayNo();
else else
{ {
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
g_GameFlow->SelectedSaveGame = selected_option; g_GameFlow->SelectedSaveGame = selected_save_slot;
selected_option = 0;
ret = InventoryResult::LoadGame; ret = InventoryResult::LoadGame;
} }
} }
@ -1250,8 +1261,10 @@ InventoryResult GuiController::DoPauseMenu()
if (goSelect) if (goSelect)
{ {
if (menu_to_display == Menu::Pause) switch (menu_to_display)
{ {
case Menu::Pause:
switch (selected_option) switch (selected_option)
{ {
case 0: case 0:
@ -1268,9 +1281,10 @@ InventoryResult GuiController::DoPauseMenu()
invMode = InventoryMode::None; invMode = InventoryMode::None;
return InventoryResult::ExitToTitle; return InventoryResult::ExitToTitle;
} }
} break;
else if (menu_to_display == Menu::Options)
{ case Menu::Options:
switch (selected_option) switch (selected_option)
{ {
case 0: case 0:
@ -1289,6 +1303,12 @@ InventoryResult GuiController::DoPauseMenu()
menu_to_display = Menu::Sound; menu_to_display = Menu::Sound;
break; 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; OldBinocular = BinocularRange;
Lara.Inventory.OldBusy = false; Lara.Inventory.OldBusy = false;
BinocularRange = 0; BinocularRange = 0;
LaraItem->MeshBits = -1; LaraItem->MeshBits = ALL_JOINT_BITS;
invobject = rings[(int)RingTypes::Inventory]->current_object_list[rings[(int)RingTypes::Inventory]->curobjinlist].invitem; invobject = rings[(int)RingTypes::Inventory]->current_object_list[rings[(int)RingTypes::Inventory]->curobjinlist].invitem;
gmeobject = inventry_objects_list[invobject].object_number; gmeobject = inventry_objects_list[invobject].object_number;
@ -2250,7 +2270,7 @@ void GuiController::UseCurrentItem()
} }
} }
void GuiController::HandleInventoryMenu() void GuiController::DoInventory()
{ {
int n; int n;
unsigned __int64 opts; unsigned __int64 opts;
@ -2260,7 +2280,7 @@ void GuiController::HandleInventoryMenu()
if (rings[(int)RingTypes::Ammo]->ringactive) 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) if (rings[(int)RingTypes::Inventory]->objlistmovement)
return; return;
@ -2452,12 +2472,12 @@ void GuiController::HandleInventoryMenu()
{ {
if (i == current_selected_option) 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; ypos += font_height;
} }
else 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; ypos += font_height;
} }
} }
@ -2710,7 +2730,7 @@ void GuiController::DrawAmmoSelector()
yrot = ammo_object_list[n].yrot; yrot = ammo_object_list[n].yrot;
zrot = ammo_object_list[n].zrot; zrot = ammo_object_list[n].zrot;
x = phd_centerx - 300 + xpos; x = phd_centerx - 300 + xpos;
y = 430; y = 480;
short obj = ConvertInventoryItemToObject(ammo_object_list[n].invitem); short obj = ConvertInventoryItemToObject(ammo_object_list[n].invitem);
float scaler = inventry_objects_list[ammo_object_list[n].invitem].scale1; 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)); 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) 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) if (n == *current_ammo_type)
@ -3028,7 +3048,7 @@ void GuiController::DrawCurrentObjectList(int ringnum)
else else
objmeup = (int)((phd_winymax + 1) * 0.0625 * 3.0 + phd_centery); 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) if (!i && !rings[ringnum]->objlistmovement)
@ -3083,7 +3103,7 @@ void GuiController::DrawCurrentObjectList(int ringnum)
int x, y, y2; int x, y, y2;
x = 400 + xoff + i * OBJLIST_SPACING; x = 400 + xoff + i * OBJLIST_SPACING;
y = 150; y = 150;
y2 = 430;//combine y2 = 480;//combine
short obj = ConvertInventoryItemToObject(rings[ringnum]->current_object_list[n].invitem); short obj = ConvertInventoryItemToObject(rings[ringnum]->current_object_list[n].invitem);
float scaler = inventry_objects_list[rings[ringnum]->current_object_list[n].invitem].scale1; 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); 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; return return_value;
DoDebouncedInput(); 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(); DrawInventory();
DrawCompass(); 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) if (useItem && !TrInput)
val = 1; val = 1;
@ -3310,7 +3341,7 @@ void GuiController::DoDiary()
short GuiController::GetLoadSaveSelection() short GuiController::GetLoadSaveSelection()
{ {
return selected_slot; return selected_save_slot;
} }
int GuiController::DoLoad() int GuiController::DoLoad()
@ -3321,30 +3352,30 @@ int GuiController::DoLoad()
{ {
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
if (selected_slot == SAVEGAME_MAX - 1) if (selected_save_slot == SAVEGAME_MAX - 1)
selected_slot -= SAVEGAME_MAX - 1; //go back up selected_save_slot -= SAVEGAME_MAX - 1; //go back up
else else
selected_slot++; selected_save_slot++;
} }
if (goUp) if (goUp)
{ {
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
if (selected_slot== 0) if (selected_save_slot== 0)
selected_slot += SAVEGAME_MAX - 1; //go back down selected_save_slot += SAVEGAME_MAX - 1; //go back down
else else
selected_slot--; selected_save_slot--;
} }
if (goSelect) if (goSelect)
{ {
if (!SavegameInfos[selected_slot].Present) if (!SavegameInfos[selected_save_slot].Present)
SayNo(); SayNo();
else else
{ {
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
g_GameFlow->SelectedSaveGame = selected_slot; g_GameFlow->SelectedSaveGame = selected_save_slot;
ExitInvLoop = 1; ExitInvLoop = 1;
return 1; return 1;
} }
@ -3368,26 +3399,26 @@ void GuiController::DoSave()
{ {
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
if (selected_slot == SAVEGAME_MAX - 1) if (selected_save_slot == SAVEGAME_MAX - 1)
selected_slot -= SAVEGAME_MAX - 1; //go back up selected_save_slot -= SAVEGAME_MAX - 1; //go back up
else else
selected_slot++; selected_save_slot++;
} }
if (goUp) if (goUp)
{ {
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always);
if (selected_slot == 0) if (selected_save_slot == 0)
selected_slot += SAVEGAME_MAX - 1; //go back down selected_save_slot += SAVEGAME_MAX - 1; //go back down
else else
selected_slot--; selected_save_slot--;
} }
if (goSelect) if (goSelect)
{ {
SoundEffect(SFX_TR4_MENU_CHOOSE, nullptr, SoundEnvironment::Always); 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 ExitInvLoop = 1; //exit inv if the user has saved
} }

View file

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

View file

@ -13,6 +13,7 @@
#include "Specific/level.h" #include "Specific/level.h"
using namespace TEN::Renderer; using namespace TEN::Renderer;
short PickupX; short PickupX;
short PickupY; short PickupY;
short CurrentPickup; short CurrentPickup;
@ -31,6 +32,18 @@ extern RendererHUDBar* g_AirBar;
bool EnableSmoothHealthBar = true; 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) void DrawHealthBarOverlay(ItemInfo* item, int value)
{ {
auto* lara = GetLaraInfo(item); auto* lara = GetLaraInfo(item);
@ -43,7 +56,7 @@ void DrawHealthBarOverlay(ItemInfo* item, int value)
else else
color2 = 0xA00000; 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); auto* lara = GetLaraInfo(item);
if (CurrentLevel) 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) void UpdateHealthBar(ItemInfo* item, int flash)
@ -141,7 +154,7 @@ void UpdateHealthBar(ItemInfo* item, int flash)
void DrawAirBar(float value) void DrawAirBar(float value)
{ {
if (CurrentLevel) 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) void UpdateAirBar(ItemInfo* item, int flash)
@ -180,7 +193,7 @@ void UpdateAirBar(ItemInfo* item, int flash)
void DrawSprintBar(float value) void DrawSprintBar(float value)
{ {
if (CurrentLevel) 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) 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; short ObjectNumber;
}; };
void DrawHUD(ItemInfo* item);
void DrawHealthBarOverlay(ItemInfo* item, int value); void DrawHealthBarOverlay(ItemInfo* item, int value);
void DrawHealthBar(ItemInfo* item, float value); void DrawHealthBar(ItemInfo* item, float value);
void UpdateHealthBar(ItemInfo* item, int flash); void UpdateHealthBar(ItemInfo* item, int flash);

View file

@ -12,6 +12,98 @@
using namespace TEN::Floordata; 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) void ClearItem(short itemNumber)
{ {
auto* item = &g_Level.Items[itemNumber]; auto* item = &g_Level.Items[itemNumber];
@ -76,13 +168,10 @@ void KillItem(short const itemNumber)
if (Objects[item->ObjectNumber].floor != nullptr) if (Objects[item->ObjectNumber].floor != nullptr)
UpdateBridgeItem(itemNumber, true); UpdateBridgeItem(itemNumber, true);
g_GameScriptEntities->NotifyKilled(item); g_GameScriptEntities->NotifyKilled(item);
g_GameScriptEntities->TryRemoveColliding(itemNumber, true); g_GameScriptEntities->TryRemoveColliding(itemNumber, true);
if (!item->LuaCallbackOnKilledName.empty()) if (!item->LuaCallbackOnKilledName.empty())
{
g_GameScript->ExecuteFunction(item->LuaCallbackOnKilledName, itemNumber); g_GameScript->ExecuteFunction(item->LuaCallbackOnKilledName, itemNumber);
}
item->LuaName.clear(); item->LuaName.clear();
item->LuaCallbackOnKilledName.clear(); item->LuaCallbackOnKilledName.clear();
@ -96,10 +185,7 @@ void KillItem(short const itemNumber)
NextItemFree = itemNumber; NextItemFree = itemNumber;
} }
else else
{
item->Flags |= IFLAG_KILLED; item->Flags |= IFLAG_KILLED;
}
} }
} }
@ -383,11 +469,11 @@ void InitialiseItem(short itemNumber)
item->MeshBits = 1; item->MeshBits = 1;
} }
else else
item->MeshBits = -1; item->MeshBits = ALL_JOINT_BITS;
item->TouchBits = 0; item->TouchBits = NO_JOINT_BITS;
item->AfterDeath = 0; item->AfterDeath = 0;
item->SwapMeshFlags = 0; item->MeshSwapBits = NO_JOINT_BITS;
if (item->Flags & IFLAG_INVISIBLE) if (item->Flags & IFLAG_INVISIBLE)
{ {

View file

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

View file

@ -67,7 +67,7 @@ void ControlMissile(short fxNumber)
// fx->frameNumber = -GetRandomControl()/11000; // fx->frameNumber = -GetRandomControl()/11000;
// fx->counter = 6; // fx->counter = 6;
// fx->objectNumber = RICOCHET1; // 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) /*else if (fx->objectNumber == DRAGON_FIRE)
{ {

View file

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

View file

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

View file

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

View file

@ -8,6 +8,7 @@
#include "Game/control/flipeffect.h" #include "Game/control/flipeffect.h"
#include "Game/control/lot.h" #include "Game/control/lot.h"
#include "Game/effects/lara_fx.h" #include "Game/effects/lara_fx.h"
#include "Game/effects/effects.h"
#include "Game/items.h" #include "Game/items.h"
#include "Game/itemdata/creature_info.h" #include "Game/itemdata/creature_info.h"
#include "Game/Lara/lara.h" #include "Game/Lara/lara.h"
@ -90,10 +91,10 @@ bool SaveGame::Save(int slot)
Save::SaveGameHeaderBuilder sghb{ fbb }; Save::SaveGameHeaderBuilder sghb{ fbb };
sghb.add_level_name(levelNameOffset); sghb.add_level_name(levelNameOffset);
sghb.add_days((GameTimer / 30) / 8640); sghb.add_days((GameTimer / FPS) / 8640);
sghb.add_hours(((GameTimer / 30) % 86400) / 3600); sghb.add_hours(((GameTimer / FPS) % 86400) / 3600);
sghb.add_minutes(((GameTimer / 30) / 60) % 6); sghb.add_minutes(((GameTimer / FPS) / 60) % 6);
sghb.add_seconds((GameTimer / 30) % 60); sghb.add_seconds((GameTimer / FPS) % 60);
sghb.add_level(CurrentLevel); sghb.add_level(CurrentLevel);
sghb.add_timer(GameTimer); sghb.add_timer(GameTimer);
sghb.add_count(++LastSaveGame); sghb.add_count(++LastSaveGame);
@ -426,6 +427,9 @@ bool SaveGame::Save(int slot)
auto itemFlagsOffset = fbb.CreateVector(itemFlags); auto itemFlagsOffset = fbb.CreateVector(itemFlags);
flatbuffers::Offset<Save::Creature> creatureOffset; flatbuffers::Offset<Save::Creature> creatureOffset;
flatbuffers::Offset<Save::QuadBike> quadOffset;
flatbuffers::Offset<Save::UPV> upvOffset;
flatbuffers::Offset<Save::Short> shortOffset; flatbuffers::Offset<Save::Short> shortOffset;
flatbuffers::Offset<Save::Int> intOffset; flatbuffers::Offset<Save::Int> intOffset;
@ -467,6 +471,45 @@ bool SaveGame::Save(int slot)
creatureBuilder.add_ai_target_number(creature->AITargetNumber); creatureBuilder.add_ai_target_number(creature->AITargetNumber);
creatureOffset = creatureBuilder.Finish(); 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>()) else if (itemToSerialize.Data.is<short>())
{ {
Save::ShortBuilder sb{ fbb }; Save::ShortBuilder sb{ fbb };
@ -521,7 +564,7 @@ bool SaveGame::Save(int slot)
serializedItem.add_ai_bits(itemToSerialize.AIBits); serializedItem.add_ai_bits(itemToSerialize.AIBits);
serializedItem.add_collidable(itemToSerialize.Collidable); serializedItem.add_collidable(itemToSerialize.Collidable);
serializedItem.add_looked_at(itemToSerialize.LookedAt); 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 if (Objects[itemToSerialize.ObjectNumber].intelligent
&& itemToSerialize.Data.is<CreatureInfo>()) && itemToSerialize.Data.is<CreatureInfo>())
@ -529,6 +572,16 @@ bool SaveGame::Save(int slot)
serializedItem.add_data_type(Save::ItemData::Creature); serializedItem.add_data_type(Save::ItemData::Creature);
serializedItem.add_data(creatureOffset.Union()); 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>()) else if (itemToSerialize.Data.is<short>())
{ {
serializedItem.add_data_type(Save::ItemData::Short); serializedItem.add_data_type(Save::ItemData::Short);
@ -627,6 +680,59 @@ bool SaveGame::Save(int slot)
} }
auto staticMeshesOffset = fbb.CreateVector(staticMeshes); 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 // Particle enemies
std::vector<flatbuffers::Offset<Save::BatInfo>> bats; std::vector<flatbuffers::Offset<Save::BatInfo>> bats;
for (int i = 0; i < NUM_BATS; i++) for (int i = 0; i < NUM_BATS; i++)
@ -905,6 +1011,7 @@ bool SaveGame::Save(int slot)
sgb.add_flip_timer(0); sgb.add_flip_timer(0);
sgb.add_static_meshes(staticMeshesOffset); sgb.add_static_meshes(staticMeshesOffset);
sgb.add_fixed_cameras(camerasOffset); sgb.add_fixed_cameras(camerasOffset);
sgb.add_particles(particleOffset);
sgb.add_bats(batsOffset); sgb.add_bats(batsOffset);
sgb.add_rats(ratsOffset); sgb.add_rats(ratsOffset);
sgb.add_spiders(spidersOffset); sgb.add_spiders(spidersOffset);
@ -954,6 +1061,23 @@ bool SaveGame::Load(int slot)
const Save::SaveGame* s = Save::GetSaveGame(buffer.get()); 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 // Flipmaps
for (int i = 0; i < s->flip_stats()->size(); i++) for (int i = 0; i < s->flip_stats()->size(); i++)
{ {
@ -1112,6 +1236,25 @@ bool SaveGame::Load(int slot)
item->Collidable = savedItem->collidable(); item->Collidable = savedItem->collidable();
item->LookedAt = savedItem->looked_at(); 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 // Creature data for intelligent items
if (item->ObjectNumber != ID_LARA && obj->intelligent && (savedItem->flags() & (TRIGGERED | CODE_BITS | ONESHOT))) 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(); creature->Tosspad = savedCreature->tosspad();
SetBaddyTarget(i, savedCreature->ai_target_number()); 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) else if (savedItem->data_type() == Save::ItemData::Short)
{ {
auto data = savedItem->data(); auto data = savedItem->data();
@ -1163,29 +1341,50 @@ bool SaveGame::Load(int slot)
auto savedData = (Save::Int*)data; auto savedData = (Save::Int*)data;
item->Data = savedData->scalar(); 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 particle->x = particleInfo->x();
item->MeshBits = savedItem->mesh_bits(); particle->y = particleInfo->y();
item->SwapMeshFlags = savedItem->swap_mesh_flags(); particle->z = particleInfo->z();
particle->xVel = particleInfo->x_vel();
// Now some post-load specific hacks for objects particle->yVel = particleInfo->y_vel();
if (item->ObjectNumber >= ID_PUZZLE_HOLE1 particle->zVel = particleInfo->z_vel();
&& item->ObjectNumber <= ID_PUZZLE_HOLE16 particle->gravity = particleInfo->gravity();
&& (item->Status == ITEM_ACTIVE particle->rotAng = particleInfo->rot_ang();
|| item->Status == ITEM_DEACTIVATED)) particle->flags = particleInfo->flags();
{ particle->sSize = particleInfo->s_size();
item->ObjectNumber = (GAME_OBJECT_ID)((int)item->ObjectNumber + ID_PUZZLE_DONE1 - ID_PUZZLE_HOLE1); particle->dSize = particleInfo->d_size();
item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + savedItem->anim_number(); particle->size = particleInfo->size();
} particle->friction = particleInfo->friction();
particle->scalar = particleInfo->scalar();
if ((item->ObjectNumber >= ID_SMASH_OBJECT1) particle->spriteIndex = particleInfo->sprite_index();
&& (item->ObjectNumber <= ID_SMASH_OBJECT8) particle->rotAdd = particleInfo->rot_add();
&& (item->Flags & ONESHOT)) particle->maxYvel = particleInfo->max_y_vel();
item->MeshBits = 0x00100; particle->on = particleInfo->on();
particle->sR = particleInfo->s_r();
if (obj->floor != nullptr) particle->sG = particleInfo->s_g();
UpdateBridgeItem(i); 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++) for (int i = 0; i < s->bats()->size(); i++)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,6 +6,7 @@
#include "Game/effects/effects.h" #include "Game/effects/effects.h"
#include "Game/items.h" #include "Game/items.h"
#include "Sound/sound.h" #include "Sound/sound.h"
#include "Renderer/Renderer11Enums.h"
namespace TEN::Entities::Traps namespace TEN::Entities::Traps
{ {
@ -138,7 +139,7 @@ namespace TEN::Entities::Traps
AddActiveItem(dartItemNumber); AddActiveItem(dartItemNumber);
dartItem->Status = ITEM_ACTIVE; 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) if (dx < -16384 || dx > 16384 || dz < -16384 || dz > 16384)
return; return;
SPARKS* spark = &Sparks[GetFreeSpark()]; auto* spark = GetFreeParticle();
spark->on = true; spark->on = true;
@ -165,7 +166,7 @@ namespace TEN::Entities::Traps
spark->colFadeSpeed = 8; spark->colFadeSpeed = 8;
spark->fadeToBlack = 4; spark->fadeToBlack = 4;
spark->transType = TransTypeEnum::COLADD; spark->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
spark->life = spark->sLife = (GetRandomControl() & 3) + 32; 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_FALL_ROTATION_SPEED = 1;
constexpr auto FALLINGBLOCK_DELAY = 52; constexpr auto FALLINGBLOCK_DELAY = 52;
constexpr auto FALLINGBLOCK_WIBBLE = 3; constexpr auto FALLINGBLOCK_WIBBLE = 3;
constexpr auto FALLINGBLOCK_HEIGHT_TOLERANCE = 8;
constexpr auto FALLINGBLOCK_CRUMBLE_DELAY = 100; constexpr auto FALLINGBLOCK_CRUMBLE_DELAY = 100;
void InitialiseFallingBlock(short itemNumber) void InitialiseFallingBlock(short itemNumber)
@ -36,7 +37,7 @@ void InitialiseFallingBlock(short itemNumber)
void FallingBlockCollision(short itemNum, ItemInfo* l, CollisionInfo* coll) void FallingBlockCollision(short itemNum, ItemInfo* l, CollisionInfo* coll)
{ {
ItemInfo* item = &g_Level.Items[itemNum]; 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)) 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; item->Pose.Position.y += item->Animation.VerticalVelocity;
if (item->Pose.Position.y < item2->Pose.Position.y + 1644) 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->ItemFlags[0] = 1;
item->Pose.Position.y = item2->Pose.Position.y + 1644; item->Pose.Position.y = item2->Pose.Position.y + 1644;
if (item->Animation.VerticalVelocity < -32) if (item->Animation.VerticalVelocity < -32)
@ -277,7 +277,7 @@ void WreckingBallControl(short itemNumber)
} }
else if (!item->ItemFlags[0]) 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; 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) && bool actionReady = (TrInput & IN_ACTION || g_Gui.GetInventoryItemChosen() != NO_ITEM);
!BinocularRange &&
laraItem->Animation.ActiveState == LS_IDLE && bool laraAvailable = !BinocularRange &&
laraItem->Animation.AnimNumber == LA_STAND_IDLE) && laraItem->Animation.ActiveState == LS_IDLE &&
laraInfo->Control.HandStatus == HandStatus::Free && laraItem->Animation.AnimNumber == LA_STAND_IDLE;
(!laraInfo->Control.IsMoving || laraInfo->InteractedItem != itemNumber))
{ bool actionActive = laraInfo->Control.IsMoving && laraInfo->InteractedItem == itemNumber;
if (keyHoleItem->ObjectNumber < ID_KEY_HOLE6)
ObjectCollision(itemNumber, laraItem, coll); if (actionActive || (actionReady && laraAvailable))
}
else
{ {
if (TestLaraPosition(&KeyHoleBounds, keyHoleItem, laraItem)) 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)) if (g_Gui.GetInventoryItemChosen() != keyHoleItem->ObjectNumber - (ID_KEY_HOLE1 - ID_KEY_ITEM1))
return; return;
laraInfo->InteractedItem = itemNumber;
} }
if (laraInfo->InteractedItem != itemNumber)
return;
if (MoveLaraPosition(&KeyHolePosition, keyHoleItem, laraItem)) if (MoveLaraPosition(&KeyHolePosition, keyHoleItem, laraItem))
{ {
if (keyHoleItem->ObjectNumber == ID_KEY_HOLE8) if (keyHoleItem->ObjectNumber == ID_KEY_HOLE8)
@ -319,8 +322,6 @@ void KeyHoleCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
return; return;
} }
} }
else
laraInfo->InteractedItem = itemNumber;
g_Gui.SetInventoryItemChosen(NO_ITEM); g_Gui.SetInventoryItemChosen(NO_ITEM);
return; return;
@ -332,6 +333,11 @@ void KeyHoleCollision(short itemNumber, ItemInfo* laraItem, CollisionInfo* coll)
laraInfo->Control.HandStatus = HandStatus::Free; laraInfo->Control.HandStatus = HandStatus::Free;
} }
} }
else
{
if (keyHoleItem->ObjectNumber < ID_KEY_HOLE6)
ObjectCollision(itemNumber, laraItem, coll);
}
return; return;
} }

View file

@ -10,288 +10,291 @@
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
BITE_INFO ApeBite = { 0, -19, 75, 15 }; namespace TEN::Entities::TR1
#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
{ {
APE_STATE_NONE = 0, BITE_INFO ApeBite = { 0, -19, 75, 15 };
APE_STATE_IDLE = 1, const std::vector<int> ApeAttackJoints = { 8, 9, 10, 11, 12, 13, 14, 15 };
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 constexpr auto APE_ATTACK_DAMAGE = 200;
enum ApeAnim
{
APE_ANIM_DEATH = 7,
APE_ANIM_VAULT = 19, #define RUN_TURN ANGLE(5.0f)
};
enum ApeFlags #define DISPLAY_ANGLE ANGLE(45.0f)
{
APE_FLAG_ATTACK = 1,
APE_FLAG_TURN_LEFT = 2,
APE_FLAG_TURN_RIGHT = 4
};
void ApeVault(short itemNumber, short angle) #define ATTACK_RANGE pow(430, 2)
{ #define PANIC_RANGE pow(SECTOR(2), 2)
auto* item = &g_Level.Items[itemNumber];
auto* creature = GetCreatureInfo(item);
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); APE_STATE_NONE = 0,
creature->Flags -= APE_FLAG_TURN_LEFT; APE_STATE_IDLE = 1,
} APE_STATE_WALK = 2,
else if (item->Flags & APE_FLAG_TURN_RIGHT) 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); APE_ANIM_DEATH = 7,
creature->Flags -= APE_FLAG_TURN_RIGHT;
}
long long xx = item->Pose.Position.z / SECTOR(1); APE_ANIM_VAULT = 19,
long long yy = item->Pose.Position.x / SECTOR(1); };
long long y = item->Pose.Position.y;
CreatureAnimation(itemNumber, angle, 0); enum ApeFlags
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)
{ {
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; 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; if (yy == yFloor)
item->Pose.Orientation.y = ANGLE(90.0f); return;
}
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
}
switch (CreatureVault(itemNumber, angle, 2, SHIFT)) if (yy < yFloor)
{
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)
{ {
item->Pose.Orientation.y -= ANGLE(90); item->Pose.Position.x = (yFloor * SECTOR(1)) - SHIFT;
creatureInfo->Flags -= APE_FLAG_TURN_LEFT; item->Pose.Orientation.y = ANGLE(90.0f);
}
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 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; break;
case APE_STATE_RUN: default:
creatureInfo->MaxTurn = RUN_TURN; return;
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;
} }
} }
CreatureJoint(item, 0, head); void ApeControl(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
if (item->Animation.ActiveState != APE_STATE_VAULT) auto* item = &g_Level.Items[itemNumber];
ApeVault(itemNumber, angle); auto* creatureInfo = GetCreatureInfo(item);
else
CreatureAnimation(itemNumber, angle, 0); 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 #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/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
BITE_INFO BearBite = { 0, 96, 335, 14 }; namespace TEN::Entities::TR1
#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
{ {
BEAR_STATE_STROLL = 0, BITE_INFO BearBite = { 0, 96, 335, 14 };
BEAR_STATE_IDLE = 1, const std::vector<int> BearAttackJoints = { 2, 3, 5, 6, 14, 17 };
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
};
// TODO #define ROAR_CHANCE 0x50
enum BearAnim #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
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)); 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: case BEAR_STATE_WALK:
{ {
item->Animation.TargetState = BEAR_STATE_REAR; item->Animation.TargetState = BEAR_STATE_REAR;
@ -90,7 +92,7 @@ void BearControl(short itemNumber)
} }
case BEAR_STATE_DEATH: case BEAR_STATE_DEATH:
{ {
if (creature->Flags && item->TouchBits & TOUCH) if (creature->Flags && item->TestBits(JointBitType::Touch, BearAttackJoints))
{ {
creature->Flags = 0; creature->Flags = 0;
@ -100,157 +102,160 @@ void BearControl(short itemNumber)
break; break;
} }
}
} }
} else
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)
{ {
case BEAR_STATE_IDLE: AI_INFO AI;
if (laraDead) 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) case BEAR_STATE_IDLE:
item->Animation.TargetState = BEAR_STATE_CHOMP; if (laraDead)
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)
{ {
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; 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); CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, 0); CreatureAnimation(itemNumber, angle, 0);
}
} }

View file

@ -1,3 +1,6 @@
#pragma once #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/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
static BITE_INFO BigRatBite = { 0, -11, 108, 3 }; namespace TEN::Entities::TR1
#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
{ {
BIG_RAT_STATE_EMPTY = 0, static BITE_INFO BigRatBite = { 0, -11, 108, 3 };
BIG_RAT_STATE_IDLE = 1, const std::vector<int> BigRatAttackJoints = { 0, 1, 2, 3, 7, 8, 24, 25 };
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 #define BIG_RAT_RUN_TURN ANGLE(6.0f)
{ #define BIG_RAT_SWIM_TURN ANGLE(3.0f)
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
};
void InitialiseBigRat(short itemNumber) constexpr auto DEFAULT_SWIM_UPDOWN_SPEED = 32;
{ constexpr auto BIG_RAT_ALERT_RANGE = SQUARE(SECTOR(1) + CLICK(2));
auto* item = &g_Level.Items[itemNumber]; constexpr auto BIG_RAT_VISIBILITY_RANGE = SQUARE(SECTOR(5));
InitialiseCreature(itemNumber); 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; BIG_RAT_STATE_EMPTY = 0,
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase; BIG_RAT_STATE_IDLE = 1,
item->Animation.ActiveState = BIG_RAT_STATE_SWIM; BIG_RAT_STATE_CHARGE_ATTACK = 2,
item->Animation.TargetState = BIG_RAT_STATE_SWIM; BIG_RAT_STATE_RUN = 3,
} BIG_RAT_STATE_BITE_ATTACK = 4,
else 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; BIG_RAT_ANIM_EMPTY = 0,
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase; BIG_RAT_ANIM_STOP_TO_RUN = 1,
item->Animation.ActiveState = BIG_RAT_STATE_IDLE; BIG_RAT_ANIM_RUN = 2,
item->Animation.TargetState = BIG_RAT_STATE_IDLE; 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) void InitialiseBigRat(short itemNumber)
{
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); auto* item = &g_Level.Items[itemNumber];
creature->LOT.Drop = -SECTOR(20); InitialiseCreature(itemNumber);
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)) 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) item->Animation.AnimNumber = Objects[item->ObjectNumber].animIndex + BIG_RAT_ANIM_SWIM;
creature->Alerted = true; item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = BIG_RAT_STATE_SWIM;
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.TargetState = 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); if (RatIsInWater(item))
CreatureAnimation(itemNumber, angle, 0); {
CreatureUnderwater(item, 0);
if (RatIsInWater(item)) item->Pose.Position.y = waterHeight;
{ }
CreatureUnderwater(item, 0); else
item->Pose.Position.y = waterHeight; item->Pose.Position.y = item->Floor;
} }
else
item->Pose.Position.y = item->Floor;
} }

View file

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

View file

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

View file

@ -14,129 +14,132 @@
// - Bacon Lara cannot be targeted. // - Bacon Lara cannot be targeted.
// - Bacon Lara cannot move like Lara. // - Bacon Lara cannot move like Lara.
// Original: namespace TEN::Entities::TR1
void InitialiseDoppelganger(short itemNumber)
{ {
ClearItem(itemNumber); // Original:
} void InitialiseDoppelganger(short itemNumber)
ItemInfo* FindReference(ItemInfo* item, short objectNumber)
{
bool found = false;
int itemNumber;
for (int i = 0; i < g_Level.NumItems; i++)
{ {
auto* currentItem = &g_Level.Items[i]; ClearItem(itemNumber);
if (currentItem->ObjectNumber == objectNumber && currentItem->RoomNumber == item->RoomNumber)
{
itemNumber = i;
found = true;
}
} }
if (!found) ItemInfo* FindReference(ItemInfo* item, short objectNumber)
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)
{ {
item->HitPoints = 1000; bool found = false;
LaraItem->HitPoints -= GetWeaponDamage(Lara.Control.Weapon.GunType); 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); static short GetWeaponDamage(LaraWeaponType weaponType)
if (item->Data == NULL)
{ {
Vector3Int pos; return short(Weapons[(int)weaponType].Damage) * 25;
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->Floor = GetCollision(item).Position.Floor; void DoppelgangerControl(short itemNumber)
if (item->Pose.Position.y >= item->Floor) {
auto* item = &g_Level.Items[itemNumber];
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); TestTriggers(item, true);
item->Animation.VerticalVelocity = 0; item->Floor = GetCollision(item).Position.Floor;
item->Animation.Airborne = false; if (item->Pose.Position.y >= item->Floor)
item->Animation.TargetState = LS_DEATH; {
item->Animation.RequiredState = LS_DEATH; 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. // TODO: DrawLara not exist ! use Renderer11.cpp DrawLara instead or create DrawLara() function with old behaviour.
void DrawEvilLara(ItemInfo* item) 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++)
{ {
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 #pragma once
void InitialiseDoppelganger(short itemNumber); namespace TEN::Entities::TR1
void DoppelgangerControl(short itemNumber); {
void InitialiseDoppelganger(short itemNumber);
void DoppelgangerControl(short itemNumber);
}

View file

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

View file

@ -1,3 +1,6 @@
#pragma once #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/level.h"
#include "Specific/trmath.h" #include "Specific/trmath.h"
BITE_INFO NatlaGunBite = { 5, 220, 7, 4 }; namespace TEN::Entities::TR1
enum NatlaState
{ {
NATLA_STATE_NONE, BITE_INFO NatlaGunBite = { 5, 220, 7, 4 };
NATLA_STATE_IDLE,
NATLA_STATE_FLY, enum NatlaState
NATLA_STATE_RUN, {
NATLA_STATE_AIM, NATLA_STATE_NONE,
NATLA_STATE_SEMI_DEATH, NATLA_STATE_IDLE,
NATLA_STATE_SHOOT, NATLA_STATE_FLY,
NATLA_STATE_FALL, NATLA_STATE_RUN,
NATLA_STATE_STAND, NATLA_STATE_AIM,
NATLA_STATE_DEATH NATLA_STATE_SEMI_DEATH,
}; NATLA_STATE_SHOOT,
NATLA_STATE_FALL,
NATLA_STATE_STAND,
NATLA_STATE_DEATH
};
#define NATLA_NEAR_DEATH 200 #define NATLA_NEAR_DEATH 200
#define NATLA_FLYMODE 0x8000 #define NATLA_FLYMODE 0x8000
@ -35,262 +37,263 @@ enum NatlaState
#define NATLA_FLY_TURN ANGLE(5.0f) #define NATLA_FLY_TURN ANGLE(5.0f)
#define NATLA_RUN_TURN ANGLE(6.0f) #define NATLA_RUN_TURN ANGLE(6.0f)
#define NATLA_LAND_CHANCE 0x100 #define NATLA_LAND_CHANCE 0x100
#define NATLA_DEATH_TIME (30 * 16) #define NATLA_DEATH_TIME (FPS * 16)
#define NATLA_SHOT_DAMAGE 100 #define NATLA_SHOT_DAMAGE 100
void NatlaControl(short itemNumber) 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)
{ {
creature->LOT.Step = CLICK(1); if (!CreatureActive(itemNumber))
creature->LOT.Drop = -CLICK(1); return;
creature->LOT.Fly = NO_FLYING;
CreatureAIInfo(item, &AI);
if (AI.ahead) auto* item = &g_Level.Items[itemNumber];
head = AI.angle; auto* creature = GetCreatureInfo(item);
GetCreatureMood(item, &AI, VIOLENT); short head = 0;
CreatureMood(item, &AI, VIOLENT); short angle = 0;
short tilt = 0;
short facing = 0;
short gun = creature->JointRotation[0] * 7 / 8;
angle = CreatureTurn(item, NATLA_RUN_TURN); int shoot;
shoot = (AI.angle > -NATLA_FIRE_ARC && AI.angle < NATLA_FIRE_ARC && Targetable(item, &AI)); 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; creature->LOT.Step = CLICK(1);
facing = 0; creature->LOT.Drop = -CLICK(1);
} creature->LOT.Fly = NO_FLYING;
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;
CreatureAIInfo(item, &AI); CreatureAIInfo(item, &AI);
}
else if (!shoot)
creature->Flags |= NATLA_FLYMODE;
if (AI.ahead) if (AI.ahead)
head = AI.angle; head = AI.angle;
if (item->Animation.ActiveState != NATLA_STATE_FLY || (creature->Flags & NATLA_FLYMODE)) GetCreatureMood(item, &AI, VIOLENT);
CreatureMood(item, &AI, TIMID); CreatureMood(item, &AI, VIOLENT);
item->Pose.Orientation.y -= facing; angle = CreatureTurn(item, NATLA_RUN_TURN);
angle = CreatureTurn(item, NATLA_FLY_TURN); shoot = (AI.angle > -NATLA_FIRE_ARC && AI.angle < NATLA_FIRE_ARC&& Targetable(item, &AI));
if (item->Animation.ActiveState == NATLA_STATE_FLY) if (facing)
{ {
if (AI.angle > NATLA_FLY_TURN) item->Pose.Orientation.y += facing;
facing += NATLA_FLY_TURN; facing = 0;
else if (AI.angle < -NATLA_FLY_TURN) }
facing -= NATLA_FLY_TURN;
else
facing += AI.angle;
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 else
{ {
item->Pose.Orientation.y += facing - angle; creature->LOT.Step = CLICK(1);
facing = 0; creature->LOT.Drop = -CLICK(1);
} creature->LOT.Fly = NO_FLYING;
CreatureAIInfo(item, &AI);
switch (item->Animation.ActiveState) shoot = (AI.angle > -NATLA_FIRE_ARC && AI.angle < NATLA_FIRE_ARC&& Targetable(item, &AI));
{
case NATLA_STATE_IDLE:
timer = 0;
if (creature->Flags & NATLA_FLYMODE) if (item->Animation.ActiveState == NATLA_STATE_FLY && (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)
{ {
short FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun); if (creature->Flags & NATLA_FLYMODE && shoot && GetRandomControl() < NATLA_LAND_CHANCE)
if (FXNumber != NO_ITEM) 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]; short FXNumber = CreatureEffect(item, &NatlaGunBite, BombGun);
gun = fx->pos.Orientation.x; if (FXNumber != NO_ITEM)
SoundEffect(SFX_TR1_ATLANTEAN_WINGS, &fx->pos); {
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 #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/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
BITE_INFO WolfBite = { 0, -14, 174, 6 }; namespace TEN::Entities::TR1
enum WolfState
{ {
WOLF_STATE_NONE = 0, BITE_INFO WolfBite = { 0, -14, 174, 6 };
WOLF_STATE_IDLE = 1, const std::vector<int> WolfAttackJoints = { 0, 1, 2, 3, 6, 8, 9, 10, 12, 13, 14 };
WOLF_STATE_WALK = 2,
WOLF_STATE_RUN = 3, #define SLEEP_FRAME 96
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 #define ATTACK_RANGE pow(SECTOR(1.5f), 2)
enum WolfAnim #define STALK_RANGE pow(SECTOR(2), 2)
{
WOLF_ANIM_DEATH = 20,
};
#define TOUCH 0x774f #define BITE_DAMAGE 100
#define LUNGE_DAMAGE 50
#define SLEEP_FRAME 96 #define WAKE_CHANCE 0x20
#define SLEEP_CHANCE 0x20
#define HOWL_CHANCE 0x180
#define ATTACK_RANGE pow(SECTOR(1.5f), 2) #define WALK_TURN ANGLE(2.0f)
#define STALK_RANGE pow(SECTOR(2), 2) #define RUN_TURN ANGLE(5.0f)
#define STALK_TURN ANGLE(2.0f)
#define BITE_DAMAGE 100 enum WolfState
#define LUNGE_DAMAGE 50
#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)
{ {
if (item->Animation.ActiveState != WOLF_STATE_DEATH) WOLF_STATE_NONE = 0,
{ WOLF_STATE_IDLE = 1,
item->Animation.AnimNumber = Objects[ID_WOLF].animIndex + WOLF_ANIM_DEATH + (short)(GetRandomControl() / 11000); WOLF_STATE_WALK = 2,
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase; WOLF_STATE_RUN = 3,
item->Animation.ActiveState = WOLF_STATE_DEATH; 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; if (!CreatureActive(itemNumber))
CreatureAIInfo(item, &AI); return;
if (AI.ahead) auto* item = &g_Level.Items[itemNumber];
head = AI.angle; auto* creature = GetCreatureInfo(item);
GetCreatureMood(item, &AI, TIMID); short head = 0;
CreatureMood(item, &AI, TIMID); short angle = 0;
short tilt = 0;
angle = CreatureTurn(item, creature->MaxTurn); if (item->HitPoints <= 0)
switch (item->Animation.ActiveState)
{ {
case WOLF_STATE_SLEEP: if (item->Animation.ActiveState != WOLF_STATE_DEATH)
head = 0;
if (creature->Mood == MoodType::Escape || AI.zoneNumber == AI.enemyZone)
{ {
item->Animation.RequiredState = WOLF_STATE_CROUCH; item->Animation.AnimNumber = Objects[ID_WOLF].animIndex + WOLF_ANIM_DEATH + (short)(GetRandomControl() / 11000);
item->Animation.TargetState = WOLF_STATE_IDLE; 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; case WOLF_STATE_SLEEP:
item->Animation.TargetState = WOLF_STATE_IDLE; head = 0;
}
break; if (creature->Mood == MoodType::Escape || AI.zoneNumber == AI.enemyZone)
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))
{ {
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: case WOLF_STATE_IDLE:
creature->MaxTurn = RUN_TURN; if (item->Animation.RequiredState)
tilt = angle; item->Animation.TargetState = item->Animation.RequiredState;
else
item->Animation.TargetState = WOLF_STATE_WALK;
break;
if (AI.ahead && AI.distance < ATTACK_RANGE) case WOLF_STATE_WALK:
{ creature->MaxTurn = WALK_TURN;
if (AI.distance > (ATTACK_RANGE / 2) &&
(AI.enemyFacing > FRONT_ARC || AI.enemyFacing < -FRONT_ARC)) 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.RequiredState = WOLF_STATE_STALK;
item->Animation.TargetState = WOLF_STATE_CROUCH; 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; CreatureEffect(item, &WolfBite, DoBloodSplat);
item->Animation.RequiredState = WOLF_STATE_NONE; 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); CreatureTilt(item, tilt);
CreatureJoint(item, 0, head); CreatureJoint(item, 0, head);
CreatureAnimation(itemNumber, angle, tilt); CreatureAnimation(itemNumber, angle, tilt);
}
} }

View file

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

View file

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

View file

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

View file

@ -208,21 +208,21 @@ void SkidooManControl(short riderItemNumber)
if (creatureInfo->Flags) if (creatureInfo->Flags)
{ {
SoundEffect(SFX_TR4_BAD_TROOP_UZI, &item->Pose); SoundEffect(SFX_TR4_BADDY_UZI, &item->Pose);
creatureInfo->Flags--; creatureInfo->Flags--;
} }
} }
if (item->Animation.ActiveState == SMAN_STATE_WAIT) 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; creatureInfo->JointRotation[0] = 0;
} }
else else
{ {
creatureInfo->JointRotation[0] = (creatureInfo->JointRotation[0] == 1) ? 2 : 1; creatureInfo->JointRotation[0] = (creatureInfo->JointRotation[0] == 1) ? 2 : 1;
DoSnowEffect(item); 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); CreatureAnimation(itemNumber, angle, 0);

View file

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

View file

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

View file

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

View file

@ -32,12 +32,12 @@
#define BOAT_SLIP 10 #define BOAT_SLIP 10
#define BOAT_SIDE_SLIP 30 #define BOAT_SIDE_SLIP 30
#define BOAT_FRONT 750 #define BOAT_FRONT 750
#define BOAT_BACK -700
#define BOAT_SIDE 300 #define BOAT_SIDE 300
#define BOAT_RADIUS 500 #define BOAT_RADIUS 500
#define BOAT_SNOW 500 #define BOAT_SNOW 500
#define BOAT_MAX_HEIGHT CLICK(1) #define BOAT_MAX_HEIGHT CLICK(1)
#define DISMOUNT_DISTANCE SECTOR(1) #define DISMOUNT_DISTANCE SECTOR(1)
#define BOAT_WAKE 700
#define BOAT_SOUND_CEILING SECTOR(5) #define BOAT_SOUND_CEILING SECTOR(5)
#define BOAT_TIP (BOAT_FRONT + 250) #define BOAT_TIP (BOAT_FRONT + 250)
@ -99,83 +99,6 @@ void InitialiseSpeedBoat(short itemNumber)
sBoat->Pitch = 0; 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) BoatMountType GetSpeedBoatMountType(ItemInfo* laraItem, ItemInfo* sBoatItem, CollisionInfo* coll)
{ {
auto* lara = GetLaraInfo(laraItem); auto* lara = GetLaraInfo(laraItem);
@ -1030,6 +953,12 @@ void SpeedBoatControl(short itemNumber)
Camera.targetElevation = -ANGLE(20.0f); Camera.targetElevation = -ANGLE(20.0f);
Camera.targetDistance = SECTOR(2); 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 else
{ {
@ -1039,14 +968,12 @@ void SpeedBoatControl(short itemNumber)
sBoatItem->Pose.Orientation.z += sBoat->LeanAngle; 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) 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) if (lara->Vehicle != itemNumber)
return; return;

View file

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

View file

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

View file

@ -2,9 +2,11 @@
#include "Objects/TR3/Entity/tr3_flamethrower.h" #include "Objects/TR3/Entity/tr3_flamethrower.h"
#include "Game/animation.h" #include "Game/animation.h"
#include "Game/camera.h"
#include "Game/control/box.h" #include "Game/control/box.h"
#include "Game/control/lot.h" #include "Game/control/lot.h"
#include "Game/effects/effects.h" #include "Game/effects/effects.h"
#include "Game/effects/tomb4fx.h"
#include "Game/items.h" #include "Game/items.h"
#include "Game/itemdata/creature_info.h" #include "Game/itemdata/creature_info.h"
#include "Game/Lara/lara.h" #include "Game/Lara/lara.h"
@ -15,6 +17,7 @@
#include "Specific/setup.h" #include "Specific/setup.h"
BITE_INFO FlamethrowerBite = { 0, 340, 64, 7 }; BITE_INFO FlamethrowerBite = { 0, 340, 64, 7 };
Vector3Int FlamethrowerOffset = { 0, 340, 0 };
// TODO // TODO
enum FlamethrowerState 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) void FlameThrowerControl(short itemNumber)
{ {
if (!CreatureActive(itemNumber)) if (!CreatureActive(itemNumber))
@ -229,10 +52,12 @@ void FlameThrowerControl(short itemNumber)
if (item->Animation.ActiveState != 6 && item->Animation.ActiveState != 11) if (item->Animation.ActiveState != 6 && item->Animation.ActiveState != 11)
{ {
TriggerDynamicLight(pos.x, pos.y, pos.z, (random & 3) + 6, 24 - ((random / 16) & 3), 16 - ((random / 64) & 3), random & 3); TriggerDynamicLight(pos.x, pos.y, pos.z, (random & 3) + 6, 24 - ((random / 16) & 3), 16 - ((random / 64) & 3), random & 3);
TriggerPilotFlame(itemNumber); TriggerPilotFlame(itemNumber, 9);
} }
else else
TriggerDynamicLight(pos.x, pos.y, pos.z, (random & 3) + 10, 31 - ((random / 16) & 3), 24 - ((random / 64) & 3), random & 7); {
TriggerDynamicLight(pos.x, pos.y, pos.z, (random & 3) + 10, 31 - ((random / 16) & 3), 24 - ((random / 64) & 3), random & 7);
}
if (item->HitPoints <= 0) if (item->HitPoints <= 0)
{ {
@ -466,10 +291,10 @@ void FlameThrowerControl(short itemNumber)
item->Animation.TargetState = 1; item->Animation.TargetState = 1;
if (creature->Flags < 40) if (creature->Flags < 40)
TriggerFlameThrower(item, &FlamethrowerBite, creature->Flags); ThrowFire(itemNumber, FlamethrowerBite.meshNum, FlamethrowerOffset, Vector3Int(0, creature->Flags * 1.5f, 0));
else else
{ {
TriggerFlameThrower(item, &FlamethrowerBite, (GetRandomControl() & 31) + 12); ThrowFire(itemNumber, FlamethrowerBite.meshNum, FlamethrowerOffset, Vector3Int(0, (GetRandomControl() & 63) + 12, 0));
if (realEnemy) if (realEnemy)
{ {
/*code*/ /*code*/
@ -501,10 +326,10 @@ void FlameThrowerControl(short itemNumber)
item->Animation.TargetState = 2; item->Animation.TargetState = 2;
if (creature->Flags < 40) if (creature->Flags < 40)
TriggerFlameThrower(item, &FlamethrowerBite, creature->Flags); ThrowFire(itemNumber, FlamethrowerBite.meshNum, FlamethrowerOffset, Vector3Int(0, creature->Flags * 1.5f, 0));
else else
{ {
TriggerFlameThrower(item, &FlamethrowerBite, (GetRandomControl() & 31) + 12); ThrowFire(itemNumber, FlamethrowerBite.meshNum, FlamethrowerOffset, Vector3Int(0, (GetRandomControl() & 63) + 12, 0));
if (realEnemy) if (realEnemy)
{ {
/*code*/ /*code*/

View file

@ -44,7 +44,7 @@ void MonkeyControl(short itemNumber)
{ {
if (item->Animation.ActiveState != 11) if (item->Animation.ActiveState != 11)
{ {
item->MeshBits = -1; item->MeshBits = ALL_JOINT_BITS;
item->Animation.AnimNumber = Objects[ID_MONKEY].animIndex + 14; item->Animation.AnimNumber = Objects[ID_MONKEY].animIndex + 14;
item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase; item->Animation.FrameNumber = g_Level.Anims[item->Animation.AnimNumber].frameBase;
item->Animation.ActiveState = 11; item->Animation.ActiveState = 11;
@ -91,7 +91,7 @@ void MonkeyControl(short itemNumber)
if (item->CarriedItem != NO_ITEM) if (item->CarriedItem != NO_ITEM)
item->MeshBits = 0xFFFFFEFF; item->MeshBits = 0xFFFFFEFF;
else else
item->MeshBits = -1; item->MeshBits = ALL_JOINT_BITS;
} }
else else
{ {

View file

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

View file

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

View file

@ -16,6 +16,7 @@
#include "Sound/sound.h" #include "Sound/sound.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
#include "Renderer/Renderer11Enums.h"
using namespace TEN::Effects::Lara; using namespace TEN::Effects::Lara;
@ -62,7 +63,7 @@ enum TonyAnim
static BOSS_STRUCT BossData; static BOSS_STRUCT BossData;
#define TONY_TURN ANGLE(2.0f) #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 #define MAX_TONY_TRIGGER_RANGE 0x4000
void InitialiseTony(short itemNumber) 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) if (dx < -MAX_TONY_TRIGGER_RANGE || dx > MAX_TONY_TRIGGER_RANGE || dz < -MAX_TONY_TRIGGER_RANGE || dz > MAX_TONY_TRIGGER_RANGE)
return; return;
auto* sptr = &Sparks[GetFreeSpark()]; auto* sptr = GetFreeParticle();
sptr->on = true; sptr->on = true;
sptr->sR = 255; sptr->sR = 255;
@ -131,7 +132,7 @@ static void TriggerTonyFlame(short itemNumber, int hand)
sptr->colFadeSpeed = 12 + (GetRandomControl() & 3); sptr->colFadeSpeed = 12 + (GetRandomControl() & 3);
sptr->fadeToBlack = 8; sptr->fadeToBlack = 8;
sptr->sLife = sptr->life = (GetRandomControl() & 7) + 24; sptr->sLife = sptr->life = (GetRandomControl() & 7) + 24;
sptr->transType = TransTypeEnum::COLADD; sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = NULL; sptr->extras = NULL;
sptr->dynamic = -1; sptr->dynamic = -1;
sptr->x = ((GetRandomControl() & 15) - 8); sptr->x = ((GetRandomControl() & 15) - 8);
@ -158,7 +159,7 @@ static void TriggerTonyFlame(short itemNumber, int hand)
sptr->maxYvel = -(GetRandomControl() & 7) - 16; sptr->maxYvel = -(GetRandomControl() & 7) - 16;
sptr->fxObj = itemNumber; sptr->fxObj = itemNumber;
sptr->nodeNumber = hand; sptr->nodeNumber = hand;
sptr->def = Objects[ID_DEFAULT_SPRITES].meshIndex; sptr->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex;
sptr->scalar = 1; sptr->scalar = 1;
unsigned char size = (GetRandomControl() & 31) + 32; unsigned char size = (GetRandomControl() & 31) + 32;
sptr->size = size; 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) if (dx < -MAX_TONY_TRIGGER_RANGE || dx > MAX_TONY_TRIGGER_RANGE || dz < -MAX_TONY_TRIGGER_RANGE || dz > MAX_TONY_TRIGGER_RANGE)
return; return;
auto* sptr = &Sparks[GetFreeSpark()]; auto* sptr = GetFreeParticle();
sptr->on = true; sptr->on = true;
sptr->sR = 255; 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->colFadeSpeed = 12 + (GetRandomControl() & 3);
sptr->fadeToBlack = 8; sptr->fadeToBlack = 8;
sptr->sLife = sptr->life = (GetRandomControl() & 7) + 24; sptr->sLife = sptr->life = (GetRandomControl() & 7) + 24;
sptr->transType = TransTypeEnum::COLADD; sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = 0; sptr->extras = 0;
sptr->dynamic = -1; sptr->dynamic = -1;
sptr->x = ((GetRandomControl() & 15) - 8); 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->flags = SP_SCALE | SP_DEF | SP_EXPDEF | SP_FX;
sptr->fxObj = (unsigned char)fxNumber; 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; sptr->scalar = 1;
unsigned char size = (GetRandomControl() & 31) + 64; unsigned char size = (GetRandomControl() & 31) + 64;
sptr->size = size; 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; int frame = laraItem->Animation.FrameNumber - g_Level.Anims[laraItem->Animation.AnimNumber].frameBase;
if (frame >= 12 && frame <= 22) 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); TestTriggers(item, true);
item->Animation.FrameNumber++; item->Animation.FrameNumber++;
} }
@ -392,21 +392,21 @@ static void MoveCart(ItemInfo* laraItem, ItemInfo* minecartItem)
if (minecartItem->Animation.Velocity < CART_MIN_VEL) if (minecartItem->Animation.Velocity < CART_MIN_VEL)
{ {
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) if (minecart->VerticalVelocity)
StopSoundEffect(SFX_TR3_MINE_CART_PULLY_LOOP); StopSoundEffect(SFX_TR3_VEHICLE_MINECART_PULLY_LOOP);
else else
SoundEffect(SFX_TR3_MINE_CART_PULLY_LOOP, &minecartItem->Pose, SoundEnvironment::Always); SoundEffect(SFX_TR3_VEHICLE_MINECART_PULLY_LOOP, &minecartItem->Pose, SoundEnvironment::Always);
} }
else else
{ {
StopSoundEffect(SFX_TR3_MINE_CART_PULLY_LOOP); StopSoundEffect(SFX_TR3_VEHICLE_MINECART_PULLY_LOOP);
if (minecart->VerticalVelocity) if (minecart->VerticalVelocity)
StopSoundEffect(SFX_TR3_MINE_CART_TRACK_LOOP); StopSoundEffect(SFX_TR3_VEHICLE_MINECART_TRACK_LOOP);
else 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)) 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 (minecartItem->Pose.Position.y > minecart->FloorHeightMiddle)
{ {
if (minecart->VerticalVelocity > 0) 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; minecartItem->Pose.Position.y = minecart->FloorHeightMiddle;
minecart->VerticalVelocity = 0; minecart->VerticalVelocity = 0;
@ -597,7 +597,7 @@ static void DoUserInput(ItemInfo* minecartItem, ItemInfo* laraItem, MinecartInfo
case CART_STATE_IDLE: case CART_STATE_IDLE:
if (!(minecart->Flags & CART_FLAG_CONTROL)) 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->Flags |= CART_FLAG_CONTROL;
minecart->StopDelay = 64; minecart->StopDelay = 64;
} }
@ -641,17 +641,17 @@ static void DoUserInput(ItemInfo* minecartItem, ItemInfo* laraItem, MinecartInfo
if (TrInput & CART_IN_DUCK) if (TrInput & CART_IN_DUCK)
{ {
laraItem->Animation.TargetState = CART_STATE_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) else if (!(TrInput & CART_IN_BRAKE) || minecart->Flags & CART_FLAG_STOPPED)
{ {
laraItem->Animation.TargetState = CART_STATE_MOVE; laraItem->Animation.TargetState = CART_STATE_MOVE;
StopSoundEffect(SFX_TR3_MINE_CART_BRAKE); StopSoundEffect(SFX_TR3_VEHICLE_MINECART_BRAKE);
} }
else else
{ {
minecart->Velocity += CART_DEC; 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; break;
@ -682,16 +682,11 @@ static void DoUserInput(ItemInfo* minecartItem, ItemInfo* laraItem, MinecartInfo
if (laraItem->Animation.AnimNumber == Objects[ID_MINECART_LARA_ANIMS].animIndex + 1 && if (laraItem->Animation.AnimNumber == Objects[ID_MINECART_LARA_ANIMS].animIndex + 1 &&
laraItem->Animation.FrameNumber == g_Level.Anims[laraItem->Animation.AnimNumber].frameEnd) 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); GetLaraJointPosition(&pos, LM_HIPS);
laraItem->Pose.Position.x = pos.x; laraItem->Pose.Position = pos;
laraItem->Pose.Position.y = pos.y; laraItem->Pose.Orientation = Vector3Shrt(0, minecartItem->Pose.Orientation.y + ANGLE(90.0f), 0);
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);
SetAnimation(laraItem, LA_STAND_SOLID); SetAnimation(laraItem, LA_STAND_SOLID);
lara->Control.HandStatus = HandStatus::Free; 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 && if (laraItem->Animation.AnimNumber == Objects[ID_MINECART_LARA_ANIMS].animIndex + 47 &&
laraItem->Animation.FrameNumber == g_Level.Anims[laraItem->Animation.AnimNumber].frameEnd) 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); GetLaraJointPosition(&pos, LM_HIPS);
laraItem->Pose.Position.x = pos.x; laraItem->Pose.Position = pos;
laraItem->Pose.Position.y = pos.y; laraItem->Pose.Orientation = Vector3Shrt(0, minecartItem->Pose.Orientation.y - ANGLE(90.0f), 0);
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);
SetAnimation(laraItem, LA_STAND_SOLID); SetAnimation(laraItem, LA_STAND_SOLID);
lara->Control.HandStatus = HandStatus::Free; lara->Control.HandStatus = HandStatus::Free;
@ -752,7 +742,7 @@ static void DoUserInput(ItemInfo* minecartItem, ItemInfo* laraItem, MinecartInfo
floorHeight < CLICK(1)) floorHeight < CLICK(1))
{ {
if (Wibble & 7 == 0) 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.x += TURN_DEATH_VEL * phd_sin(minecartItem->Pose.Orientation.y);
minecartItem->Pose.Position.z += TURN_DEATH_VEL * phd_cos(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/input.h"
#include "Specific/setup.h" #include "Specific/setup.h"
#include "Specific/prng.h" #include "Specific/prng.h"
#include "Game/particle/SimpleParticle.h"
using std::vector; using std::vector;
using namespace TEN::Math::Random; using namespace TEN::Math::Random;
@ -60,6 +61,12 @@ using namespace TEN::Math::Random;
#define MAX_MOMENTUM_TURN ANGLE(1.5f) #define MAX_MOMENTUM_TURN ANGLE(1.5f)
#define QUAD_MAX_MOM_TURN ANGLE(150.0f) #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_MAX_HEIGHT CLICK(1)
#define QUAD_MIN_BOUNCE ((MAX_VELOCITY / 2) / 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); GetJointAbsPosition(laraItem, &pos, LM_HIPS);
laraItem->Pose.Position = pos; laraItem->Pose.Position = pos;
laraItem->Pose.Orientation.x = 0;
laraItem->Pose.Orientation.z = 0;
laraItem->Animation.Airborne = true; laraItem->Animation.Airborne = true;
laraItem->Animation.VerticalVelocity = quadItem->Animation.VerticalVelocity; laraItem->Animation.VerticalVelocity = quadItem->Animation.VerticalVelocity;
laraItem->Pose.Orientation.x = 0;
laraItem->Pose.Orientation.z = 0;
laraItem->HitPoints = 0; laraItem->HitPoints = 0;
lara->Control.HandStatus = HandStatus::Free; lara->Control.HandStatus = HandStatus::Free;
quadItem->Flags |= ONESHOT; quadItem->Flags |= ONESHOT;
@ -273,8 +280,8 @@ static bool QuadCheckGetOff(ItemInfo* laraItem, ItemInfo* quadItem)
else if (laraItem->Animation.ActiveState == QUAD_STATE_FALL_DEATH) else if (laraItem->Animation.ActiveState == QUAD_STATE_FALL_DEATH)
{ {
laraItem->Animation.TargetState = LS_DEATH; laraItem->Animation.TargetState = LS_DEATH;
laraItem->Animation.VerticalVelocity = DAMAGE_START + DAMAGE_LENGTH;
laraItem->Animation.Velocity = 0; laraItem->Animation.Velocity = 0;
laraItem->Animation.VerticalVelocity = DAMAGE_START + DAMAGE_LENGTH;
quad->Flags |= QUAD_FLAG_DEAD; quad->Flags |= QUAD_FLAG_DEAD;
return false; return false;
@ -841,7 +848,7 @@ static void AnimateQuadBike(ItemInfo* laraItem, ItemInfo* quadItem, int collide,
} }
laraItem->Animation.FrameNumber = GetFrameNumber(laraItem, laraItem->Animation.AnimNumber); 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 else
{ {
@ -963,11 +970,43 @@ static void AnimateQuadBike(ItemInfo* laraItem, ItemInfo* quadItem, int collide,
if (TestEnvironment(ENV_FLAG_WATER, quadItem) || if (TestEnvironment(ENV_FLAG_WATER, quadItem) ||
TestEnvironment(ENV_FLAG_SWAMP, quadItem)) TestEnvironment(ENV_FLAG_SWAMP, quadItem))
{ {
laraItem->Animation.TargetState = QUAD_STATE_FALL_OFF; auto waterDepth = (float)GetWaterDepth(quadItem);
laraItem->Pose.Position.y = quadItem->Pose.Position.y + 700;
laraItem->RoomNumber = quadItem->RoomNumber; // HACK: Sometimes quadbike test position may end up under non-portal ceiling block.
laraItem->HitPoints = 0; // GetWaterDepth returns DEEP_WATER constant in that case, which is too large for our needs.
QuadbikeExplode(laraItem, quadItem); 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) 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->on = true;
spark->sR = 0; spark->sR = 0;
@ -1270,7 +1309,7 @@ static void TriggerQuadExhaustSmoke(int x, int y, int z, short angle, int speed,
else else
spark->flags = SP_SCALE | SP_DEF | SP_EXPDEF; 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->scalar = 2;
spark->gravity = -(GetRandomControl() & 3) - 4; spark->gravity = -(GetRandomControl() & 3) - 4;
spark->maxYvel = -(GetRandomControl() & 7) - 8; spark->maxYvel = -(GetRandomControl() & 7) - 8;
@ -1338,12 +1377,12 @@ bool QuadBikeControl(ItemInfo* laraItem, CollisionInfo* coll)
else if (quad->Pitch > 0xA000) else if (quad->Pitch > 0xA000)
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 else
{ {
if (drive != -1) if (drive != -1)
SoundEffect(SFX_TR3_QUADBIKE_IDLE, &quadItem->Pose); SoundEffect(SFX_TR3_VEHICLE_QUADBIKE_IDLE, &quadItem->Pose);
quad->Pitch = 0; quad->Pitch = 0;
} }
@ -1388,7 +1427,7 @@ bool QuadBikeControl(ItemInfo* laraItem, CollisionInfo* coll)
{ {
if (quadItem->Pose.Position.y == quadItem->Floor) if (quadItem->Pose.Position.y == quadItem->Floor)
{ {
ExplodingDeath(lara->ItemNumber, 0xffffffff, 1); ExplodingDeath(lara->ItemNumber, ALL_JOINT_BITS, 1);
laraItem->HitPoints = 0; laraItem->HitPoints = 0;
laraItem->Flags |= ONESHOT; laraItem->Flags |= ONESHOT;
QuadbikeExplode(laraItem, quadItem); QuadbikeExplode(laraItem, quadItem);

View file

@ -14,6 +14,7 @@
#include "Specific/input.h" #include "Specific/input.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/setup.h" #include "Specific/setup.h"
#include "Renderer/Renderer11Enums.h"
#define RBOAT_SLIP 10 #define RBOAT_SLIP 10
#define RBOAT_SIDE_SLIP 30 #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) 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->on = 1;
sptr->sR = 0; 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->colFadeSpeed = 4 + (GetRandomControl() & 3);
sptr->fadeToBlack = 12 - (snow * 8); sptr->fadeToBlack = 12 - (snow * 8);
sptr->sLife = sptr->life = (GetRandomControl() & 3) + 20; sptr->sLife = sptr->life = (GetRandomControl() & 3) + 20;
sptr->transType = TransTypeEnum::COLADD; sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = 0; sptr->extras = 0;
sptr->dynamic = -1; sptr->dynamic = -1;
@ -846,7 +847,7 @@ static void TriggerRubberBoatMist(long x, long y, long z, long velocity, short a
else else
sptr->flags = SP_SCALE | SP_DEF | SP_EXPDEF; sptr->flags = SP_SCALE | SP_DEF | SP_EXPDEF;
sptr->def = Objects[ID_EXPLOSION_SPRITES].meshIndex; sptr->spriteIndex = Objects[ID_EXPLOSION_SPRITES].meshIndex;
if (!snow) if (!snow)
{ {
@ -1032,9 +1033,9 @@ void RubberBoatControl(short itemNumber)
rBoat->Pitch += ((pitch - rBoat->Pitch) / 4); rBoat->Pitch += ((pitch - rBoat->Pitch) / 4);
if (rBoatItem->Animation.Velocity > 8) 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) 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) if (lara->Vehicle != itemNumber)
return; return;

View file

@ -102,7 +102,8 @@ enum UPVState
// TODO // TODO
enum UPVAnim enum UPVAnim
{ {
UPV_ANIM_DEATH = 0, UPV_ANIM_DEATH_MOVING = 0,
UPV_ANIM_DEATH = 1,
UPV_ANIM_IDLE = 5, UPV_ANIM_IDLE = 5,
@ -172,7 +173,7 @@ static void FireUPVHarpoon(ItemInfo* laraItem, ItemInfo* UPVItem)
AddActiveItem(itemNumber); 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++; Statistics.Game.AmmoUsed++;
UPV->HarpoonLeft = !UPV->HarpoonLeft; 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) static void TriggerUPVMist(long x, long y, long z, long velocity, short angle)
{ {
auto* sptr = &Sparks[GetFreeSpark()]; auto* sptr = GetFreeParticle();
sptr->on = 1; sptr->on = 1;
sptr->sR = 0; 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->colFadeSpeed = 4 + (GetRandomControl() & 3);
sptr->fadeToBlack = 12; sptr->fadeToBlack = 12;
sptr->sLife = sptr->life = (GetRandomControl() & 3) + 20; sptr->sLife = sptr->life = (GetRandomControl() & 3) + 20;
sptr->transType = TransTypeEnum::COLADD; sptr->blendMode = BLEND_MODES::BLENDMODE_ADDITIVE;
sptr->extras = 0; sptr->extras = 0;
sptr->dynamic = -1; 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 //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); StopSoundEffect(SFX_TR3_VEHICLE_UPV_LOOP);
SoundEffect(SFX_TR3_UPV_STOP, (PHD_3DPOS*)&UPVItem->Pose.Position.x, SoundEnvironment::Always); 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); UPVItem->Pose.Orientation.x += ANGLE(1.0f);
if (frame == MOUNT_SURFACE_SOUND_FRAME) 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) if (frame == MOUNT_SURFACE_CONTROL_FRAME)
UPV->Flags |= UPV_CONTROL; UPV->Flags |= UPV_CONTROL;
@ -665,7 +666,7 @@ static void UPVControl(ItemInfo* laraItem, ItemInfo* UPVItem)
else if (anim == UPV_ANIM_MOUNT_UNDERWATER) else if (anim == UPV_ANIM_MOUNT_UNDERWATER)
{ {
if (frame == MOUNT_UNDERWATER_SOUND_FRAME) 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) if (frame == MOUNT_UNDERWATER_CONTROL_FRAME)
UPV->Flags |= UPV_CONTROL; UPV->Flags |= UPV_CONTROL;
@ -760,7 +761,8 @@ static void UPVControl(ItemInfo* laraItem, ItemInfo* UPVItem)
break; break;
case UPV_STATE_DEATH: 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(); auto vec = Vector3Int();
GetLaraJointPosition(&vec, LM_HIPS); GetLaraJointPosition(&vec, LM_HIPS);
@ -958,8 +960,8 @@ bool UPVControl(ItemInfo* laraItem, CollisionInfo* coll)
UPV->Flags |= UPV_SURFACE; 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; UPVItem->Pose.Position.y = waterHeight + SURFACE_DIST;
@ -971,7 +973,6 @@ bool UPVControl(ItemInfo* laraItem, CollisionInfo* coll)
UPV->Flags |= UPV_SURFACE; UPV->Flags |= UPV_SURFACE;
} }
else else
UPV->Flags &= ~UPV_SURFACE; UPV->Flags &= ~UPV_SURFACE;
@ -1031,7 +1032,7 @@ bool UPVControl(ItemInfo* laraItem, CollisionInfo* coll)
BackgroundCollision(laraItem, UPVItem); BackgroundCollision(laraItem, UPVItem);
if (UPV->Flags & UPV_CONTROL) 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.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); 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