Merge branch 'develop' into develop_60fps

# Conflicts:
#	TombEngine/Game/control/control.cpp
This commit is contained in:
MontyTRC89 2024-04-28 06:36:13 +02:00
commit b03480e926
30 changed files with 575 additions and 427 deletions

View file

@ -1,3 +1,13 @@
Version 1.5
===========
* Fixed original issue with classic switch off trigger wrongly activating some trigger actions.
* Fixed incorrect diving animation when swandiving from a high place.
* Fixed AI for skidoo driver and worker with shotgun TR2 enemies.
Lua API changes:
* Added Inventory.GetUsedItem(), Inventory.SetUsedItem() and Inventory.ClearUsedItem() functions.
Version 1.4
===========
@ -15,8 +25,10 @@ Version 1.4
* Fixed occasional collision warnings in a log when teeth spikes object was activated.
* Fixed climbable pushables collision during continuous pulling action.
* Fixed collision for solid static meshes with zero collision box.
* Fixed bottom collision for solid static meshes.
* Fixed T-Rex's head rotation.
* Auto-switch to a crawl state if player start position is in a crawlspace.
* Allow directional flame emitter (negative OCBs) to be rotated at any angle.
* Revise wall spikes:
- Wall spikes now stop when they touch a pushable, another spike wall or a normal wall.
- Wall spikes will shatter any shatter in its path.

View file

@ -126,6 +126,18 @@
<td class="name" ><a href="#SetItemCount">SetItemCount(objectID, count)</a></td>
<td class="summary">Set the amount of an item in the player's inventory.</td>
</tr>
<tr>
<td class="name" ><a href="#GetUsedItem">GetUsedItem()</a></td>
<td class="summary">Get last item used in the player's inventory.</td>
</tr>
<tr>
<td class="name" ><a href="#SetUsedItem">SetUsedItem(objectID)</a></td>
<td class="summary">Set last item used in the player's inventory.</td>
</tr>
<tr>
<td class="name" ><a href="#ClearUsedItem">ClearUsedItem()</a></td>
<td class="summary">Clear last item used in the player's inventory.</td>
</tr>
</table>
<br/>
@ -247,6 +259,70 @@
</dd>
<dt>
<a name = "GetUsedItem"></a>
<strong>GetUsedItem()</strong>
</dt>
<dd>
Get last item used in the player's inventory.
This value will be valid only for a single frame after exiting inventory, after which Lara says "No".
Therefore, this function must be preferably used either in OnLoop or OnUseItem events.
<h3>Returns:</h3>
<ol>
<span class="types"><a class="type" href="../4 enums/Objects.ObjID.html#">ObjID</a></span>
Last item used in the inventory.
</ol>
</dd>
<dt>
<a name = "SetUsedItem"></a>
<strong>SetUsedItem(objectID)</strong>
</dt>
<dd>
Set last item used in the player's inventory.
You will be able to specify only objects which already exist in the inventory.
Will only be valid for the next frame. If not processed by the game, Lara will say "No".
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">objectID</span>
<span class="types"><a class="type" href="../4 enums/Objects.ObjID.html#">ObjID</a></span>
Object ID of the item to select from inventory.
</li>
</ul>
</dd>
<dt>
<a name = "ClearUsedItem"></a>
<strong>ClearUsedItem()</strong>
</dt>
<dd>
Clear last item used in the player's inventory.
When this function is used in OnUseItem level function, it allows to override existing item functionality.
For items without existing functionality, this function is needed to avoid Lara saying "No" after using it.
</dd>
</dl>

View file

@ -265,7 +265,8 @@ LOAD
SAVE
START
END
LOOP</pre>
LOOP
USEITEM</pre>
<h3>Parameters:</h3>

View file

@ -190,7 +190,7 @@ namespace TEN::Entities::Player
}
// 3) Check for death floor (if applicable).
if (setup.TestDeathFloor && pointColl.Block->Flags.Death)
if (setup.TestDeathFloor && pointColl.Block->Flags.Death && pointColl.Position.Bridge == NO_VALUE)
return false;
// LOS setup at upper floor bound.

View file

@ -153,7 +153,7 @@ void LaraControl(ItemInfo* item, CollisionInfo* coll)
}
else if (item->Animation.ActiveState == LS_FREEFALL_DIVE)
{
SetAnimation(item, LA_SWANDIVE_DIVE);
SetAnimation(item, LA_SWANDIVE_FREEFALL_DIVE);
item->Animation.Velocity.y /= 2;
item->Pose.Orientation.x = ANGLE(-85.0f);
player.Control.HandStatus = HandStatus::Free;
@ -642,12 +642,6 @@ void UpdateLara(ItemInfo* item, bool isTitle)
if (isTitle)
ActionMap = actionMap;
if (g_Gui.GetInventoryItemChosen() != NO_VALUE)
{
g_Gui.SetInventoryItemChosen(NO_VALUE);
SayNo();
}
// Update player animations.
g_Renderer.UpdateLaraAnimations(true);

View file

@ -276,49 +276,6 @@ void HandlePlayerStatusEffects(ItemInfo& item, WaterStatus waterStatus, PlayerWa
}
}
static void UsePlayerMedipack(ItemInfo& item)
{
auto& player = GetLaraInfo(item);
// Can't use medipack; return early.
if (item.HitPoints <= 0 ||
(item.HitPoints >= LARA_HEALTH_MAX && player.Status.Poison == 0))
{
return;
}
bool hasUsedMedipack = false;
if (IsClicked(In::SmallMedipack) &&
player.Inventory.TotalSmallMedipacks != 0)
{
hasUsedMedipack = true;
item.HitPoints += LARA_HEALTH_MAX / 2;
if (item.HitPoints > LARA_HEALTH_MAX)
item.HitPoints = LARA_HEALTH_MAX;
if (player.Inventory.TotalSmallMedipacks != -1)
player.Inventory.TotalSmallMedipacks--;
}
else if (IsClicked(In::LargeMedipack) &&
player.Inventory.TotalLargeMedipacks != 0)
{
hasUsedMedipack = true;
item.HitPoints = LARA_HEALTH_MAX;
if (player.Inventory.TotalLargeMedipacks != -1)
player.Inventory.TotalLargeMedipacks--;
}
if (hasUsedMedipack)
{
player.Status.Poison = 0;
SaveGame::Statistics.Game.HealthUsed++;
SoundEffect(SFX_TR4_MENU_MEDI, nullptr, SoundEnvironment::Always);
}
}
static std::optional<LaraWeaponType> GetPlayerScrolledWeaponType(const ItemInfo& item, LaraWeaponType currentWeaponType, bool getPrev)
{
static const auto SCROLL_WEAPON_TYPES = std::vector<LaraWeaponType>
@ -376,8 +333,14 @@ void HandlePlayerQuickActions(ItemInfo& item)
auto& player = GetLaraInfo(item);
// Handle medipacks.
if (IsClicked(In::SmallMedipack) || IsClicked(In::LargeMedipack))
UsePlayerMedipack(item);
if (IsClicked(In::SmallMedipack))
{
g_Gui.UseItem(item, GAME_OBJECT_ID::ID_SMALLMEDI_ITEM);
}
else if (IsClicked(In::LargeMedipack))
{
g_Gui.UseItem(item, GAME_OBJECT_ID::ID_BIGMEDI_ITEM);
}
// Handle weapon scroll request.
if (IsClicked(In::PreviousWeapon) || IsClicked(In::NextWeapon))
@ -419,7 +382,7 @@ void HandlePlayerQuickActions(ItemInfo& item)
// TODO: 10th possible weapon, probably grapple gun.
/*if (IsClicked(In::Weapon10) && player.Weapons[(int)LaraWeaponType::].Present)
player.Control.Weapon.RequestGunType = LaraWeaponType::;*/
player.Control.Weapon.RequestGunType = LaraWeaponType::;*/
}
bool CanPlayerLookAround(const ItemInfo& item)

View file

