mirror of
https://github.com/TombEngine/TombEngine.git
synced 2025-05-10 20:46:47 +03:00
Move FlashFader to environment controller
This commit is contained in:
parent
913190ce25
commit
a18a385f53
11 changed files with 168 additions and 136 deletions
|
@ -11,6 +11,7 @@
|
|||
#include "lot.h"
|
||||
#include "collide.h"
|
||||
#include "effects\debris.h"
|
||||
#include "effects\weather.h"
|
||||
#include "lara_two_guns.h"
|
||||
#include "switch.h"
|
||||
#include "objects.h"
|
||||
|
@ -27,6 +28,7 @@
|
|||
#include "generic_switch.h"
|
||||
|
||||
using namespace TEN::Entities::Switches;
|
||||
using namespace TEN::Effects::Environment;
|
||||
|
||||
extern GameFlow* g_GameFlow;
|
||||
|
||||
|
@ -392,20 +394,20 @@ void ControlGrenade(short itemNumber)
|
|||
if (item->itemFlags[0] == GRENADE_FLASH)
|
||||
{
|
||||
// Flash grenades
|
||||
int R, G, B;
|
||||
if (item->itemFlags[1] == 1)
|
||||
{
|
||||
WeaponEnemyTimer = 120;
|
||||
FlashFadeR = 255;
|
||||
FlashFadeG = 255;
|
||||
FlashFadeB = 255;
|
||||
R = 255;
|
||||
G = 255;
|
||||
B = 255;
|
||||
}
|
||||
else
|
||||
{
|
||||
FlashFadeR = (GetRandomControl() & 0x1F) + 224;
|
||||
FlashFadeG = FlashFadeB = FlashFadeR - GetRandomControl() & 0x1F;
|
||||
R = (GetRandomControl() & 0x1F) + 224;
|
||||
G = B = R - GetRandomControl() & 0x1F;
|
||||
}
|
||||
|
||||
FlashFader = 32;
|
||||
Weather.Flash(R, G, B, 0.03f);
|
||||
|
||||
TriggerFlashSmoke(item->pos.xPos, item->pos.yPos, item->pos.zPos, item->roomNumber);
|
||||
TriggerFlashSmoke(item->pos.xPos, item->pos.yPos, item->pos.zPos, item->roomNumber);
|
||||
|
@ -740,11 +742,7 @@ void ControlGrenade(short itemNumber)
|
|||
{
|
||||
if (item->itemFlags[0] == GRENADE_FLASH)
|
||||
{
|
||||
FlashFader = 32;
|
||||
FlashFadeR = 255;
|
||||
FlashFadeG = 255;
|
||||
FlashFadeB = 255;
|
||||
|
||||
Weather.Flash(255, 255, 255, 0.03f);
|
||||
TriggerFlashSmoke(item->pos.xPos, item->pos.yPos, item->pos.zPos, item->roomNumber);
|
||||
TriggerFlashSmoke(item->pos.xPos, item->pos.yPos, item->pos.zPos, item->roomNumber);
|
||||
}
|
||||
|
|
|
@ -7,13 +7,17 @@
|
|||
#include "lara_fire.h"
|
||||
#include "lara.h"
|
||||
#include "effects\flmtorch.h"
|
||||
#include "effects\weather.h"
|
||||
#include "sphere.h"
|
||||
#include "level.h"
|
||||
#include "collide.h"
|
||||
#include "Sound\sound.h"
|
||||
#include "savegame.h"
|
||||
#include "input.h"
|
||||
|
||||
using TEN::Renderer::g_Renderer;
|
||||
using namespace TEN::Effects::Environment;
|
||||
|
||||
struct OLD_CAMERA
|
||||
{
|
||||
short currentAnimState;
|
||||
|
@ -791,11 +795,6 @@ void FixedCamera(ITEM_INFO* item)
|
|||
|
||||
if (camera->flags & 2)
|
||||
{
|
||||
if (FlashFader > 2)
|
||||
{
|
||||
FlashFader = (FlashFader >> 1) & 0xFE;
|
||||
}
|
||||
|
||||
SniperOverlay = 1;
|
||||
|
||||
Camera.target.x = (Camera.target.x + 2 * LastTarget.x) / 3;
|
||||
|
@ -825,11 +824,11 @@ void FixedCamera(ITEM_INFO* item)
|
|||
{
|
||||
SoundEffect(SFX_TR4_EXPLOSION1, 0, 83886084);
|
||||
SoundEffect(SFX_TR5_HK_FIRE, 0, 0);
|
||||
|
||||
FlashFadeR = 192;
|
||||
FlashFadeB = 0;
|
||||
FlashFader = 24;
|
||||
FlashFadeG = (GetRandomControl() & 0x1F) + 160;
|
||||
|
||||
auto R = 192;
|
||||
auto G = (GetRandomControl() & 0x1F) + 160;
|
||||
auto B = 0;
|
||||
Weather.Flash(R, G, B, 0.04f);
|
||||
|
||||
SniperCount = 15;
|
||||
|
||||
|
|
|
@ -121,10 +121,6 @@ int TriggerTimer;
|
|||
int JustLoaded;
|
||||
int OldLaraBusy;
|
||||
int Infrared;
|
||||
short FlashFadeR;
|
||||
short FlashFadeG;
|
||||
short FlashFadeB;
|
||||
short FlashFader;
|
||||
|
||||
std::vector<short> OutsideRoomTable[OUTSIDE_SIZE][OUTSIDE_SIZE];
|
||||
short IsRoomOutsideNo;
|
||||
|
|
|
@ -94,10 +94,6 @@ extern int TriggerTimer;
|
|||
extern int JustLoaded;
|
||||
extern int OldLaraBusy;
|
||||
extern int Infrared;
|
||||
extern short FlashFadeR;
|
||||
extern short FlashFadeG;
|
||||
extern short FlashFadeB;
|
||||
extern short FlashFader;
|
||||
extern std::vector<short> OutsideRoomTable[OUTSIDE_SIZE][OUTSIDE_SIZE];
|
||||
extern short IsRoomOutsideNo;
|
||||
|
||||
|
|
|
@ -16,45 +16,77 @@ namespace Environment
|
|||
{
|
||||
GameScriptLevel* level = g_GameFlow->GetLevel(CurrentLevel);
|
||||
|
||||
// Sky
|
||||
UpdateSky(level);
|
||||
UpdateStorm(level);
|
||||
UpdateWind(level);
|
||||
UpdateFlash(level);
|
||||
}
|
||||
|
||||
void EnvironmentController::Clear()
|
||||
{
|
||||
// Clear storm vars
|
||||
StormTimer = 0;
|
||||
StormSkyColor = 1;
|
||||
StormSkyColor2 = 1;
|
||||
|
||||
// Clear wind vars
|
||||
WindCurrent = WindFinalX = WindFinalZ = 0;
|
||||
WindAngle = WindDAngle = 2048;
|
||||
|
||||
// Clear flash vars
|
||||
FlashProgress = 0.0f;
|
||||
FlashColorBase = Vector3(0, 0, 0);
|
||||
}
|
||||
|
||||
void EnvironmentController::Flash(int r, int g, int b, float speed)
|
||||
{
|
||||
FlashProgress = 1.0f;
|
||||
FlashSpeed = std::clamp(speed, 0.01f, 1.0f);
|
||||
FlashColorBase = Vector3(std::clamp(r, 0, UCHAR_MAX) / (float)UCHAR_MAX,
|
||||
std::clamp(g, 0, UCHAR_MAX) / (float)UCHAR_MAX,
|
||||
std::clamp(b, 0, UCHAR_MAX) / (float)UCHAR_MAX);
|
||||
}
|
||||
|
||||
void EnvironmentController::UpdateSky(GameScriptLevel* level)
|
||||
{
|
||||
if (level->Layer1.Enabled)
|
||||
{
|
||||
Position1 += level->Layer1.CloudSpeed;
|
||||
if (Position1 <= 9728)
|
||||
SkyPosition1 += level->Layer1.CloudSpeed;
|
||||
if (SkyPosition1 <= 9728)
|
||||
{
|
||||
if (Position1 < 0)
|
||||
Position1 += 9728;
|
||||
if (SkyPosition1 < 0)
|
||||
SkyPosition1 += 9728;
|
||||
}
|
||||
else
|
||||
{
|
||||
Position1 -= 9728;
|
||||
SkyPosition1 -= 9728;
|
||||
}
|
||||
}
|
||||
|
||||
if (level->Layer2.Enabled)
|
||||
{
|
||||
Position2 += level->Layer2.CloudSpeed;
|
||||
if (Position2 <= 9728)
|
||||
SkyPosition2 += level->Layer2.CloudSpeed;
|
||||
if (SkyPosition2 <= 9728)
|
||||
{
|
||||
if (Position2 < 0)
|
||||
Position2 += 9728;
|
||||
if (SkyPosition2 < 0)
|
||||
SkyPosition2 += 9728;
|
||||
}
|
||||
else
|
||||
{
|
||||
Position2 -= 9728;
|
||||
SkyPosition2 -= 9728;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EnvironmentController::UpdateStorm(GameScriptLevel* level)
|
||||
{
|
||||
auto color = Vector4(level->Layer1.R / 255.0f, level->Layer1.G / 255.0f, level->Layer1.B / 255.0f, 1.0f);
|
||||
|
||||
// Storm
|
||||
|
||||
if (level->Storm)
|
||||
{
|
||||
if (LightningCount || LightningRand)
|
||||
if (StormCount || StormRand)
|
||||
{
|
||||
UpdateStorm();
|
||||
UpdateLightning();
|
||||
if (StormTimer > -1)
|
||||
StormTimer--;
|
||||
if (!StormTimer)
|
||||
|
@ -62,75 +94,75 @@ namespace Environment
|
|||
}
|
||||
else if (!(rand() & 0x7F))
|
||||
{
|
||||
LightningCount = (rand() & 0x1F) + 16;
|
||||
StormCount = (rand() & 0x1F) + 16;
|
||||
StormTimer = (rand() & 3) + 12;
|
||||
}
|
||||
|
||||
auto flashBrightness = SkyStormColor / 255.0f;
|
||||
auto flashBrightness = StormSkyColor / 255.0f;
|
||||
auto r = std::clamp(color.x + flashBrightness, 0.0f, 1.0f);
|
||||
auto g = std::clamp(color.y + flashBrightness, 0.0f, 1.0f);
|
||||
auto b = std::clamp(color.z + flashBrightness, 0.0f, 1.0f);
|
||||
|
||||
Color = Vector4(r, g, b, color.w);
|
||||
SkyCurrentColor = Vector4(r, g, b, color.w);
|
||||
}
|
||||
else
|
||||
Color = color;
|
||||
|
||||
// Wind
|
||||
|
||||
CurrentWind += (GetRandomControl() & 7) - 3;
|
||||
if (CurrentWind <= -2)
|
||||
CurrentWind++;
|
||||
else if (CurrentWind >= 9)
|
||||
CurrentWind--;
|
||||
|
||||
DWindAngle = (DWindAngle + 2 * (GetRandomControl() & 63) - 64) & 0x1FFE;
|
||||
|
||||
if (DWindAngle < 1024)
|
||||
DWindAngle = 2048 - DWindAngle;
|
||||
else if (DWindAngle > 3072)
|
||||
DWindAngle += 6144 - 2 * DWindAngle;
|
||||
|
||||
WindAngle = (WindAngle + ((DWindAngle - WindAngle) >> 3)) & 0x1FFE;
|
||||
|
||||
FinalWindX = CurrentWind * phd_sin(WindAngle << 3);
|
||||
FinalWindZ = CurrentWind * phd_cos(WindAngle << 3);
|
||||
SkyCurrentColor = color;
|
||||
}
|
||||
|
||||
void EnvironmentController::Clear()
|
||||
void EnvironmentController::UpdateLightning()
|
||||
{
|
||||
StormTimer = 0;
|
||||
SkyStormColor = 1;
|
||||
SkyStormColor2 = 1;
|
||||
StormCount--;
|
||||
|
||||
CurrentWind = FinalWindX = FinalWindZ = 0;
|
||||
WindAngle = DWindAngle = 2048;
|
||||
}
|
||||
|
||||
void EnvironmentController::UpdateStorm()
|
||||
{
|
||||
LightningCount--;
|
||||
|
||||
if (LightningCount <= 0)
|
||||
if (StormCount <= 0)
|
||||
{
|
||||
SkyStormColor = 0;
|
||||
LightningRand = 0;
|
||||
StormSkyColor = 0;
|
||||
StormRand = 0;
|
||||
}
|
||||
else if (LightningCount < 5 && SkyStormColor < 50)
|
||||
else if (StormCount < 5 && StormSkyColor < 50)
|
||||
{
|
||||
auto newColor =SkyStormColor - LightningCount * 2;
|
||||
auto newColor = StormSkyColor - StormCount * 2;
|
||||
if (newColor < 0)
|
||||
newColor = 0;
|
||||
SkyStormColor = newColor;
|
||||
StormSkyColor = newColor;
|
||||
}
|
||||
else if (LightningCount)
|
||||
else if (StormCount)
|
||||
{
|
||||
LightningRand = ((rand() & 0x1FF - LightningRand) >> 1) + LightningRand;
|
||||
SkyStormColor2 += LightningRand * SkyStormColor2 >> 8;
|
||||
SkyStormColor = SkyStormColor2;
|
||||
if (SkyStormColor > 255)
|
||||
SkyStormColor = 255;
|
||||
StormRand = ((rand() & 0x1FF - StormRand) >> 1) + StormRand;
|
||||
StormSkyColor2 += StormRand * StormSkyColor2 >> 8;
|
||||
StormSkyColor = StormSkyColor2;
|
||||
if (StormSkyColor > 255)
|
||||
StormSkyColor = 255;
|
||||
}
|
||||
}
|
||||
|
||||
void EnvironmentController::UpdateWind(GameScriptLevel* level)
|
||||
{
|
||||
WindCurrent += (GetRandomControl() & 7) - 3;
|
||||
if (WindCurrent <= -2)
|
||||
WindCurrent++;
|
||||
else if (WindCurrent >= 9)
|
||||
WindCurrent--;
|
||||
|
||||
WindDAngle = (WindDAngle + 2 * (GetRandomControl() & 63) - 64) & 0x1FFE;
|
||||
|
||||
if (WindDAngle < 1024)
|
||||
WindDAngle = 2048 - WindDAngle;
|
||||
else if (WindDAngle > 3072)
|
||||
WindDAngle += 6144 - 2 * WindDAngle;
|
||||
|
||||
WindAngle = (WindAngle + ((WindDAngle - WindAngle) >> 3)) & 0x1FFE;
|
||||
|
||||
WindFinalX = WindCurrent * phd_sin(WindAngle << 3);
|
||||
WindFinalZ = WindCurrent * phd_cos(WindAngle << 3);
|
||||
}
|
||||
|
||||
void EnvironmentController::UpdateFlash(GameScriptLevel* level)
|
||||
{
|
||||
if (FlashProgress > 0.0f)
|
||||
{
|
||||
FlashProgress -= FlashSpeed;
|
||||
if (FlashProgress < 0.0f)
|
||||
FlashProgress = 0.0f;
|
||||
}
|
||||
}
|
||||
}}}
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include <SimpleMath.h>
|
||||
#include "Scripting/GameScriptLevel.h"
|
||||
|
||||
using namespace DirectX::SimpleMath;
|
||||
|
||||
|
@ -10,35 +11,46 @@ namespace Environment {
|
|||
class EnvironmentController
|
||||
{
|
||||
public:
|
||||
Vector3 Wind() { return Vector3(FinalWindX / 2.0f, 0, FinalWindZ / 2.0f); }
|
||||
Vector4 SkyColor() { return Color; }
|
||||
short SkyLayer1Position() { return Position1; }
|
||||
short SkyLayer2Position() { return Position2; }
|
||||
Vector3 Wind() { return Vector3(WindFinalX / 2.0f, 0, WindFinalZ / 2.0f); }
|
||||
Vector4 FlashColor() { return FlashColorBase * sin(FlashProgress * PI / 2);; }
|
||||
Vector4 SkyColor() { return SkyCurrentColor; }
|
||||
short SkyLayer1Position() { return SkyPosition1; }
|
||||
short SkyLayer2Position() { return SkyPosition2; }
|
||||
|
||||
void Flash(int r, int g, int b, float speed);
|
||||
void Update();
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
// Sky
|
||||
Vector4 Color;
|
||||
short Position1;
|
||||
short Position2;
|
||||
Vector4 SkyCurrentColor;
|
||||
short SkyPosition1;
|
||||
short SkyPosition2;
|
||||
|
||||
// Wind
|
||||
int FinalWindX;
|
||||
int FinalWindZ;
|
||||
int WindFinalX;
|
||||
int WindFinalZ;
|
||||
int WindAngle;
|
||||
int DWindAngle;
|
||||
int CurrentWind;
|
||||
int WindDAngle;
|
||||
int WindCurrent;
|
||||
|
||||
// Flash fader
|
||||
Vector3 FlashColorBase = {};
|
||||
float FlashSpeed = 1.0f;
|
||||
float FlashProgress = 0.0f;
|
||||
|
||||
// Lightning
|
||||
int LightningCount;
|
||||
int LightningRand;
|
||||
int StormTimer;
|
||||
byte SkyStormColor = 1;
|
||||
byte SkyStormColor2 = 1;
|
||||
int StormCount;
|
||||
int StormRand;
|
||||
int StormTimer;
|
||||
byte StormSkyColor = 1;
|
||||
byte StormSkyColor2 = 1;
|
||||
|
||||
void UpdateStorm();
|
||||
void UpdateSky(GameScriptLevel* level);
|
||||
void UpdateStorm(GameScriptLevel* level);
|
||||
void UpdateWind(GameScriptLevel* level);
|
||||
void UpdateFlash(GameScriptLevel* level);
|
||||
void UpdateLightning();
|
||||
};
|
||||
|
||||
extern EnvironmentController Weather;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#include "framework.h"
|
||||
#include "traps.h"
|
||||
|
||||
#include "items.h"
|
||||
#include "effects\effects.h"
|
||||
#include "effects\tomb4fx.h"
|
||||
#include "effects\weather.h"
|
||||
#include "lara.h"
|
||||
#include "collide.h"
|
||||
#include "sphere.h"
|
||||
|
@ -15,6 +15,8 @@
|
|||
#include "Sound\sound.h"
|
||||
#include "kayak.h"
|
||||
|
||||
using namespace TEN::Effects::Environment;
|
||||
|
||||
static short WreckingBallData[2] = {0, 0};
|
||||
ITEM_INFO* WBItem;
|
||||
short WBRoom;
|
||||
|
@ -221,10 +223,7 @@ void FlameEmitter2Control(short itemNumber)
|
|||
|
||||
if (g_Level.Rooms[roomNumber].flags & ENV_FLAG_WATER)
|
||||
{
|
||||
FlashFadeR = 255;
|
||||
FlashFadeG = 128;
|
||||
FlashFadeB = 0;
|
||||
FlashFader = 32;
|
||||
Weather.Flash(255, 128, 0, 0.03f);
|
||||
KillItem(itemNumber);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "control.h"
|
||||
#include "sphere.h"
|
||||
#include "effects\effects.h"
|
||||
#include "effects\weather.h"
|
||||
#include "Sound\sound.h"
|
||||
#include "setup.h"
|
||||
#include "box.h"
|
||||
|
@ -14,6 +15,8 @@
|
|||
#include "lot.h"
|
||||
#include "creature_info.h"
|
||||
|
||||
using namespace TEN::Effects::Environment;
|
||||
|
||||
namespace TEN::Entities::TR4
|
||||
{
|
||||
enum AHMET_STATE
|
||||
|
@ -366,10 +369,7 @@ namespace TEN::Entities::TR4
|
|||
if (item->currentAnimState != 7 || item->frameNumber != g_Level.Anims[item->animNumber].frameEnd)
|
||||
return false;
|
||||
|
||||
FlashFadeR = 255;
|
||||
FlashFadeG = 64;
|
||||
FlashFadeB = 0;
|
||||
FlashFader = 32;
|
||||
Weather.Flash(255, 64, 0, 0.03f);
|
||||
|
||||
item->pos.xPos = (item->itemFlags[0] * 1024) + 512;
|
||||
item->pos.yPos = (item->itemFlags[1] * 256);
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
#include "misc.h"
|
||||
#include "Lara.h"
|
||||
#include "effects\effects.h"
|
||||
#include "effects\weather.h"
|
||||
#include "effects\tomb4fx.h"
|
||||
#include "creature_info.h"
|
||||
|
||||
using std::vector;
|
||||
using namespace TEN::Effects::Environment;
|
||||
|
||||
BaboonRespawnClass BaboonRespawn;
|
||||
static BITE_INFO baboonBite = { 10, 10, 11, 4 };
|
||||
|
@ -99,10 +101,7 @@ void BaboonDieEffect(ITEM_INFO* item)
|
|||
TriggerBaboonShockwave(pos, ANGLE(135.0f));
|
||||
|
||||
// trigger flash screen
|
||||
FlashFadeR = 255;
|
||||
FlashFadeG = 64;
|
||||
FlashFadeB = 0;
|
||||
FlashFader = 32;
|
||||
Weather.Flash(255, 64, 0, 0.03f);
|
||||
}
|
||||
|
||||
static void KillRespawnedBaboon(short itemNumber, bool remove = false)
|
||||
|
|
|
@ -5,10 +5,13 @@
|
|||
#include "Sound\sound.h"
|
||||
#include "effects\effects.h"
|
||||
#include "effects\tomb4fx.h"
|
||||
#include "effects\weather.h"
|
||||
#include "items.h"
|
||||
#include "collide.h"
|
||||
#include "objectslist.h"
|
||||
|
||||
using namespace TEN::Effects::Environment;
|
||||
|
||||
void InitialiseMine(short itemNum)
|
||||
{
|
||||
ITEM_INFO* item = &g_Level.Items[itemNum];
|
||||
|
@ -47,10 +50,7 @@ void MineControl(short itemNum)
|
|||
ExplodeItemNode(item, i, 0, -128);
|
||||
}
|
||||
|
||||
FlashFadeR = 255;
|
||||
FlashFadeG = 192;
|
||||
FlashFadeB = 64;
|
||||
FlashFader = 32;
|
||||
Weather.Flash(255, 192, 64, 0.03f);
|
||||
|
||||
short currentItemNumber = g_Level.Rooms[item->roomNumber].itemNumber;
|
||||
|
||||
|
|
|
@ -4,9 +4,13 @@
|
|||
#include "level.h"
|
||||
#include "control.h"
|
||||
#include "Sound\sound.h"
|
||||
#include "effects\weather.h"
|
||||
#include "lara.h"
|
||||
#include "camera.h"
|
||||
#include "Box.h"
|
||||
|
||||
using namespace TEN::Effects::Environment;
|
||||
|
||||
void InitialiseTeleporter(short itemNumber)
|
||||
{
|
||||
/*ITEM_INFO* item = &g_Level.Items[itemNumber];
|
||||
|
@ -55,11 +59,6 @@ void ControlTeleporter(short itemNumber)
|
|||
ITEM_INFO* targetItem = &g_Level.Items[item->itemFlags[1]];
|
||||
SoundEffect(SFX_RICH_TELEPORT, &targetItem->pos, (flags << 8) | 8);
|
||||
|
||||
if (FlashFader > 4)
|
||||
{
|
||||
FlashFader = (FlashFader >> 1) & 0xFE;
|
||||
}
|
||||
|
||||
if (GlobalCounter & 1)
|
||||
{
|
||||
PHD_VECTOR src;
|
||||
|
@ -93,10 +92,12 @@ void ControlTeleporter(short itemNumber)
|
|||
v23 = item->itemFlags[0];
|
||||
v24 = item->itemFlags[0];
|
||||
v25 = GetRandomControl();
|
||||
FlashFadeR = v23;
|
||||
FlashFadeB = v24 >> 2;
|
||||
FlashFader = 32;
|
||||
FlashFadeG = v24 - v25 % (v24 >> 1);
|
||||
|
||||
auto R = v23;
|
||||
auto G = v24 - v25 % (v24 >> 1);
|
||||
auto B = v24 >> 2;
|
||||
Weather.Flash(R, G, B, 0.03f);
|
||||
|
||||
LOBYTE(v3) = SoundEffect(399, 0, 0);
|
||||
}
|
||||
if (!(GlobalCounter & 3))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue