Tr1 electric ball (#1413)

Implementation of the Electric Ball from Tomb Raider I, inside Level 5: St Francis Folly
This commit is contained in:
Nemoel-Tomo 2025-01-12 17:15:45 +01:00 committed by GitHub
parent a97548467e
commit 2a89abe66d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 582 additions and 20 deletions

View file

@ -2,20 +2,22 @@
#include "Game/effects/Electricity.h"
#include "Game/effects/effects.h"
#include "Game/effects/spark.h"
#include "Game/people.h"
#include "Game/Setup.h"
#include "Math/Math.h"
using namespace TEN::Math;
using namespace TEN::Effects::Spark;
namespace TEN::Effects::Electricity
{
constexpr auto HELICAL_LASER_LIFE_MAX = 18.0f;
std::vector<Electricity> ElectricityArcs = {};
std::vector<HelicalLaser> HelicalLasers = {};
std::vector<HelicalLaser> HelicalLasers = {};
std::array<Vector3, ELECTRICITY_KNOTS_SIZE> ElectricityKnots = {};
std::array<Vector3, ELECTRICITY_KNOTS_SIZE> ElectricityKnots = {};
std::array<Vector3, ELECTRICITY_BUFFER_SIZE> ElectricityBuffer = {};
// BIG TODO: Make a family of Bezier, B-Spline, and Catmull-Rom curve classes.
@ -30,8 +32,8 @@ namespace TEN::Effects::Electricity
auto point4 = knots[3];
auto spline = ((point2 * 2) + (point3 - point1) * alpha) +
(((point1 * 2) - (point2 * 5) + (point3 * 4) - point4) * SQUARE(alpha)) +
(((point1 * -1) + (point2 * 3) - (point3 * 3) + point4) * CUBE(alpha));
(((point1 * 2) - (point2 * 5) + (point3 * 4) - point4) * SQUARE(alpha)) +
(((point1 * -1) + (point2 * 3) - (point3 * 3) + point4) * CUBE(alpha));
return spline;
}
@ -83,7 +85,7 @@ namespace TEN::Effects::Electricity
fmod(Random::GenerateInt(), amplitude),
fmod(Random::GenerateInt(), amplitude),
fmod(Random::GenerateInt(), amplitude)) -
Vector3(amplitude/ 2);
Vector3(amplitude / 2);
}
else
{
@ -130,17 +132,53 @@ namespace TEN::Effects::Electricity
spark.maxYvel = 0;
spark.gravity = 0;
spark.sSize =
spark.dSize =
spark.size = scale + Random::GenerateInt(0, 4);
spark.dSize =
spark.size = scale + Random::GenerateInt(0, 4);
spark.flags = SP_DEF | SP_SCALE;
}
void SpawnElectricEffect(const ItemInfo& item, int jointNumber, const Vector3i& offset, const float spawnRadius, float beamOriginRadius, float beamTargetRadius, int frequency, const Vector3& pos)
{
//TODO: Make electric effect correctly spawn random on any mesh bounds surface. On water surface too.
int randomIndex = Random::GenerateInt(0, 100);
Vector3 pos1 = Vector3::Zero;
if (randomIndex < frequency)
{
if (pos != Vector3::Zero)
{
pos1 = pos + offset.ToVector3();
}
else
{
pos1 = GetJointPosition(item, jointNumber, offset).ToVector3();
}
auto sphere = BoundingSphere(pos1, spawnRadius);
auto pos2 = Random::GeneratePointOnSphere(sphere);
SpawnElectricityGlow(pos2, 28, 32, 32, 64);
SpawnCyborgSpark(pos2);
SpawnDynamicLight(pos2.x, pos2.y, pos2.z, Random::GenerateInt(4, 8), 31, 63, 127);
sphere = BoundingSphere(pos1, beamOriginRadius);
auto sphere1 = BoundingSphere(pos1, beamTargetRadius);
pos1 = Random::GeneratePointOnSphere(sphere);
pos2 = Random::GeneratePointOnSphere(sphere1);
SpawnElectricity(pos1, pos2, Random::GenerateInt(8, 16), 32, 64, 128, 24, (int)ElectricityFlags::Spline | (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::ThinIn, 6, 8);
}
}
void SpawnHelicalLaser(const Vector3& origin, const Vector3& target)
{
{
constexpr auto SEGMENTS_NUM_MAX = 128;
constexpr auto COLOR = Vector4(0.0f, 0.375f, 1.0f, 1.0f);
constexpr auto LENGTH_MAX = BLOCK(4);
constexpr auto ROTATION = ANGLE(-10.0f);
constexpr auto COLOR = Vector4(0.0f, 0.375f, 1.0f, 1.0f);
constexpr auto LENGTH_MAX = BLOCK(4);
constexpr auto ROTATION = ANGLE(-10.0f);
constexpr auto ELECTRICITY_FLAGS = (int)ElectricityFlags::ThinIn | (int)ElectricityFlags::ThinOut;
@ -164,7 +202,7 @@ namespace TEN::Effects::Electricity
SpawnElectricity(origin, target, 1, 0, laser.Color.x * UCHAR_MAX, laser.Color.z * UCHAR_MAX, 20, ELECTRICITY_FLAGS, 19, 5);
SpawnElectricity(origin, target, 1, 110, 255, 250, 20, ELECTRICITY_FLAGS, 4, 5);
SpawnElectricityGlow(laser.LightPosition, 0, 0, (laser.Color.x / 2) * UCHAR_MAX, (laser.Color.z / 2) * UCHAR_MAX);
}
}
void UpdateHelicalLasers()
{
@ -273,7 +311,7 @@ namespace TEN::Effects::Electricity
else
{
int numSegments = (arc.segments * 3) - 1;
auto deltaPos = (knots[knots.size() - 1] - knots[0]) / numSegments;
auto pos = knots[0] + deltaPos + Vector3(
fmod(Random::GenerateInt(), arc.amplitude * 2),
@ -330,4 +368,4 @@ namespace TEN::Effects::Electricity
buffer[bufferIndex] = knots[1];
}
}
}

View file

@ -108,9 +108,9 @@ namespace TEN::Effects::Electricity
extern std::array<Vector3, ELECTRICITY_BUFFER_SIZE> ElectricityBuffer;
void SpawnElectricity(const Vector3& origin, const Vector3& target, float amplitude, byte r, byte g, byte b, float life, int flags, float width, unsigned int numSegments);
void SpawnElectricEffect(const ItemInfo& item, int jointNumber, const Vector3i& offset, const float spawnRadius, float beamOriginRadius, float beamTargetRadius, int frequency, const Vector3& pos);
void SpawnElectricityGlow(const Vector3& pos, float scale, byte r, byte g, byte b);
void SpawnHelicalLaser(const Vector3& origin, const Vector3& target);
void UpdateElectricityArcs();
void UpdateHelicalLasers();

View file

@ -272,6 +272,7 @@ enum class ShockwaveStyle
Normal = 0,
Sophia = 1,
Knockback = 2,
Invisible = 3,
};
#define ENERGY_ARC_STRAIGHT_LINE 0