@ -1010,10 +1010,6 @@ bool CollideSolidBounds(ItemInfo* item, const GameBoundingBox& box, const Pose&
auto collBounds = collBox.ToBoundingOrientedBox(Pose(item->Pose.Position));
bool intersects = staticBounds.Intersects(collBounds);
// Check if previous item horizontal position intersects bounds.
auto prevCollBounds = collBox.ToBoundingOrientedBox(Pose(coll->Setup.PrevPosition));
bool prevHorIntersects = staticBounds.Intersects(prevCollBounds);
// Draw item coll bounds.
g_Renderer.AddDebugBox(collBounds, intersects ? Vector4(1, 0, 0, 1) : Vector4(0, 1, 0, 1), RendererDebugPage::CollisionStats);
@ -1072,7 +1068,7 @@ bool CollideSolidBounds(ItemInfo* item, const GameBoundingBox& box, const Pose&
auto distanceToVerticalPlane = height / 2 - yPoint;
// Correct position according to top/bottom bounds, if collided and plane is nearby.
if (intersects && prevHorIntersects && minDistance < height)
if (intersects && minDistance < height)
{
if (bottom)
{

View file

@ -214,6 +214,14 @@ GameStatus ControlPhase(int numFrames)
// Smash shatters and clear stopper flags under them.
UpdateShatters();
// Clear last selected item in inventory (need to be after on loop event handling, so they can detect that).
g_Gui.CancelInventorySelection();
// Control lock is processed after handling scripts, because builder may want to
// process input externally, while still locking Lara from input.
if (!isTitle && Lara.Control.IsLocked)
ClearAllActions();
// Update weather.
Weather.Update();
@ -252,7 +260,7 @@ GameStatus ControlPhase(int numFrames)
g_GameFlow->GetLevel(CurrentLevel)->GetLensFlareSpriteID()
);
}
// Update HUD.
g_Hud.Update(*LaraItem);
UpdateFadeScreenAndCinematicBars();

View file

@ -41,6 +41,7 @@ namespace TEN::Control::Volumes
Save,
Start,
End,
UseItem,
Count
};

View file

@ -114,7 +114,7 @@ int GetSwitchTrigger(ItemInfo* item, short* itemNumbersPtr, int attatchedToSwitc
return k;
}
int SwitchTrigger(short itemNumber, short timer)
bool SwitchTrigger(short itemNumber, short timer)
{
auto& item = g_Level.Items[itemNumber];
const auto& player = Lara;
@ -127,7 +127,7 @@ int SwitchTrigger(short itemNumber, short timer)
item.Status = ITEM_ACTIVE;
item.ItemFlags[1] = false;
return 1;
return true;
}
if (item.ObjectNumber >= ID_PUZZLE_HOLE1 && item.ObjectNumber <= ID_PUZZLE_HOLE16 &&
@ -137,13 +137,13 @@ int SwitchTrigger(short itemNumber, short timer)
item.Status = ITEM_DEACTIVATED;
item.ItemFlags[1] = false;
return 1;
return true;
}
if ((item.ObjectNumber >= ID_PUZZLE_DONE1 && item.ObjectNumber <= ID_PUZZLE_DONE16) ||
(item.ObjectNumber >= ID_PUZZLE_HOLE1 && item.ObjectNumber <= ID_PUZZLE_HOLE16))
{
return 0;
return false;
}
// Handle reusable receptacles.
@ -156,7 +156,7 @@ int SwitchTrigger(short itemNumber, short timer)
item.Status = ITEM_ACTIVE;
item.ItemFlags[5] = (int)ReusableReceptacleState::Done;
item.ItemFlags[1] = false;
return 1;
return true;
}
if (item.ObjectNumber >= ID_KEY_HOLE1 && item.ObjectNumber <= ID_KEY_HOLE16 &&
@ -167,11 +167,11 @@ int SwitchTrigger(short itemNumber, short timer)
item.Status = ITEM_DEACTIVATED;
item.ItemFlags[5] = (int)ReusableReceptacleState::Empty;
item.ItemFlags[1] = false;
return 1;
return true;
}
if (item.ObjectNumber >= ID_KEY_HOLE1 && item.ObjectNumber <= ID_KEY_HOLE16)
return 0;
return false;
// Handle switches.
if (item.Status == ITEM_DEACTIVATED)
@ -186,7 +186,7 @@ int SwitchTrigger(short itemNumber, short timer)
if (timer != 1)
item.Timer = FPS * timer;
return 1;
return true;
}
if (item.TriggerFlags >= 0 || item.Animation.ActiveState != SWITCH_OFF)
@ -197,12 +197,12 @@ int SwitchTrigger(short itemNumber, short timer)
if (!item.ItemFlags[0] == 0)
item.Flags |= ONESHOT;
return 1;
return true;
}
else
{
item.Status = ITEM_ACTIVE;
return 1;
return true;
}
}
else if (item.Status != ITEM_NOT_ACTIVE)
@ -211,13 +211,14 @@ int SwitchTrigger(short itemNumber, short timer)
item.Animation.AnimNumber == GetAnimIndex(item, 2) &&
item.Animation.FrameNumber == GetFrameIndex(&item, 0))
{
return 1;
return true;
}
return ((item.Flags & ONESHOT) >> 8);
if (item.Flags & ONESHOT)
return true;
}
return 0;
return false;
}
int KeyTrigger(short itemNumber)
@ -398,15 +399,11 @@ void Trigger(short const value, short const flags)
void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bool heavy, int heavyFlags)
{
int flip = -1;
int flipAvailable = 0;
int newEffect = -1;
int switchOff = 0;
//int switchFlag = 0;
short objectNumber = 0;
bool switchOff = false;
bool flipAvailable = false;
int flip = NO_VALUE;
int newEffect = NO_VALUE;
int keyResult = 0;
short cameraFlags = 0;
short cameraTimer = 0;
int spotCamIndex = 0;
auto data = GetTriggerIndex(floor, x, y, z);
@ -466,13 +463,7 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo
if (!SwitchTrigger(value, timer))
return;
objectNumber = g_Level.Items[value].ObjectNumber;
//This disables the antitrigger of the Valve switch (ocb 5). I don't know the purpose of this in TR4.
//if (objectNumber >= ID_SWITCH_TYPE1 && objectNumber <= ID_SWITCH_TYPE6 && g_Level.Items[value].TriggerFlags == 5)
//switchFlag = 1;
switchOff = (g_Level.Items[value].Animation.ActiveState == 1);
switchOff = (triggerType == TRIGGER_TYPES::SWITCH && timer && g_Level.Items[value].Animation.ActiveState == 1);
break;
case TRIGGER_TYPES::MONKEY:
@ -571,8 +562,8 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo
if (keyResult >= 2 ||
(triggerType == TRIGGER_TYPES::ANTIPAD ||
triggerType == TRIGGER_TYPES::ANTITRIGGER ||
triggerType == TRIGGER_TYPES::HEAVYANTITRIGGER) &&
triggerType == TRIGGER_TYPES::ANTITRIGGER ||
triggerType == TRIGGER_TYPES::HEAVYANTITRIGGER) &&
item->Flags & ATONESHOT)
break;
@ -656,7 +647,7 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo
if (triggerType == TRIGGER_TYPES::COMBAT)
break;
if (triggerType == TRIGGER_TYPES::SWITCH && timer && switchOff)
if (switchOff)
break;
if (Camera.number != Camera.last || triggerType == TRIGGER_TYPES::SWITCH)
@ -674,6 +665,9 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo
if (keyResult == 1)
break;
if (switchOff)
break;
if (triggerType == TRIGGER_TYPES::ANTIPAD ||
triggerType == TRIGGER_TYPES::ANTITRIGGER ||
triggerType == TRIGGER_TYPES::HEAVYANTITRIGGER)
@ -748,17 +742,26 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo
break;
case TO_FLIPEFFECT:
if (switchOff)
break;
TriggerTimer = timer;
newEffect = value;
break;
case TO_FINISH:
if (switchOff)
break;
NextLevel = value ? value : (CurrentLevel + 1);
RequiredStartPos = timer;
break;
case TO_CD:
PlaySoundTrack(value, flags);
if (switchOff)
break;
PlaySoundTrack(value, flags & CODE_BITS);
break;
case TO_CUTSCENE:
@ -766,6 +769,9 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo
break;
case TO_SECRET:
if (switchOff)
break;
if (!(SaveGame::Statistics.Level.Secrets & (1 << value)))
{
PlaySecretTrack();
@ -777,6 +783,8 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo
case TO_VOLUMEEVENT:
case TO_GLOBALEVENT:
trigger = *(data++);
if (!switchOff)
{
auto& list = targetType == TO_VOLUMEEVENT ? g_Level.VolumeEventSets : g_Level.GlobalEventSets;
@ -815,10 +823,10 @@ void TestTriggers(int x, int y, int z, FloorInfo* floor, Activator activator, bo
if (cameraItem && (Camera.type == CameraType::Fixed || Camera.type == CameraType::Heavy))
Camera.item = cameraItem;
if (flip != -1)
if (flip != NO_VALUE)
DoFlipMap(flip);
if (newEffect != -1 && (flip || !flipAvailable))
if (newEffect != NO_VALUE && (flip || !flipAvailable))
FlipEffect = newEffect;
}

View file

@ -63,7 +63,7 @@ extern int KeyTriggerActive;
bool GetKeyTrigger(ItemInfo* item);
int GetSwitchTrigger(ItemInfo* item, short* itemNumbersPtr, int attatchedToSwitch);
int SwitchTrigger(short itemNumber, short timer);
bool SwitchTrigger(short itemNumber, short timer);
int KeyTrigger(short itemNumber);
bool PickupTrigger(short itemNumber);
void RefreshCamera(short type, short* data);

View file

@ -89,14 +89,16 @@ namespace TEN::Control::Volumes
return nullptr;
}
void HandleEvent(Event& event, Activator& activator)
bool HandleEvent(Event& event, Activator& activator)
{
if (event.Function.empty() || event.CallCounter == 0 || event.CallCounter < NO_CALL_COUNTER)
return;
return false;
g_GameScript->ExecuteFunction(event.Function, activator, event.Data);
if (event.CallCounter != NO_CALL_COUNTER)
event.CallCounter--;
return true;
}
bool HandleEvent(const std::string& name, EventType eventType, Activator activator)

View file

@ -43,7 +43,7 @@ namespace TEN::Control::Volumes
void TestVolumes(short roomNumber, MESH_INFO* mesh);
void TestVolumes(CAMERA_INFO* camera);
void HandleEvent(Event& event, Activator& activator);
bool HandleEvent(Event& event, Activator& activator);
bool HandleEvent(const std::string& name, EventType eventType, Activator activator);
void HandleAllGlobalEvents(EventType type, Activator& activator);
bool SetEventState(const std::string& name, EventType eventType, bool enabled);

View file

@ -939,22 +939,19 @@ void TriggerSuperJetFlame(ItemInfo* item, int yvel, int deadly)
sptr->xVel = (GetRandomControl() & 0xFF) - 128;
sptr->zVel = (GetRandomControl() & 0xFF) - 128;
if (item->Pose.Orientation.y == 0)
{
sptr->zVel = -(size - (size >> 2));
}
else if (item->Pose.Orientation.y == ANGLE(90.0f))
{
sptr->xVel = -(size - (size >> 2));
}
else if (item->Pose.Orientation.y == ANGLE(-180.0f))
{
sptr->zVel = size - (size >> 2);
}
else
{
sptr->xVel = size - (size >> 2);
}
float xAngle = item->Pose.Orientation.x + ANGLE(180); // Nullmesh is rotated 180 degrees in editor
float yAngle = item->Pose.Orientation.y;
Vector3 dir;
dir.x = phd_cos(xAngle) * phd_sin(yAngle);
dir.y = phd_sin(xAngle);
dir.z = phd_cos(xAngle) * phd_cos(yAngle);
dir.Normalize();
sptr->xVel += dir.x * (size - (size >> 2));
sptr->yVel -= dir.y * (size - (size >> 2));
sptr->zVel += dir.z * (size - (size >> 2));
}
}

View file

@ -6,6 +6,7 @@
#include "Game/animation.h"
#include "Game/camera.h"
#include "Game/control/control.h"
#include "Game/control/volume.h"
#include "Game/items.h"
#include "Game/Lara/lara.h"
#include "Game/Lara/lara_fire.h"
@ -1882,7 +1883,7 @@ namespace TEN::Gui
AlterFOV(ANGLE(DEFAULT_FOV), false);
lara->Inventory.IsBusy = false;
InventoryItemChosen = NO_VALUE;
UseItem = false;
ItemUsed = false;
if (lara->Weapons[(int)LaraWeaponType::Shotgun].Ammo[0].HasInfinite())
{
@ -2036,16 +2037,16 @@ namespace TEN::Gui
}
}
void GuiController::UseCurrentItem(ItemInfo* item)
void GuiController::UseItem(ItemInfo& item, int objectNumber)
{
const std::vector<int> CrouchStates =
const auto CROUCH_STATES = std::vector<int>
{
LS_CROUCH_IDLE,
LS_CROUCH_TURN_LEFT,
LS_CROUCH_TURN_RIGHT,
LS_CROUCH_TURN_180
};
const std::vector<int> CrawlStates =
const auto CRAWL_STATES = std::vector<int>
{
LS_CRAWL_IDLE,
LS_CRAWL_FORWARD,
@ -2056,265 +2057,232 @@ namespace TEN::Gui
LS_CRAWL_TO_HANG
};
auto* lara = GetLaraInfo(item);
auto& player = GetLaraInfo(item);
int prevOpticRange = lara->Control.Look.OpticRange;
short inventoryObject = Rings[(int)RingTypes::Inventory].CurrentObjectList[Rings[(int)RingTypes::Inventory].CurrentObjectInList].InventoryItem;
short gameObject = InventoryObjectTable[inventoryObject].ObjectNumber;
short prevOpticRange = player.Control.Look.OpticRange;
player.Control.Look.OpticRange = 0;
player.Inventory.OldBusy = false;
item.MeshBits = ALL_JOINT_BITS;
item->MeshBits = ALL_JOINT_BITS;
lara->Control.Look.OpticRange = 0;
lara->Inventory.OldBusy = false;
InventoryItemChosen = objectNumber;
if (lara->Control.WaterStatus == WaterStatus::Dry ||
lara->Control.WaterStatus == WaterStatus::Wade)
// Use item event handling.
g_GameScript->OnUseItem((GAME_OBJECT_ID)InventoryItemChosen);
HandleAllGlobalEvents(EventType::UseItem, (Activator)item.Index);
// Quickly discard further processing if chosen item was reset in script.
if (InventoryItemChosen == NO_VALUE)
return;
if (InventoryItemChosen == ID_PISTOLS_ITEM ||
InventoryItemChosen == ID_UZI_ITEM ||
InventoryItemChosen == ID_REVOLVER_ITEM)
{
if (gameObject == ID_PISTOLS_ITEM)
if (player.Control.WaterStatus != WaterStatus::Dry &&
player.Control.WaterStatus != WaterStatus::Wade)
{
lara->Control.Weapon.RequestGunType = LaraWeaponType::Pistol;
if (lara->Control.HandStatus != HandStatus::Free)
return;
if (lara->Control.Weapon.GunType == LaraWeaponType::Pistol)
lara->Control.HandStatus = HandStatus::WeaponDraw;
return;
}
if (gameObject == ID_UZI_ITEM)
switch (InventoryItemChosen)
{
lara->Control.Weapon.RequestGunType = LaraWeaponType::Uzi;
case ID_PISTOLS_ITEM:
player.Control.Weapon.RequestGunType = LaraWeaponType::Pistol;
break;
if (lara->Control.HandStatus != HandStatus::Free)
case ID_UZI_ITEM:
player.Control.Weapon.RequestGunType = LaraWeaponType::Uzi;
break;
case ID_REVOLVER_ITEM:
player.Control.Weapon.RequestGunType = LaraWeaponType::Revolver;
break;
default:
return;
if (lara->Control.Weapon.GunType == LaraWeaponType::Uzi)
lara->Control.HandStatus = HandStatus::WeaponDraw;
return;
}
if (player.Control.HandStatus == HandStatus::Free &&
player.Control.Weapon.GunType == player.Control.Weapon.RequestGunType)
{
player.Control.HandStatus = HandStatus::WeaponDraw;
}
InventoryItemChosen = NO_VALUE;
return;
}
if (gameObject != ID_SHOTGUN_ITEM &&
gameObject != ID_REVOLVER_ITEM &&
gameObject != ID_HK_ITEM &&
gameObject != ID_CROSSBOW_ITEM &&
gameObject != ID_GRENADE_GUN_ITEM &&
gameObject != ID_ROCKET_LAUNCHER_ITEM &&
gameObject != ID_HARPOON_ITEM)
if (InventoryItemChosen == ID_SHOTGUN_ITEM ||
InventoryItemChosen == ID_HK_ITEM ||
InventoryItemChosen == ID_CROSSBOW_ITEM ||
InventoryItemChosen == ID_GRENADE_GUN_ITEM ||
InventoryItemChosen == ID_ROCKET_LAUNCHER_ITEM ||
InventoryItemChosen == ID_HARPOON_ITEM)
{
if (gameObject == ID_FLARE_INV_ITEM)
if (InventoryItemChosen != ID_HARPOON_ITEM &&
player.Control.WaterStatus != WaterStatus::Dry &&
player.Control.WaterStatus != WaterStatus::Wade)
{
if (lara->Control.HandStatus == HandStatus::Free)
{
if (!TestState(item->Animation.ActiveState, CrawlStates))
{
if (lara->Control.Weapon.GunType != LaraWeaponType::Flare)
{
// HACK.
ClearAllActions();
ActionMap[(int)In::Flare].Update(1.0f);
HandleWeapon(*item);
ClearAllActions();
}
return;
}
}
SayNo();
return;
}
switch (inventoryObject)
if (TestState(item.Animation.ActiveState, CROUCH_STATES) ||
TestState(item.Animation.ActiveState, CRAWL_STATES))
{
case INV_OBJECT_BINOCULARS:
if (((item->Animation.ActiveState == LS_IDLE && item->Animation.AnimNumber == LA_STAND_IDLE) ||
(lara->Control.IsLow && !IsHeld(In::Crouch))) &&
!UseSpotCam && !TrackCameraInit)
{
lara->Control.Look.OpticRange = 128;
lara->Control.Look.IsUsingBinoculars = true;
lara->Inventory.OldBusy = true;
// TODO: To prevent Lara from crouching or performing other actions, the inherent state of
// LA_BINOCULARS_IDLE must be changed to LS_IDLE. @Sezz 2022.05.19
//SetAnimation(item, LA_BINOCULARS_IDLE);
if (lara->Control.HandStatus != HandStatus::Free)
lara->Control.HandStatus = HandStatus::WeaponUndraw;
}
if (prevOpticRange)
{
lara->Control.Look.OpticRange = prevOpticRange;
}
else
{
BinocularOldCamera = Camera.oldType;
}
return;
case INV_OBJECT_SMALL_MEDIPACK:
if ((item->HitPoints <= 0 || item->HitPoints >= LARA_HEALTH_MAX) &&
lara->Status.Poison == 0)
{
SayNo();
return;
}
if (lara->Inventory.TotalSmallMedipacks != 0)
{
if (lara->Inventory.TotalSmallMedipacks != -1)
lara->Inventory.TotalSmallMedipacks--;
lara->Status.Poison = 0;
item->HitPoints += LARA_HEALTH_MAX / 2;
if (item->HitPoints > LARA_HEALTH_MAX)
item->HitPoints = LARA_HEALTH_MAX;
SoundEffect(SFX_TR4_MENU_MEDI, nullptr, SoundEnvironment::Always);
SaveGame::Statistics.Game.HealthUsed++;
}
else
SayNo();
return;
case INV_OBJECT_LARGE_MEDIPACK:
if ((item->HitPoints <= 0 || item->HitPoints >= LARA_HEALTH_MAX) &&
lara->Status.Poison == 0)
{
SayNo();
return;
}
if (lara->Inventory.TotalLargeMedipacks != 0)
{
if (lara->Inventory.TotalLargeMedipacks != -1)
lara->Inventory.TotalLargeMedipacks--;
lara->Status.Poison = 0;
item->HitPoints = LARA_HEALTH_MAX;
SoundEffect(SFX_TR4_MENU_MEDI, nullptr, SoundEnvironment::Always);
SaveGame::Statistics.Game.HealthUsed++;
}
else
SayNo();
return;
default:
InventoryItemChosen = gameObject;
return;
}
switch (InventoryItemChosen)
{
case ID_SHOTGUN_ITEM:
player.Control.Weapon.RequestGunType = LaraWeaponType::Shotgun;
break;
case ID_REVOLVER_ITEM:
player.Control.Weapon.RequestGunType = LaraWeaponType::Revolver;
break;
case ID_HK_ITEM:
player.Control.Weapon.RequestGunType = LaraWeaponType::HK;
break;
case ID_CROSSBOW_ITEM:
player.Control.Weapon.RequestGunType = LaraWeaponType::Crossbow;
break;
case ID_GRENADE_GUN_ITEM:
player.Control.Weapon.RequestGunType = LaraWeaponType::GrenadeLauncher;
break;
case ID_HARPOON_ITEM:
player.Control.Weapon.RequestGunType = LaraWeaponType::HarpoonGun;
break;
case ID_ROCKET_LAUNCHER_ITEM:
player.Control.Weapon.RequestGunType = LaraWeaponType::RocketLauncher;
break;
default:
return;
}
if (player.Control.HandStatus == HandStatus::Free &&
player.Control.Weapon.GunType == player.Control.Weapon.RequestGunType)
{
player.Control.HandStatus = HandStatus::WeaponDraw;
}
InventoryItemChosen = NO_VALUE;
return;
}
if (lara->Control.HandStatus == HandStatus::Busy)
switch (InventoryItemChosen)
{
SayNo();
return;
}
if (TestState(item->Animation.ActiveState, CrouchStates) ||
TestState(item->Animation.ActiveState, CrawlStates))
{
SayNo();
return;
}
if (gameObject == ID_SHOTGUN_ITEM)
{
lara->Control.Weapon.RequestGunType = LaraWeaponType::Shotgun;
if (lara->Control.HandStatus != HandStatus::Free)
case ID_FLARE_INV_ITEM:
if (player.Control.HandStatus != HandStatus::Free)
return;
if (lara->Control.Weapon.GunType == LaraWeaponType::Shotgun)
lara->Control.HandStatus = HandStatus::WeaponDraw;
if (!TestState(item.Animation.ActiveState, CRAWL_STATES))
{
if (player.Control.Weapon.GunType != LaraWeaponType::Flare)
{
// HACK.
ClearAllActions();
ActionMap[(int)In::Flare].Update(1.0f);
HandleWeapon(item);
ClearAllActions();
}
}
InventoryItemChosen = NO_VALUE;
return;
}
if (gameObject == ID_REVOLVER_ITEM)
{
lara->Control.Weapon.RequestGunType = LaraWeaponType::Revolver;
case ID_BINOCULARS_ITEM:
if (((item.Animation.ActiveState == LS_IDLE && item.Animation.AnimNumber == LA_STAND_IDLE) ||
(player.Control.IsLow && !IsHeld(In::Crouch))) &&
!UseSpotCam && !TrackCameraInit)
{
player.Control.Look.OpticRange = ANGLE(0.7f);
player.Control.Look.IsUsingBinoculars = true;
player.Inventory.OldBusy = true;
if (lara->Control.HandStatus != HandStatus::Free)
return;
// TODO: To prevent Lara from crouching or performing other actions, the inherent state of
// LA_BINOCULARS_IDLE must be changed to LS_IDLE. @Sezz 2022.05.19
//SetAnimation(item, LA_BINOCULARS_IDLE);
if (lara->Control.Weapon.GunType == LaraWeaponType::Revolver)
lara->Control.HandStatus = HandStatus::WeaponDraw;
if (player.Control.HandStatus != HandStatus::Free)
player.Control.HandStatus = HandStatus::WeaponUndraw;
}
if (prevOpticRange != ANGLE(0.0f))
{
player.Control.Look.OpticRange = prevOpticRange;
}
else
{
BinocularOldCamera = Camera.oldType;
}
InventoryItemChosen = NO_VALUE;
return;
}
else if (gameObject == ID_HK_ITEM)
{
lara->Control.Weapon.RequestGunType = LaraWeaponType::HK;
if (lara->Control.HandStatus != HandStatus::Free)
case ID_SMALLMEDI_ITEM:
if ((item.HitPoints <= 0 || item.HitPoints >= LARA_HEALTH_MAX) &&
player.Status.Poison == 0)
{
return;
}
if (lara->Control.Weapon.GunType == LaraWeaponType::HK)
lara->Control.HandStatus = HandStatus::WeaponDraw;
if (player.Inventory.TotalSmallMedipacks != 0)
{
if (player.Inventory.TotalSmallMedipacks != NO_VALUE)
player.Inventory.TotalSmallMedipacks--;
player.Status.Poison = 0;
item.HitPoints += LARA_HEALTH_MAX / 2;
if (item.HitPoints > LARA_HEALTH_MAX)
item.HitPoints = LARA_HEALTH_MAX;
SoundEffect(SFX_TR4_MENU_MEDI, nullptr, SoundEnvironment::Always);
SaveGame::Statistics.Game.HealthUsed++;
}
else
{
return;
}
InventoryItemChosen = NO_VALUE;
return;
}
else if (gameObject == ID_CROSSBOW_ITEM)
{
lara->Control.Weapon.RequestGunType = LaraWeaponType::Crossbow;
if (lara->Control.HandStatus != HandStatus::Free)
case ID_BIGMEDI_ITEM:
if ((item.HitPoints <= 0 || item.HitPoints >= LARA_HEALTH_MAX) &&
player.Status.Poison == 0)
{
return;
}
if (lara->Control.Weapon.GunType == LaraWeaponType::Crossbow)
lara->Control.HandStatus = HandStatus::WeaponDraw;
if (player.Inventory.TotalLargeMedipacks != 0)
{
if (player.Inventory.TotalLargeMedipacks != NO_VALUE)
player.Inventory.TotalLargeMedipacks--;
player.Status.Poison = 0;
item.HitPoints = LARA_HEALTH_MAX;
SoundEffect(SFX_TR4_MENU_MEDI, nullptr, SoundEnvironment::Always);
SaveGame::Statistics.Game.HealthUsed++;
}
else
{
return;
}
InventoryItemChosen = NO_VALUE;
return;
}
else if (gameObject == ID_GRENADE_GUN_ITEM)
{
lara->Control.Weapon.RequestGunType = LaraWeaponType::GrenadeLauncher;
if (lara->Control.HandStatus != HandStatus::Free)
return;
if (lara->Control.Weapon.GunType == LaraWeaponType::GrenadeLauncher)
lara->Control.HandStatus = HandStatus::WeaponDraw;
return;
}
else if (gameObject == ID_HARPOON_ITEM)
{
lara->Control.Weapon.RequestGunType = LaraWeaponType::HarpoonGun;
if (lara->Control.HandStatus != HandStatus::Free)
return;
if (lara->Control.Weapon.GunType == LaraWeaponType::HarpoonGun)
lara->Control.HandStatus = HandStatus::WeaponDraw;
return;
}
else if (gameObject == ID_ROCKET_LAUNCHER_ITEM)
{
lara->Control.Weapon.RequestGunType = LaraWeaponType::RocketLauncher;
if (lara->Control.HandStatus != HandStatus::Free)
return;
if (lara->Control.Weapon.GunType == LaraWeaponType::RocketLauncher)
lara->Control.HandStatus = HandStatus::WeaponDraw;
default:
return;
}
}
@ -2660,7 +2628,7 @@ namespace TEN::Gui
case MenuType::Equip:
case MenuType::Use:
MenuActive = false;
UseItem = true;
ItemUsed = true;
break;
case MenuType::Diary:
@ -3391,7 +3359,7 @@ namespace TEN::Gui
break;
}
if (UseItem && NoAction())
if (ItemUsed && NoAction())
exitLoop = true;
SetEnterInventory(NO_VALUE);
@ -3402,8 +3370,8 @@ namespace TEN::Gui
LastInvItem = Rings[(int)RingTypes::Inventory].CurrentObjectList[Rings[(int)RingTypes::Inventory].CurrentObjectInList].InventoryItem;
UpdateWeaponStatus(item);
if (UseItem)
UseCurrentItem(item);
if (ItemUsed)
UseItem(*item, InventoryObjectTable[LastInvItem].ObjectNumber);
AlterFOV(LastFOV);
ResumeAllSounds(SoundPauseMode::Inventory);
@ -3438,6 +3406,15 @@ namespace TEN::Gui
}
}
void GuiController::CancelInventorySelection()
{
if (GetInventoryItemChosen() != NO_VALUE)
{
SetInventoryItemChosen(NO_VALUE);
SayNo();
}
}
void GuiController::DrawCompass(ItemInfo* item)
{
constexpr auto POS_2D = Vector2(130.0f, 450.0f);

View file

@ -139,7 +139,7 @@ namespace TEN::Gui
// Inventory variables
short CombineObject1;
short CombineObject2;
bool UseItem;
bool ItemUsed;
char SeperateTypeFlag;
char CombineTypeFlag;
InventoryRing Rings[2];
@ -180,6 +180,8 @@ namespace TEN::Gui
void DrawAmmoSelector();
bool PerformWaterskinCombine(ItemInfo* item, bool flag);
void DrawCompass(ItemInfo* item);
void CancelInventorySelection();
void UseItem(ItemInfo& item, int objectNumber);
// Getters
const InventoryRing& GetRing(RingTypes ringType);
@ -220,7 +222,6 @@ namespace TEN::Gui
void SeparateObject(ItemInfo* item, int objectNumber);
void InsertObjectIntoList(int objectNumber);
void InsertObjectIntoList_v2(int objectNumber);
void UseCurrentItem(ItemInfo* item);
void SpinBack(EulerAngles& orient);
void UpdateWeaponStatus(ItemInfo* item);
void DoStatisticsMode();

View file

@ -40,7 +40,7 @@ void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature)
}
}
bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist)
bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist, bool canFloat)
{
auto projectedPos = Geometry::TranslatePoint(item.Pose.Position, dir, dist);
auto pointColl = GetCollision(item.Pose.Position, item.RoomNumber, dir, dist);
@ -59,16 +59,29 @@ bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist)
{
// Test for step.
int relFloorHeight = abs(pointColl.Position.Floor - item.Pose.Position.y);
if (relFloorHeight >= CLICK(1) && item.Pose.Position.y >= pointColl.Position.Floor)
if (relFloorHeight >= CLICK(1) && item.Pose.Position.y >= pointColl.Position.Floor && canFloat)
{
return false;
}
else if (relFloorHeight >= CLICK(1) && !canFloat)
{
return false;
}
}
// Sloped floor.
else
{
// Half block.
int relFloorHeight = abs(pointColl.Position.Floor - item.Pose.Position.y);
if (relFloorHeight > CLICK(1))
if (relFloorHeight > CLICK(1) && canFloat)
{
return false;
}
else if (relFloorHeight > CLICK(2) && !canFloat)
{
return false;
}
short slopeAngle = ANGLE(0.0f);
if (pointColl.FloorTilt.x > 0)
@ -104,8 +117,16 @@ bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist)
// Test ceiling height.
int relCeilHeight = abs(pointColl.Position.Ceiling - pointColl.Position.Floor);
if (relCeilHeight <= height)
return false;
if (canFloat)
{
if (relCeilHeight <= height)
return false;
}
else
{
if (relCeilHeight < BLOCK(1))
return false;
}
// Check for blocked grey box.
if (g_Level.Boxes[pointColl.Block->Box].flags & BLOCKABLE)
@ -123,4 +144,4 @@ bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist)
return false;
return true;
}
}