View file

@ -20,7 +20,7 @@ namespace TEN::Effects::Boss
{
ShockwaveExplosion = (1 << 0),
Shield = (1 << 1),
Lizard = (1 << 2)
Lizard = (1 << 2)
};
void ShieldControl(int itemNumber);

View file

@ -0,0 +1,471 @@
#include "framework.h"
#include "Objects/TR1/Trap/ElectricBall.h"
#include "Game/effects/effects.h"
#include "Game/effects/tomb4fx.h"
#include "Game/Lara/lara.h"
#include "Game/Setup.h"
#include "Math/Math.h"
#include "Game/control/los.h"
#include "Game/effects/item_fx.h"
#include "Game/misc.h"
#include "Objects/Effects/Boss.h"
#include "Game/effects/spark.h"
using namespace TEN::Math;
using namespace TEN::Effects::Electricity;
using namespace TEN::Effects::Items;
using namespace TEN::Effects::Spark;
using namespace TEN::Effects::Boss;
namespace TEN::Entities::Traps
{
enum TargetType
{
None,
Target
};
struct ElectricBallInfo
{
Vector3i Position = Vector3i::Zero;
Color Colorw;
Electricity* EnergyArcs = {};
Electricity* MainEnergyArc = {};
unsigned int Count = 0;
};
ElectricBallInfo EldectricBallData;
constexpr auto ELECTRIC_BALL_LIGHTNING_DAMAGE = 40;
constexpr auto ELECTRIC_BALL_DISTANCE_RANDOM_TARGET = 7;
constexpr auto ELECTRIC_BALL_DISTANCE_LARA_TARGET = 3;
const auto ElectricBallBite = CreatureBiteInfo(Vector3::Zero, 0);
void TriggerElectricBallShockwaveAttackSparks(int x, int y, int z, byte r, byte g, byte b, byte size)
{
auto* spark = GetFreeParticle();
spark->dG = g;
spark->sG = g;
spark->colFadeSpeed = 2;
spark->dR = r;
spark->sR = r;
spark->blendMode = BlendMode::Additive;
spark->life = 4;
spark->sLife = 4;
spark->x = x;
spark->on = 1;
spark->dB = b;
spark->sB = b;
spark->fadeToBlack = 4;
spark->y = y;
spark->z = z;
spark->zVel = 0;
spark->yVel = 0;
spark->xVel = 0;
spark->flags = SP_SCALE | SP_DEF;
spark->scalar = 3;
spark->maxYvel = 0;
spark->spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LENS_FLARE_LIGHT;
spark->gravity = 0;
spark->dSize = spark->sSize = spark->size = size + (GetRandomControl() & 3);
}
const void SpawnElectricSmoke(const Vector3& pos, const EulerAngles& orient, float life)
{
auto& smoke = *GetFreeParticle();
float scale = (life * 18) * phd_cos(orient.x);
smoke.on = true;
smoke.blendMode = BlendMode::Additive;
smoke.spriteIndex = Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LENS_FLARE_LIGHT;// SPR_LENSFLARE_LIGHT;
smoke.x = pos.x;
smoke.y = pos.y;
smoke.z = pos.z;
smoke.xVel = Random::GenerateInt(-25, 25);
smoke.yVel = life * 3;
smoke.zVel = Random::GenerateInt(-25, 25);
smoke.sB = (Random::GenerateInt(28, 196) * life) / 16;
smoke.sR =
smoke.sG = smoke.sB - (smoke.sB / 4);
smoke.dB = (Random::GenerateInt(62, 80) * life) / 16;
smoke.dR = smoke.dB / 3;
smoke.dG =
smoke.dB /2 ;
smoke.colFadeSpeed = Random::GenerateInt(8, 12);
smoke.fadeToBlack = 8;
smoke.sLife =
smoke.life = Random::GenerateFloat(24.0f, 28.0f);
smoke.friction = 0;
smoke.rotAng = Random::GenerateAngle(0, ANGLE(22.5f));
smoke.rotAdd = Random::GenerateAngle(ANGLE(-0.35f), ANGLE(0.35f));
smoke.gravity = Random::GenerateAngle(ANGLE(0.175f), ANGLE(0.35f));
smoke.maxYvel = 0;
smoke.scalar = 1;
smoke.size =
smoke.sSize = scale;
smoke.dSize = 1;
smoke.flags = SP_SCALE | SP_DEF | SP_ROTATE | SP_EXPDEF;
}
void InitializeElectricBall(short itemNumber)
{
auto& item = g_Level.Items[itemNumber];
EldectricBallData.Colorw = item.Model.Color;
//Distance before switch off.
item.ItemFlags[0] = ELECTRIC_BALL_DISTANCE_RANDOM_TARGET;
//Distance to attack Lara.
item.ItemFlags[1] = ELECTRIC_BALL_DISTANCE_LARA_TARGET;
item.ItemFlags[2] = TargetType::None;
}
static std::vector<int> GetTargetEntityList(const ItemInfo& item)
{
auto entityList = std::vector<int>{};
for (auto& currentEntity : g_Level.Items)
{
if (currentEntity.ObjectNumber == ID_ELECTRIC_BALL_IMPACT_POINT &&
currentEntity.RoomNumber == item.RoomNumber &&
currentEntity.TriggerFlags == item.TriggerFlags)
{
entityList.push_back(currentEntity.Index);
}
}
return entityList;
}
static Vector3 GetTargetPosition(ItemInfo& item)
{
if (item.ItemFlags[2] != TargetType::None)
{
const auto& targetEntity = g_Level.Items[item.ItemFlags[2]];
return targetEntity.Pose.Position.ToVector3();
}
// Failsafe.
const auto& creature = *GetCreatureInfo(&item);
return creature.Target.ToVector3();
}
static int GetTargetItemNumber(const ItemInfo& item)
{
if (item.ObjectNumber == ID_ELECTRIC_BALL && !Objects[ID_ELECTRIC_BALL_IMPACT_POINT].loaded)
return NO_VALUE;
auto targetList = GetTargetEntityList(item);
if (targetList.empty())
return NO_VALUE;
if (targetList.size() == 1)
return targetList[0];
else
return targetList[Random::GenerateInt(0, (int)targetList.size() - 1 )];
}
void SpawnElectricBallLightning(ItemInfo& item, const Vector3& pos, const CreatureBiteInfo& bite)
{
auto offset = Vector3::Zero;
auto origin = GameVector(GetJointPosition(&item, bite), item.RoomNumber);
auto targetRandom = Vector3i::Zero;
auto target = GameVector(pos, item.RoomNumber);
constexpr auto SPAWN_RADIUS = BLOCK(0.30f);
auto orient = Geometry::GetOrientToPoint(origin.ToVector3(), target.ToVector3());
auto pos1 = GetJointPosition(item, 0, Vector3i(0, 200, 0)).ToVector3();
auto targetNew = GameVector::Zero;
auto hitPos = Vector3i::Zero;
static constexpr auto raygunSmokeLife = 16.0f;
auto distance = origin - target;
if (item.ItemFlags[3] == 0)
{
auto random = Random::GenerateFloat(0.20f, 0.60f);
auto halftarget = origin.ToVector3() + Vector3(random, random, random) * (target.ToVector3() - origin.ToVector3());
StopSoundEffect(SFX_TR5_GOD_HEAD_CHARGE);
SoundEffect(SFX_TR5_GOD_HEAD_BLAST, &item.Pose);
SpawnElectricity(origin.ToVector3(), halftarget, 5, 32, 64, 128, 60, (int)ElectricityFlags::Spline , 8, 12);
auto arc = ElectricityArcs.back();;
SpawnElectricityGlow(arc.pos1, 68, 32, 32, 64);
targetRandom = Vector3i(Random::GenerateInt(515, 650), 0, Random::GenerateInt(615, 810));
targetNew = GameVector((target.ToVector3() - Vector3(0, 120, 0)) + targetRandom.ToVector3(), item.RoomNumber);
SpawnElectricity(arc.pos4, target.ToVector3() + targetRandom.ToVector3(), 15, 32, 64, 128, 60, (int)ElectricityFlags::Spline | (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::MoveEnd, 4, 12);
TriggerRicochetSpark(targetNew, 12, 12, Color(32, 64, 128, 1.0f));
SpawnElectricSmoke((target.ToVector3() - Vector3(0, 120, 0)) + targetRandom.ToVector3(), EulerAngles::Identity, raygunSmokeLife);
TriggerShockwave((Pose*)&targetNew, 1, 15, 3, 32, 64, 128, 80, EulerAngles::Identity, 8, false, true, true, (int)ShockwaveStyle::Invisible);
SpawnElectricity((target.ToVector3() - Vector3(0, 120, 0)) + targetRandom.ToVector3(), target.ToVector3() + targetRandom.ToVector3(), 15, 32, 64, 128, 160, (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::MoveEnd, 12, 12);
SpawnElectricity((target.ToVector3() - Vector3(0, 120, 0)) + targetRandom.ToVector3(), target.ToVector3() + targetRandom.ToVector3(), 15, 32, 64, 128, 160, (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::MoveEnd, 12, 12);
if (Random::GenerateInt(0, 100) > 20)
{
halftarget = arc.pos4 + Vector3(random, random, random) * (target.ToVector3() - arc.pos4);
SpawnElectricity(arc.pos4, halftarget, 10, 32, 64, 128, 60, (int)ElectricityFlags::Spline, 8, 12);//3
arc = ElectricityArcs.back();
}
//Main Arc
targetRandom = Vector3i(Random::GenerateInt(5, 20), 0, Random::GenerateInt(5, 20));
targetNew = GameVector((target.ToVector3() - Vector3(0, 120, 0)) + targetRandom.ToVector3(), item.RoomNumber);
SpawnElectricity(arc.pos4, target.ToVector3() + targetRandom.ToVector3(), 15, 32, 64, 128, 60, (int)ElectricityFlags::Spline | (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::MoveEnd, 8, 12);
TriggerRicochetSpark(targetNew, 12, 12, Color(32, 64, 128, 1.0f));
SpawnElectricSmoke((target.ToVector3() - Vector3(0, 120, 0)) + targetRandom.ToVector3(), EulerAngles::Identity, raygunSmokeLife);
TriggerShockwave((Pose*)&targetNew, 1, 15, 3, 32, 64, 128, 80, EulerAngles::Identity, 8, false, true, true, (int)ShockwaveStyle::Invisible);
SpawnElectricity((target.ToVector3() - Vector3(0, 220, 0)) + targetRandom.ToVector3(), target.ToVector3() + targetRandom.ToVector3(), 15, 32, 64, 128, 160, (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::MoveEnd, 12, 12);
SpawnElectricity((target.ToVector3() - Vector3(0, 120, 0)) + targetRandom.ToVector3(), target.ToVector3() + targetRandom.ToVector3(), 15, 32, 64, 128, 160, (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::MoveEnd, 12, 12);
if (Random::GenerateInt(0, 100) > 50)
{
halftarget = arc.pos4 + Vector3(random, random, random) * (target.ToVector3() - arc.pos4);
SpawnElectricity(arc.pos4, halftarget, 10, 32, 64, 128, 60, (int)ElectricityFlags::Spline, 8, 12);
arc = ElectricityArcs.back();
}
targetRandom = Vector3(Random::GenerateInt(225, 650), random, Random::GenerateInt(125, 150));
targetNew = GameVector((target.ToVector3() - Vector3(0, 120, 0)) + targetRandom.ToVector3(), item.RoomNumber);
SpawnElectricity(arc.pos4, target.ToVector3() + targetRandom.ToVector3(), 15, 32, 64, 128, 60, (int)ElectricityFlags::Spline | (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::MoveEnd, 8, 12);
TriggerRicochetSpark(targetNew, 12, 12, Color(32, 64, 128, 1.0f));
SpawnElectricSmoke((target.ToVector3() - Vector3(0, 120, 0)) + targetRandom.ToVector3(), EulerAngles::Identity, raygunSmokeLife);
TriggerShockwave((Pose*)&targetNew, 1, 15, 3, 32, 64, 128, 80, EulerAngles::Identity, 8, false, true, true, (int)ShockwaveStyle::Invisible);
SpawnElectricity((target.ToVector3() - Vector3(0, 120, 0)) + targetRandom.ToVector3(), target.ToVector3() + targetRandom.ToVector3(), 15, 32, 64, 128, 160, (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::MoveEnd, 12, 12);
SpawnElectricity((target.ToVector3() - Vector3(0, 120, 0)) + targetRandom.ToVector3(), target.ToVector3() + targetRandom.ToVector3(), 15, 32, 64, 128, 160, (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::MoveEnd, 12, 12);
item.ItemFlags[3] = Random::GenerateInt(120,230);
item.ItemFlags[4] = 120;
item.ItemFlags[6] = 20;
item.ItemFlags[2] = TargetType::None;
auto sparkOrigin = GetJointPosition(item, 0, Vector3i(0, 300, 0)).ToVector3();
TriggerElectricBallShockwaveAttackSparks(origin.x, origin.y, origin.z, 128, 128, 200, 128);
}
if (item.ItemFlags[4] > 90)
{
if (item.ItemFlags[4] > 0)
{
short blue =( item.ItemFlags[4] << 2) + Random::GenerateInt(2,18);
short green = blue >> 2;
short red = 0;
SpawnDynamicLight(pos1.x, pos1.y, pos1.z, (item.ItemFlags[4] +8) / 5, red, green, blue);
item.ItemFlags[3]--;
}
}
if (item.ItemFlags[4] > 0)
item.ItemFlags[4]--;
if (item.ItemFlags[4] > 110)
{
auto sphere = BoundingSphere(pos1, SPAWN_RADIUS);
auto pos2 = Random::GeneratePointOnSphere(sphere);
SpawnElectricSmoke(pos2, EulerAngles::Identity, raygunSmokeLife);
SpawnElectricSmoke(pos2, EulerAngles::Identity, raygunSmokeLife);
SpawnElectricSmoke(pos2, EulerAngles::Identity, raygunSmokeLife);
pos2 = Random::GeneratePointInSphere(sphere);
SpawnElectricSmoke(pos2, EulerAngles::Identity, raygunSmokeLife);
SpawnElectricSmoke(pos2, EulerAngles::Identity, raygunSmokeLife);
SpawnElectricSmoke(pos2, EulerAngles::Identity, raygunSmokeLife);
}
int randomIndex = Random::GenerateInt(0, 100);
SpawnElectricEffect(item, 0, Vector3i(0, 0, 0), BLOCK(0.35f), BLOCK(0.45f), BLOCK(0.25f), 6, Vector3::Zero);
if (randomIndex < 6)
{
if (item.ItemFlags[4] > 60)
{
pos1 = GetJointPosition(item, 0, Vector3i(0, 100, 0)).ToVector3();
auto sphere = BoundingSphere(pos1, SPAWN_RADIUS);
auto pos2 = Random::GeneratePointOnSphere(sphere);
SpawnElectricSmoke(pos2, EulerAngles::Identity, raygunSmokeLife);
SpawnElectricSmoke(pos2, EulerAngles::Identity, raygunSmokeLife);
SpawnElectricSmoke(pos2, EulerAngles::Identity, raygunSmokeLife);
}
}
if (item.ItemFlags[4] > 75 && randomIndex < 37)
{
pos1 = GetJointPosition(item, 0, Vector3i(0, 160, 0)).ToVector3();
auto sphere = BoundingSphere(pos1, BLOCK(0.25f));
auto pos2 = Random::GeneratePointOnSphere(sphere);
SpawnElectricSmoke(pos2, EulerAngles::Identity, raygunSmokeLife);
SpawnElectricSmoke(pos2, EulerAngles::Identity, raygunSmokeLife);
SpawnElectricSmoke(pos2, EulerAngles::Identity, raygunSmokeLife);
}
if (item.ItemFlags[4] > 45)
{
SoundEffect(SFX_TR4_LARA_ELECTRIC_CRACKLES, &item.Pose);
SoundEffect(SFX_TR4_LARA_ELECTRIC_LOOP, &item.Pose);
SoundEffect(SFX_TR5_DOOR_BEAM, &item.Pose);
}
if (item.ItemFlags[3] < 30)
{
int intensity = 0.01f;
if (item.Model.Color.x < 4.0f)
item.Model.Color.x += 0.05f;
else
item.Model.Color.x = 4.0f;
if (item.Model.Color.y < 4.0f)
item.Model.Color.y += 0.05f;
else
item.Model.Color.y = 4.0f;
if (item.Model.Color.z < 4.0f)
item.Model.Color.z += 0.09f;
else
item.Model.Color.z = 4.0f;
SoundEffect(SFX_TR5_GOD_HEAD_CHARGE, &item.Pose);
}
else if (item.ItemFlags[4] > 80)
{
if (item.Model.Color.x <= EldectricBallData.Colorw.x)
item.Model.Color.x = EldectricBallData.Colorw.x;
else
item.Model.Color.x -= 0.1f;
if (item.Model.Color.y <= EldectricBallData.Colorw.y)
item.Model.Color.y = EldectricBallData.Colorw.y;
else
item.Model.Color.y -= 0.1f;
if (item.Model.Color.z <= EldectricBallData.Colorw.z)
item.Model.Color.z = EldectricBallData.Colorw.z;
else
item.Model.Color.z -= 0.1f;
}
if (item.ItemFlags[3] > 28)
{
SpawnElectricityGlow(origin.ToVector3(), 108, 22, 22, 54);
}
else
{
SpawnElectricityGlow(origin.ToVector3(), 128, 102, 102, 204);
SpawnElectricity(origin.ToVector3() - Vector3(0, 1300, 0), origin.ToVector3() - (targetRandom.ToVector3() * 8), 15, 32, 64, 128, 30, (int)ElectricityFlags::Spline | (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::MoveEnd, 8, 12);
SpawnElectricity(origin.ToVector3() - Vector3(0, 1300, 0), origin.ToVector3() - (targetRandom.ToVector3() * 8), 15, 32, 64, 128, 30, (int)ElectricityFlags::Spline | (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::MoveEnd, 8, 12);
SpawnElectricity(origin.ToVector3() + Vector3(0, 300, 0), origin.ToVector3() + (targetRandom.ToVector3() * 8), 15, 32, 64, 128, 30, (int)ElectricityFlags::Spline | (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::MoveEnd, 8, 12);
SpawnElectricity(origin.ToVector3() + Vector3(0, 300, 0), origin.ToVector3() + (targetRandom.ToVector3() * 8), 15, 32, 64, 128, 30, (int)ElectricityFlags::Spline | (int)ElectricityFlags::ThinOut | (int)ElectricityFlags::MoveEnd, 8, 12);
SpawnElectricEffect(item, 0, Vector3i(0, 0, 0), BLOCK(0.45f), BLOCK(0.45f), BLOCK(0.25f), 80, Vector3::Zero);
}
if (item.ItemFlags[6] > 0)
{
if (ObjectOnLOS2(&origin, &target, &hitPos, nullptr, ID_LARA) == LaraItem->Index)
{
if (LaraItem->HitPoints <= ELECTRIC_BALL_LIGHTNING_DAMAGE)
{
ItemElectricBurn(LaraItem);
DoDamage(LaraItem, ELECTRIC_BALL_LIGHTNING_DAMAGE);
}
else
{
DoDamage(LaraItem, ELECTRIC_BALL_LIGHTNING_DAMAGE);
}
}
item.ItemFlags[6]--;
SoundEffect(SFX_TR5_GOD_HEAD_LASER_LOOPS, &item.Pose);
SoundEffect(SFX_TR4_ELECTRIC_ARCING_LOOP, &item.Pose);
}
}
void ControlElectricBall(short itemNumber)
{
if (!CreatureActive(itemNumber))
return;
auto& item = g_Level.Items[itemNumber];
auto& object = Objects[item.ObjectNumber];
const auto& laraItem = *LaraItem;
static auto targetPos = Vector3i::Zero;
AI_INFO ai;
CreatureAIInfo(&item, &ai);
if (ai.distance > SQUARE(BLOCK(item.ItemFlags[0])))
{
item.ItemFlags[3] = 70;
item.ItemFlags[4] = 70;
item.Model.Color = EldectricBallData.Colorw;
item.ItemFlags[2] = TargetType::None;
return;
}
SoundEffect(SFX_TR5_DOOR_BEAM, &item.Pose);
auto targetPlayer = GameVector(GetJointPosition(LaraItem, LM_TORSO), LaraItem->RoomNumber);
auto origin = GameVector(GetJointPosition(item, 0), item.RoomNumber);
bool los = LOS(&origin, &targetPlayer);
FloorInfo* floor = GetFloor(item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z, &item.RoomNumber);
if (ai.distance < SQUARE(BLOCK(item.ItemFlags[1])) && los)
{
targetPos = Vector3(LaraItem->Pose.Position.x, GetFloorHeight(floor, item.Pose.Position.x, item.Pose.Position.y, item.Pose.Position.z), LaraItem->Pose.Position.z);
item.ItemFlags[2] = TargetType::Target;
item.ItemFlags[5] = LaraItem->Index;
}
else if ((item.ObjectNumber == ID_ELECTRIC_BALL &&
Objects[ID_ELECTRIC_BALL_IMPACT_POINT].loaded &&
item.ItemFlags[2] == TargetType::None))
{
item.ItemFlags[2] = (short)GetTargetItemNumber(item);
targetPos = GetTargetPosition(item);
item.ItemFlags[5] = g_Level.Items[item.ItemFlags[2]].Index;
}
if (item.ItemFlags[2] != TargetType::None)
{
SpawnElectricBallLightning(item, targetPos.ToVector3(), ElectricBallBite);
}
else
{
item.Model.Color = EldectricBallData.Colorw;
item.ItemFlags[2] = TargetType::None;
}
if (item.ItemFlags[3] > 0)
item.ItemFlags[3]--;
}
void InitializeElectricBallImpactPoint(short itemNumber)
{
auto& item = g_Level.Items[itemNumber];
}
}

View file

@ -0,0 +1,14 @@
#pragma once
struct ItemInfo;
class Vector3i;
struct CreatureBiteInfo;
namespace TEN::Entities::Traps
{
void InitializeElectricBall(short itemNumber);
void SpawnElectricBallLightning(ItemInfo& item, const Vector3& pos, const CreatureBiteInfo& bite);
void ControlElectricBall(short itemNumber);
void InitializeElectricBallImpactPoint(short itemNumber);
void TriggerElectricBallShockwaveAttackSparks(int x, int y, int z, byte r, byte g, byte b, byte size);
}

View file

@ -28,6 +28,7 @@
#include "Objects/TR1/Trap/DamoclesSword.h"
#include "Objects/TR1/Trap/SlammingDoors.h"
#include "Objects/TR1/Trap/SwingingBlade.h"
#include "Objects/TR1/Trap/ElectricBall.h"
using namespace TEN::Entities::Creatures::TR1;
using namespace TEN::Entities::Traps;
@ -269,6 +270,27 @@ static void StartTrap(ObjectInfo* obj)
obj->shadowType = ShadowMode::All;
obj->SetHitEffect(true);
}
obj = &Objects[ID_ELECTRIC_BALL];
if (obj->loaded)
{
obj->Initialize = InitializeElectricBall;
obj->control = ControlElectricBall;
obj->collision = GenericSphereBoxCollision;
obj->shadowType = ShadowMode::All;
obj->HitPoints = NOT_TARGETABLE;
obj->radius = 512;
obj->intelligent = true;
obj->SetHitEffect(true);
}
obj = &Objects[ID_ELECTRIC_BALL_IMPACT_POINT];
if (obj->loaded)
{
obj->Initialize = InitializeElectricBallImpactPoint;
obj->drawRoutine = nullptr;
obj->usingDrawAnimatingItem = false;
}
}
static void StartProjectiles(ObjectInfo* obj)

View file

@ -83,7 +83,7 @@ void ControlTeleporter(short itemNumber)
BYTE1(v21) |= 0x80u;
TriggerLightningGlow(src.x, src.y, src.z, (item->itemFlags[0] >> 3) | ((v20 | (v21 << 8)) << 7));
v22 = GetRandomControl();
TriggerDynamicLight(src.x, src.y, src.z, (v22 & 3) + (item->itemFlags[0] >> 5) + 8, v12, v16, v13);
SpawnDynamicLight(src.x, src.y, src.z, (v22 & 3) + (item->itemFlags[0] >> 5) + 8, v12, v16, v13);
}
LOBYTE(v3) = GetRandomControl();
if (v3 & 1)

View file

@ -369,6 +369,8 @@ enum GAME_OBJECT_ID : short
ID_ELECTRIC_CLEANER,
ID_SLAMMING_DOORS,
ID_SWINGING_BLADE,
ID_ELECTRIC_BALL,
ID_ELECTRIC_BALL_IMPACT_POINT,
ID_PUZZLE_ITEM1 = 500,
ID_PUZZLE_ITEM2,

View file

@ -308,9 +308,16 @@ namespace TEN::Renderer
AddSpriteBillboardConstrained(
&_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LIGHTHING],
center, Vector4(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f), PI_DIV_2, 1.0f,
Vector2(arc.width * 8, Vector3::Distance(origin, target)),
BlendMode::Additive, dir, true, view);
center,
Vector4(255.0f, 255.0f, 255.0f, 0.5f),
PI_DIV_2, 1.0f, Vector2(4, Vector3::Distance(origin, target)), BlendMode::Additive, dir, true, view);
AddSpriteBillboardConstrained(
&_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_LIGHTHING],
center,
Vector4(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f),
PI_DIV_2, 1.0f, Vector2(arc.width * 8, Vector3::Distance(origin, target)), BlendMode::Additive, dir, true, view);
}
}
}
@ -864,6 +871,9 @@ namespace TEN::Renderer
p2 = Vector3::Transform(p2, rotMatrix);
p3 = Vector3::Transform(p3, rotMatrix);
if (shockwave->style == (int)ShockwaveStyle::Invisible)
return;
if (shockwave->style == (int)ShockwaveStyle::Normal)
{
angle -= PI / 8.0f;

View file

@ -1553,6 +1553,8 @@ static const std::unordered_map<std::string, GAME_OBJECT_ID> kObjIDs{
{ "ELECTRIC_CLEANER", ID_ELECTRIC_CLEANER },
{ "SLAMMING_DOORS", ID_SLAMMING_DOORS },
{ "SWINGING_BLADE", ID_SWINGING_BLADE },
{ "ELECTRIC_BALL", ID_ELECTRIC_BALL },
{ "ELECTRIC_BALL_IMPACT_POINT", ID_ELECTRIC_BALL_IMPACT_POINT },
{ "PUZZLE_ITEM1", ID_PUZZLE_ITEM1 },
{ "PUZZLE_ITEM2", ID_PUZZLE_ITEM2 },
{ "PUZZLE_ITEM3", ID_PUZZLE_ITEM3 },

View file

@ -604,6 +604,7 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings.
<ClInclude Include="Objects\TR1\Entity\tr1_wolf.h" />
<ClInclude Include="Objects\TR1\Entity\WingedMutant.h" />
<ClInclude Include="Objects\TR1\Trap\DamoclesSword.h" />
<ClInclude Include="Objects\TR1\Trap\ElectricBall.h" />
<ClInclude Include="Objects\TR1\Trap\SlammingDoors.h" />
<ClInclude Include="Objects\TR1\tr1_objects.h" />
<ClInclude Include="Objects\TR1\Trap\SwingingBlade.h" />
@ -1130,6 +1131,7 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings.
<ClCompile Include="Objects\TR1\Entity\WingedMutant.cpp" />
<ClCompile Include="Objects\TR1\tr1_objects.cpp" />
<ClCompile Include="Objects\TR1\Trap\DamoclesSword.cpp" />
<ClCompile Include="Objects\TR1\Trap\ElectricBall.cpp" />
<ClCompile Include="Objects\TR1\Trap\SlammingDoors.cpp" />
<ClCompile Include="Objects\TR1\Trap\SwingingBlade.cpp" />
<ClCompile Include="Objects\TR2\Entity\Bartoli.cpp" />