View file

@ -19,4 +19,4 @@ enum LaraMeshMask
CreatureInfo* GetCreatureInfo(ItemInfo* item);
void TargetNearestEntity(ItemInfo* item, CreatureInfo* creature);
bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist);
bool IsNextSectorValid(const ItemInfo& item, const Vector3& dir, float dist, bool canFloat);

View file

@ -21,16 +21,19 @@ namespace TEN::Effects::EmberEmitter
unsigned char b = 0;
float brightnessShift = Random::GenerateFloat(-0.1f, 0.1f);
r = std::clamp(item.Model.Color.x + brightnessShift, 0.0f, 1.0f) * UCHAR_MAX;
g = std::clamp(item.Model.Color.y + brightnessShift, 0.0f, 1.0f) * UCHAR_MAX;
b = std::clamp(item.Model.Color.z + brightnessShift, 0.0f, 1.0f) * UCHAR_MAX;
r = std::clamp(item.Model.Color.x / 2.0f + brightnessShift, 0.0f, 1.0f) * UCHAR_MAX;
g = std::clamp(item.Model.Color.y / 2.0f + brightnessShift, 0.0f, 1.0f) * UCHAR_MAX;
b = std::clamp(item.Model.Color.z / 2.0f + brightnessShift, 0.0f, 1.0f) * UCHAR_MAX;
if (item.TriggerFlags < 0)
{
if (item.TriggerFlags > -11)
item.TriggerFlags = -11;
if (!item.ItemFlags[2])
{
int div = abs(item.TriggerFlags) % 10 << 10;
int mod = abs(item.TriggerFlags) / 10 << 10;
int div = (abs(item.TriggerFlags) % 10) << 10;
int mod = (abs(item.TriggerFlags) / 10) << 10;
// TODO: Use Random::GenerateInt()
item.ItemFlags[0] = GetRandomControl() % div;

View file

@ -131,6 +131,9 @@ namespace TEN::Entities::Creatures::TR2
int skidooItemNumber = (short)riderItem.ItemFlags[0];
auto* skidooItem = &g_Level.Items[skidooItemNumber];
if (!CreatureActive(skidooItemNumber))
return;
if (!skidooItem->Data)
{
EnableEntityAI(skidooItemNumber, true);

View file

@ -5,29 +5,61 @@
#include "Game/control/box.h"
#include "Game/control/control.h"
#include "Game/effects/effects.h"
#include "Game/items.h"
#include "Game/itemdata/creature_info.h"
#include "Game/items.h"
#include "Game/misc.h"
#include "Game/people.h"
#include "Game/Setup.h"
#include "Math/Math.h"
#include "Specific/level.h"
using namespace TEN::Math;
namespace TEN::Entities::Creatures::TR2
{
constexpr auto WORKER_SHOTGUN_NUM_SHOTS = 6;
const auto WorkerShotgunBite = CreatureBiteInfo(Vector3(0, 350, 40), 9);
// TODO
enum ShotgunWorkerState
{
// No state 0.
WORKER_SHOTGUN_STATE_WALK = 1,
WORKER_SHOTGUN_STATE_IDLE = 2,
WORKER_SHOTGUN_STATE_REST = 3,
WORKER_SHOTGUN_STATE_STANDING_ATTACK = 4,
WORKER_SHOTGUN_STATE_RUN = 5,
WORKER_SHOTGUN_STATE_WALKING_ATTACK = 6,
WORKER_SHOTGUN_STATE_DEATH = 7,
WORKER_SHOTGUN_STATE_STANDING_ATTACK_AIM = 8,
WORKER_SHOTGUN_STATE_KNEEL_ATTACK_AIM = 9,
WORKER_SHOTGUN_STATE_KNEEL_ATTACK = 10
};
// TODO
enum ShotgunWorkerAnim
{
WORKER_SHOTGUN_ANIM_WALK = 0,
WORKER_SHOTGUN_ANIM_STANDING_ATTACK_AIM = 1,
WORKER_SHOTGUN_ANIM_STANDING_ATTACK_SHOOT = 2,
WORKER_SHOTGUN_ANIM_STANDING_ATTACK_STOP = 3,
WORKER_SHOTGUN_ANIM_WALK_TO_IDLE = 4,
WORKER_SHOTGUN_ANIM_IDLE = 5,
WORKER_SHOTGUN_ANIM_IDLE_TO_WALK = 6,
WORKER_SHOTGUN_ANIM_WALKING_ATTACK_AIM = 7,
WORKER_SHOTGUN_ANIM_WALKING_ATTACK_SHOOT = 8,
WORKER_SHOTGUN_ANIM_REST_TO_IDLE = 9,
WORKER_SHOTGUN_ANIM_REST = 10,
WORKER_SHOTGUN_ANIM_REST_TO_STANDING_ATTACK = 11,
WORKER_SHOTGUN_ANIM_IDLE_TO_REST = 12,
WORKER_SHOTGUN_ANIM_STANDING_ATTACK_TO_IDLE = 13,
WORKER_SHOTGUN_ANIM_WALK_TO_RUN = 14,
WORKER_SHOTGUN_ANIM_RUN_TO_WALK = 15,
WORKER_SHOTGUN_ANIM_IDLE_TO_RUN = 16,
WORKER_SHOTGUN_ANIM_RUN = 17,
WORKER_SHOTGUN_ANIM_DEATH = 18,
WORKER_SHOTGUN_ANIM_KNEEL_ATTACK_AIM = 19,
WORKER_SHOTGUN_ANIM_KNEEL_ATTACK_SHOOT = 20,
WORKER_SHOTGUN_ANIM_KNEEL_ATTACK_STOP = 21
};
static void ShootWorkerShotgun(ItemInfo& item, AI_INFO& ai, const CreatureBiteInfo& bite, short headingAngle, int damage)
@ -62,8 +94,8 @@ namespace TEN::Entities::Creatures::TR2
if (item->HitPoints <= 0)
{
if (item->Animation.ActiveState != 7)
SetAnimation(item, 18);
if (item->Animation.ActiveState != WORKER_SHOTGUN_STATE_DEATH)
SetAnimation(item, WORKER_SHOTGUN_ANIM_DEATH);
}
else
{
@ -77,7 +109,7 @@ namespace TEN::Entities::Creatures::TR2
switch (item->Animation.ActiveState)
{
case 2:
case WORKER_SHOTGUN_STATE_IDLE:
creature->MaxTurn = 0;
creature->Flags = 0;
@ -89,38 +121,38 @@ namespace TEN::Entities::Creatures::TR2
if (creature->Mood == MoodType::Escape)
{
item->Animation.TargetState = 5;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_RUN;
}
else if (Targetable(item, &ai))
{
if (ai.distance < SQUARE(BLOCK(3)) || ai.zoneNumber != ai.enemyZone)
{
item->Animation.TargetState = (GetRandomControl() >= 0x4000) ? 9 : 8;
item->Animation.TargetState = Random::TestProbability(1 / 2.0f) ? WORKER_SHOTGUN_STATE_KNEEL_ATTACK_AIM : WORKER_SHOTGUN_STATE_STANDING_ATTACK_AIM;
}
else
{
item->Animation.TargetState = 1;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_WALK;
}
}
else if (creature->Mood == MoodType::Attack || !ai.ahead)
{
if (ai.distance <= SQUARE(BLOCK(2)))
{
item->Animation.TargetState = 1;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_WALK;
}
else
{
item->Animation.TargetState = 5;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_RUN;
}
}
else
{
item->Animation.TargetState = 3;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_REST;
}
break;
case 3:
case WORKER_SHOTGUN_STATE_REST:
if (ai.ahead)
{
extraHeadRot.x = ai.xAngle;
@ -129,16 +161,16 @@ namespace TEN::Entities::Creatures::TR2
if (Targetable(item, &ai))
{
item->Animation.TargetState = 4;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_STANDING_ATTACK;
}
else if (creature->Mood == MoodType::Attack || !ai.ahead)
{
item->Animation.TargetState = 2;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_IDLE;
}
break;
case 1:
case WORKER_SHOTGUN_STATE_WALK:
creature->MaxTurn = ANGLE(3.0f);
if (ai.ahead)
@ -149,32 +181,33 @@ namespace TEN::Entities::Creatures::TR2
if (creature->Mood == MoodType::Escape)
{
item->Animation.TargetState = 5;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_RUN;
}
else if (Targetable(item, &ai))
{
if (ai.distance < SQUARE(BLOCK(3)) || ai.zoneNumber != ai.enemyZone)
{
item->Animation.TargetState = 2;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_IDLE;
}
else
{
item->Animation.TargetState = 6;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_WALKING_ATTACK;
creature->Flags = 0;
}
}
else if (creature->Mood == MoodType::Attack || !ai.ahead)
{
if (ai.distance > SQUARE(BLOCK(2)))
item->Animation.TargetState = 5;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_RUN;
}
else
{
item->Animation.TargetState = 2;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_IDLE;
}
break;
case 5:
case WORKER_SHOTGUN_STATE_RUN:
creature->MaxTurn = ANGLE(5.0f);
tiltAngle = headingAngle / 2;
@ -188,18 +221,18 @@ namespace TEN::Entities::Creatures::TR2
{
if (Targetable(item, &ai))
{
item->Animation.TargetState = 1;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_WALK;
}
else if (creature->Mood == MoodType::Bored || creature->Mood == MoodType::Stalk)
{
item->Animation.TargetState = 1;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_WALK;
}
}
break;
case 8:
case 9:
case WORKER_SHOTGUN_STATE_STANDING_ATTACK_AIM:
case WORKER_SHOTGUN_STATE_KNEEL_ATTACK_AIM:
creature->Flags = 0;
if (ai.ahead)
@ -210,20 +243,20 @@ namespace TEN::Entities::Creatures::TR2
if (Targetable(item, &ai))
{
if (item->Animation.ActiveState == 8)
if (item->Animation.ActiveState == WORKER_SHOTGUN_STATE_STANDING_ATTACK_AIM)
{
item->Animation.TargetState = 4;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_STANDING_ATTACK;
}
else
{
item->Animation.TargetState = 10;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_KNEEL_ATTACK;
}
}
break;
case 4:
case 10:
case WORKER_SHOTGUN_STATE_STANDING_ATTACK:
case WORKER_SHOTGUN_STATE_KNEEL_ATTACK:
if (ai.ahead)
{
extraTorsoRot.x = ai.xAngle;
@ -238,15 +271,15 @@ namespace TEN::Entities::Creatures::TR2
creature->Flags = 1;
}
if (item->Animation.ActiveState == 4 && item->Animation.TargetState != 2 &&
if (item->Animation.ActiveState == WORKER_SHOTGUN_STATE_STANDING_ATTACK && item->Animation.TargetState != WORKER_SHOTGUN_STATE_IDLE &&
(creature->Mood == MoodType::Escape || ai.distance > SQUARE(BLOCK(3)) || !Targetable(item, &ai)))
{
item->Animation.TargetState = 2;
item->Animation.TargetState = WORKER_SHOTGUN_STATE_IDLE;
}
break;
case 6:
case WORKER_SHOTGUN_STATE_WALKING_ATTACK:
if (ai.ahead)
{
extraTorsoRot.x = ai.xAngle;

View file

@ -72,13 +72,13 @@ namespace TEN::Entities::Traps
static Vector3 GetElectricCleanerMovementDirection(const ItemInfo& item, const Vector3& dir0, const Vector3& dir1, const Vector3& dir2)
{
if (IsNextSectorValid(item, dir0, BLOCK(1)))
if (IsNextSectorValid(item, dir0, BLOCK(1), false))
return dir0;
if (IsNextSectorValid(item, dir1, BLOCK(1)))
if (IsNextSectorValid(item, dir1, BLOCK(1), false))
return dir1;
if (IsNextSectorValid(item, dir2, BLOCK(1)))
if (IsNextSectorValid(item, dir2, BLOCK(1), false))
return dir2;
return Vector3::Zero;

View file

@ -99,7 +99,7 @@ namespace TEN::Entities::Traps
if (pointColl.RoomNumber != item.RoomNumber)
ItemNewRoom(itemNumber, pointColl.RoomNumber);
if (!IsNextSectorValid(item, forwardDir, BLOCK(0.5f)))
if (!IsNextSectorValid(item, forwardDir, BLOCK(0.5f), true))
{
switch (headingAngle)
{

View file

@ -36,9 +36,9 @@ void PulseLightControl(short itemNumber)
item->Pose.Position.y,
item->Pose.Position.z,
24,
(pulse * item->Model.Color.x * UCHAR_MAX) / 512,
(pulse * item->Model.Color.y * UCHAR_MAX) / 512,
(pulse * item->Model.Color.z * UCHAR_MAX) / 512);
(pulse * item->Model.Color.x * SCHAR_MAX) / 512,
(pulse * item->Model.Color.y * SCHAR_MAX) / 512,
(pulse * item->Model.Color.z * SCHAR_MAX) / 512);
}
}
@ -61,9 +61,9 @@ void StrobeLightControl(short itemNumber)
{
item->Pose.Orientation.y += ANGLE(16.0f);
byte r = item->Model.Color.x * UCHAR_MAX;
byte g = item->Model.Color.y * UCHAR_MAX;
byte b = item->Model.Color.z * UCHAR_MAX;
byte r = item->Model.Color.x * SCHAR_MAX;
byte g = item->Model.Color.y * SCHAR_MAX;
byte b = item->Model.Color.z * SCHAR_MAX;
TriggerAlertLight(
item->Pose.Position.x,
@ -94,9 +94,9 @@ void ColorLightControl(short itemNumber)
item->Pose.Position.y,
item->Pose.Position.z,
24,
item->Model.Color.x * UCHAR_MAX,
item->Model.Color.y * UCHAR_MAX,
item->Model.Color.z * UCHAR_MAX);
item->Model.Color.x * SCHAR_MAX,
item->Model.Color.y * SCHAR_MAX,
item->Model.Color.z * SCHAR_MAX);
}
}
@ -228,9 +228,9 @@ void BlinkingLightControl(short itemNumber)
TriggerDynamicLight(
pos.x, pos.y, pos.z,
16,
item->Model.Color.x * UCHAR_MAX,
item->Model.Color.y * UCHAR_MAX,
item->Model.Color.z * UCHAR_MAX);
item->Model.Color.x * SCHAR_MAX,
item->Model.Color.y * SCHAR_MAX,
item->Model.Color.z * SCHAR_MAX);
item->MeshBits = 2;

View file

@ -1454,14 +1454,13 @@ namespace TEN::Renderer
}
}
SetCullMode(CullMode::CounterClockwise);
}
// TODO: temporary fix, we need to remove every use of SpriteBatch and PrimitiveBatch because
// they mess up render states cache.
// TODO: temporary fix, we need to remove every use of SpriteBatch and PrimitiveBatch because
// they mess up render states cache
SetBlendMode(BlendMode::Opaque, true);
SetDepthState(DepthState::Write, true);
SetCullMode(CullMode::CounterClockwise, true);
SetBlendMode(BlendMode::Opaque, true);
SetDepthState(DepthState::Write, true);
SetCullMode(CullMode::CounterClockwise, true);
}
}
void Renderer::PrepareSmokeParticles(RenderView& view)

View file

@ -57,8 +57,9 @@ public:
virtual void OnLoop(float deltaTime, bool postLoop) = 0;
virtual void OnSave() = 0;
virtual void OnEnd(GameStatus reason) = 0;
virtual void ShortenTENCalls() = 0;
virtual void OnUseItem(GAME_OBJECT_ID objectNumber) = 0;
virtual void ShortenTENCalls() = 0;
virtual void FreeLevelScripts() = 0;
virtual void ResetScripts(bool clearGameVars) = 0;
virtual void ExecuteScriptFile(const std::string& luaFileName) = 0;

View file

@ -53,14 +53,6 @@ static constexpr char ScriptReserved_DisplayStringSetScale[] = "SetScale";
static constexpr char ScriptReserved_DisplayStringSetColor[] = "SetColor";
static constexpr char ScriptReserved_DisplaySpriteDraw[] = "Draw";
// Built-in LevelFuncs
static constexpr char ScriptReserved_OnStart[] = "OnStart";
static constexpr char ScriptReserved_OnLoad[] = "OnLoad";
static constexpr char ScriptReserved_OnLoop[] = "OnLoop";
static constexpr char ScriptReserved_OnControlPhase[] = "OnControlPhase"; // DEPRECATED
static constexpr char ScriptReserved_OnSave[] = "OnSave";
static constexpr char ScriptReserved_OnEnd[] = "OnEnd";
static constexpr char ScriptReserved_EndReasonExitToTitle[] = "EXITTOTITLE";
static constexpr char ScriptReserved_EndReasonLevelComplete[] = "LEVELCOMPLETE";
static constexpr char ScriptReserved_EndReasonLoadGame[] = "LOADGAME";
@ -81,15 +73,25 @@ static constexpr char ScriptReserved_PostLoop[] = "POSTLOOP";
static constexpr char ScriptReserved_PreControlPhase[] = "PRECONTROLPHASE";
static constexpr char ScriptReserved_PostControlPhase[] = "POSTCONTROLPHASE";
// Event types
static constexpr char ScriptReserved_EventOnEnter[] = "ENTER";
static constexpr char ScriptReserved_EventOnInside[] = "INSIDE";
static constexpr char ScriptReserved_EventOnLeave[] = "LEAVE";
static constexpr char ScriptReserved_EventOnLoad[] = "LOAD";
static constexpr char ScriptReserved_EventOnSave[] = "SAVE";
static constexpr char ScriptReserved_EventOnStart[] = "START";
static constexpr char ScriptReserved_EventOnEnd[] = "END";
static constexpr char ScriptReserved_EventOnLoop[] = "LOOP";
// Built-in LevelFuncs
static constexpr char ScriptReserved_OnStart[] = "OnStart";
static constexpr char ScriptReserved_OnLoad[] = "OnLoad";
static constexpr char ScriptReserved_OnLoop[] = "OnLoop";
static constexpr char ScriptReserved_OnControlPhase[] = "OnControlPhase"; // DEPRECATED
static constexpr char ScriptReserved_OnSave[] = "OnSave";
static constexpr char ScriptReserved_OnEnd[] = "OnEnd";
static constexpr char ScriptReserved_OnUseItem[] = "OnUseItem";
// Event types (volume events + global events)
static constexpr char ScriptReserved_EventOnEnter[] = "ENTER";
static constexpr char ScriptReserved_EventOnInside[] = "INSIDE";
static constexpr char ScriptReserved_EventOnLeave[] = "LEAVE";
static constexpr char ScriptReserved_EventOnStart[] = "START";
static constexpr char ScriptReserved_EventOnLoad[] = "LOAD";
static constexpr char ScriptReserved_EventOnLoop[] = "LOOP";
static constexpr char ScriptReserved_EventOnSave[] = "SAVE";
static constexpr char ScriptReserved_EventOnEnd[] = "END";
static constexpr char ScriptReserved_EventOnUseItem[] = "USEITEM";
// Member functions
static constexpr char ScriptReserved_New[] = "New";
@ -259,6 +261,9 @@ static constexpr char ScriptReserved_GiveInvItem[] = "GiveItem";
static constexpr char ScriptReserved_TakeInvItem[] = "TakeItem";
static constexpr char ScriptReserved_GetInvItemCount[] = "GetItemCount";
static constexpr char ScriptReserved_SetInvItemCount[] = "SetItemCount";
static constexpr char ScriptReserved_GetUsedItem[] = "GetUsedItem";
static constexpr char ScriptReserved_SetUsedItem[] = "SetUsedItem";
static constexpr char ScriptReserved_ClearUsedItem[] = "ClearUsedItem";
static constexpr char ScriptReserved_GetMoveableByName[] = "GetMoveableByName";
static constexpr char ScriptReserved_GetStaticByName[] = "GetStaticByName";
static constexpr char ScriptReserved_GetMoveablesBySlot[] = "GetMoveablesBySlot";

View file

@ -1,12 +1,14 @@
#include "framework.h"
#include "Scripting/Internal/TEN/Inventory/InventoryHandler.h"
#include "Game/gui.h"
#include "Game/Hud/Hud.h"
#include "Game/Lara/lara.h"
#include "Game/pickup/pickup.h"
#include "Scripting/Internal/ReservedScriptNames.h"
using namespace TEN::Hud;
using namespace TEN::Gui;
/***
Inventory manipulation
@ -59,6 +61,36 @@ namespace TEN::Scripting::InventoryHandler
SetInventoryCount(objectID, count);
}
/// Get last item used in the player's inventory.
// This value will be valid only for a single frame after exiting inventory, after which Lara says "No".
// Therefore, this function must be preferably used either in OnLoop or OnUseItem events.
//@function GetUsedItem
//@treturn Objects.ObjID Last item used in the inventory.
static GAME_OBJECT_ID GetUsedItem()
{
return (GAME_OBJECT_ID)g_Gui.GetInventoryItemChosen();
}
/// Set last item used in the player's inventory.
// You will be able to specify only objects which already exist in the inventory.
// Will only be valid for the next frame. If not processed by the game, Lara will say "No".
//@function SetUsedItem
//@tparam Objects.ObjID objectID Object ID of the item to select from inventory.
static void SetUsedItem(GAME_OBJECT_ID objectID)
{
if (g_Gui.IsObjectInInventory(objectID))
g_Gui.SetInventoryItemChosen(objectID);
}
/// Clear last item used in the player's inventory.
// When this function is used in OnUseItem level function, it allows to override existing item functionality.
// For items without existing functionality, this function is needed to avoid Lara saying "No" after using it.
//@function ClearUsedItem
static void ClearUsedItem()
{
g_Gui.SetInventoryItemChosen(GAME_OBJECT_ID::ID_NO_OBJECT);
}
void Register(sol::state* state, sol::table& parent)
{
auto tableInventory = sol::table{ state->lua_state(), sol::create };
@ -68,5 +100,8 @@ namespace TEN::Scripting::InventoryHandler
tableInventory.set_function(ScriptReserved_TakeInvItem, &TakeItem);
tableInventory.set_function(ScriptReserved_GetInvItemCount, &GetItemCount);
tableInventory.set_function(ScriptReserved_SetInvItemCount, &SetItemCount);
tableInventory.set_function(ScriptReserved_SetUsedItem, &SetUsedItem);
tableInventory.set_function(ScriptReserved_GetUsedItem, &GetUsedItem);
tableInventory.set_function(ScriptReserved_ClearUsedItem, &ClearUsedItem);
}
}

View file

@ -65,7 +65,8 @@ static const std::unordered_map<std::string, EventType> EVENT_TYPES
{ ScriptReserved_EventOnLoad, EventType::Load },
{ ScriptReserved_EventOnSave, EventType::Save },
{ ScriptReserved_EventOnStart, EventType::Start },
{ ScriptReserved_EventOnEnd, EventType::End }
{ ScriptReserved_EventOnEnd, EventType::End },
{ ScriptReserved_EventOnUseItem, EventType::UseItem }
};
enum class LevelEndReason
@ -293,6 +294,7 @@ Possible event type values:
START
END
LOOP
USEITEM
@function HandleEvent
@tparam string name Name of the event set to find.
@ -457,6 +459,7 @@ void LogicHandler::FreeLevelScripts()
m_onLoop = sol::nil;
m_onSave = sol::nil;
m_onEnd = sol::nil;
m_onUseItem = sol::nil;
m_handler.GetState()->collect_garbage();
}
@ -999,6 +1002,12 @@ void LogicHandler::OnEnd(GameStatus reason)
CallLevelFuncByName(name, endReason);
}
void LogicHandler::OnUseItem(GAME_OBJECT_ID objectNumber)
{
if (m_onUseItem.valid())
CallLevelFunc(m_onUseItem, objectNumber);
}
/*** Special tables
TombEngine uses the following tables for specific things.
@ -1142,4 +1151,5 @@ void LogicHandler::InitCallbacks()
assignCB(m_onLoop, ScriptReserved_OnLoop);
assignCB(m_onSave, ScriptReserved_OnSave);
assignCB(m_onEnd, ScriptReserved_OnEnd);
assignCB(m_onUseItem, ScriptReserved_OnUseItem);
}

View file

@ -56,6 +56,7 @@ private:
sol::protected_function m_onLoop{};
sol::protected_function m_onSave{};
sol::protected_function m_onEnd{};
sol::protected_function m_onUseItem{};
std::unordered_set<std::string> m_callbacksPreSave;
std::unordered_set<std::string> m_callbacksPostSave;
@ -170,4 +171,5 @@ public:
void OnLoop(float deltaTime, bool postLoop) override;
void OnSave() override;
void OnEnd(GameStatus reason) override;
void OnUseItem(GAME_OBJECT_ID item) override;
};