This commit is contained in:
parent
e216c50350
commit
961ad4e212
9 changed files with 169 additions and 25 deletions
Binary file not shown.
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
11
makefile.gfx
11
makefile.gfx
|
@ -174,10 +174,12 @@ INGAMEFX_GFX_DIR := $(GRAF_DIR)/ingamefx
|
||||||
INGAMEFX_GFX_TRANS := +bubble_1 +bubble_2 +bubble_3 \
|
INGAMEFX_GFX_TRANS := +bubble_1 +bubble_2 +bubble_3 \
|
||||||
+health_full_1 +health_full_2 +health_full_3 +health_full_4 +health_full_5 \
|
+health_full_1 +health_full_2 +health_full_3 +health_full_4 +health_full_5 \
|
||||||
+health_empty_1 +health_empty_2 +health_empty_3 +health_empty_4 +health_empty_5 \
|
+health_empty_1 +health_empty_2 +health_empty_3 +health_empty_4 +health_empty_5 \
|
||||||
+watermeter +netblob \
|
+watermeter +waterhilight +netblob \
|
||||||
+spike
|
+spike
|
||||||
|
INGAMEFX_GFX_TRANS_NONROT_NONCLIP := +water
|
||||||
|
|
||||||
INGAMEFX_GFX_TRANS_IN := $(foreach FILE,$(INGAMEFX_GFX_TRANS),$(INGAMEFX_GFX_DIR)/$(FILE).bmp)
|
INGAMEFX_GFX_TRANS_IN := $(foreach FILE,$(INGAMEFX_GFX_TRANS),$(INGAMEFX_GFX_DIR)/$(FILE).bmp)
|
||||||
|
INGAMEFX_GFX_TRANS_NONROT_NONCLIP_IN := $(foreach FILE,$(INGAMEFX_GFX_TRANS_NONROT_NONCLIP),$(INGAMEFX_GFX_DIR)/$(FILE).bmp)
|
||||||
|
|
||||||
####
|
####
|
||||||
|
|
||||||
|
@ -229,11 +231,12 @@ UI_GFX_TRANS_IN := $(foreach FILE,$(UI_GFX_TRANS),$(UI_GFX_DIR)/$(FILE))
|
||||||
|
|
||||||
####
|
####
|
||||||
|
|
||||||
INGAMEGFX_SPR_DEP := $(INGAMEFX_GFX_NONTRANS_IN) $(INGAMEFX_GFX_TRANS_IN) $(PICKUP_GFX_IN) \
|
INGAMEGFX_SPR_DEP := $(INGAMEFX_GFX_NONTRANS_IN) $(INGAMEFX_GFX_TRANS_IN) $(INGAMEFX_GFX_TRANS_NONROT_NONCLIP_IN) \
|
||||||
$(UI_GFX_FONT_IN) $(UI_GFX_NONTRANS_IN) $(UI_GFX_TRANS_IN)
|
$(PICKUP_GFX_IN) $(UI_GFX_FONT_IN) $(UI_GFX_NONTRANS_IN) $(UI_GFX_TRANS_IN)
|
||||||
|
|
||||||
INGAMEGFX_SPR_IN := -c+ -z+ $(INGAMEFX_GFX_TRANS_IN) $(PICKUP_GFX_IN) $(UI_GFX_FONT_IN) $(UI_GFX_TRANS_IN) \
|
INGAMEGFX_SPR_IN := -c+ -z+ $(INGAMEFX_GFX_TRANS_IN) $(PICKUP_GFX_IN) $(UI_GFX_FONT_IN) $(UI_GFX_TRANS_IN) \
|
||||||
-c- -z- $(INGAMEFX_GFX_NONTRANS_IN) $(UI_GFX_NONTRANS_IN)
|
-c- -z- $(INGAMEFX_GFX_NONTRANS_IN) $(UI_GFX_NONTRANS_IN) \
|
||||||
|
-c- -q- -z+ -r- $(INGAMEFX_GFX_TRANS_NONROT_NONCLIP_IN)
|
||||||
|
|
||||||
#----------------------------------------------------------------------------
|
#----------------------------------------------------------------------------
|
||||||
# Front end graphics
|
# Front end graphics
|
||||||
|
|
|
@ -309,6 +309,10 @@ void CGameScene::initLevel()
|
||||||
DVECTOR mapSize=Level.getMapSize();
|
DVECTOR mapSize=Level.getMapSize();
|
||||||
CPlayer::CameraBox camBox={0,0,mapSize.vx,mapSize.vy};
|
CPlayer::CameraBox camBox={0,0,mapSize.vx,mapSize.vy};
|
||||||
m_player->setCameraBox(camBox);
|
m_player->setCameraBox(camBox);
|
||||||
|
if(s_globalLevelSelectThing==1)
|
||||||
|
{
|
||||||
|
m_player->setHealthType(CPlayer::HEALTH_TYPE__OUT_OF_WATER);
|
||||||
|
}
|
||||||
|
|
||||||
// Init actors (needs moving and tidying
|
// Init actors (needs moving and tidying
|
||||||
int actorNum;
|
int actorNum;
|
||||||
|
|
|
@ -126,8 +126,6 @@
|
||||||
#define _STATE_DEBUG_
|
#define _STATE_DEBUG_
|
||||||
|
|
||||||
|
|
||||||
#define SLIPSPEED 10 // Speed that player slips on icy surfaces
|
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
Structure defintions
|
Structure defintions
|
||||||
-------------------- */
|
-------------------- */
|
||||||
|
@ -261,6 +259,9 @@ pint ledgeShift=1;
|
||||||
|
|
||||||
pint cammove=2;
|
pint cammove=2;
|
||||||
|
|
||||||
|
pint waterDrainSpeed=4;
|
||||||
|
pint waterSoakUpSpeed=20;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --------------------------------- Addon stuff ---------------------------------
|
// --------------------------------- Addon stuff ---------------------------------
|
||||||
|
@ -493,6 +494,9 @@ registerAddon(PLAYER_ADDON_JELLYLAUNCHER);
|
||||||
registerAddon(PLAYER_ADDON_GLASSES);
|
registerAddon(PLAYER_ADDON_GLASSES);
|
||||||
registerAddon(PLAYER_ADDON_BUBBLEWAND);
|
registerAddon(PLAYER_ADDON_BUBBLEWAND);
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
|
|
||||||
|
setHealthType(HEALTH_TYPE__NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
|
@ -542,6 +546,14 @@ void CPlayer::think(int _frames)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
if(m_healthType==HEALTH_TYPE__OUT_OF_WATER&&m_currentMode!=PLAYER_MODE_DEAD)
|
||||||
|
{
|
||||||
|
m_healthWaterLevel-=waterDrainSpeed*_frames;
|
||||||
|
if(m_healthWaterLevel<=0)
|
||||||
|
{
|
||||||
|
dieYouPorousFreak();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(PadGetDown(0)&PAD_L1)
|
if(PadGetDown(0)&PAD_L1)
|
||||||
{
|
{
|
||||||
|
@ -808,11 +820,6 @@ static int lastposnum=0;
|
||||||
int mouth=-1,eyes=-1;
|
int mouth=-1,eyes=-1;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
int itembaseX=110;
|
|
||||||
int itembaseY=60;
|
|
||||||
int itemgap=40;
|
|
||||||
|
|
||||||
#include "gui\gui.h"
|
#include "gui\gui.h"
|
||||||
void CPlayer::render()
|
void CPlayer::render()
|
||||||
{
|
{
|
||||||
|
@ -861,7 +868,9 @@ for(int i=0;i<NUM_LASTPOS;i++)
|
||||||
|
|
||||||
|
|
||||||
// Health
|
// Health
|
||||||
|
if(m_healthType==HEALTH_TYPE__NORMAL)
|
||||||
{
|
{
|
||||||
|
// In water - Use normal SB face for health
|
||||||
static int s_fullHealthFrames[]=
|
static int s_fullHealthFrames[]=
|
||||||
{
|
{
|
||||||
FRM__HEALTH_FULL_1,
|
FRM__HEALTH_FULL_1,
|
||||||
|
@ -902,9 +911,34 @@ for(int i=0;i<NUM_LASTPOS;i++)
|
||||||
y+=ygap;
|
y+=ygap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Out of water - Use bowl of water
|
||||||
|
POLY_FT4 *ft4;
|
||||||
|
sFrameHdr *fh;
|
||||||
|
int V,W,H,partH;
|
||||||
|
|
||||||
|
ft4=m_spriteBank->printFT4(FRM__WATERHILIGHT,HEALTH_ICONX,HEALTH_ICONY,0,0,0);
|
||||||
|
setSemiTrans(ft4,true);
|
||||||
|
|
||||||
|
m_spriteBank->printFT4(FRM__WATERMETER,HEALTH_ICONX,HEALTH_ICONY,0,0,0);
|
||||||
|
|
||||||
|
fh=m_spriteBank->getFrameHeader(FRM__WATER);
|
||||||
|
ft4=m_spriteBank->printFT4(fh,0,0,0,0,0);
|
||||||
|
setSemiTrans(ft4,true);
|
||||||
|
V=fh->V;
|
||||||
|
W=fh->W;
|
||||||
|
H=fh->H;
|
||||||
|
partH=(H*(255-(m_healthWaterLevel>>WATERLEVELSHIFT)))>>8;
|
||||||
|
if(partH>H)partH=H;
|
||||||
|
setXYWH(ft4,HEALTH_ICONX,HEALTH_ICONY+(partH),W,H-partH);
|
||||||
|
ft4->v0=V+(partH);
|
||||||
|
ft4->v1=V+(partH);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Mode specific ui
|
// Mode specific ui
|
||||||
int itemX=itembaseX;
|
int itemX=COLLECTEDITEM_BASEX;
|
||||||
|
|
||||||
// Pickups
|
// Pickups
|
||||||
m_currentPlayerModeClass->renderModeUi();
|
m_currentPlayerModeClass->renderModeUi();
|
||||||
|
@ -913,16 +947,16 @@ for(int i=0;i<NUM_LASTPOS;i++)
|
||||||
int x,y;
|
int x,y;
|
||||||
sFrameHdr *fh=m_spriteBank->getFrameHeader(FRM__SHOE);
|
sFrameHdr *fh=m_spriteBank->getFrameHeader(FRM__SHOE);
|
||||||
x=itemX-(fh->W/2);
|
x=itemX-(fh->W/2);
|
||||||
y=itembaseY-(fh->H/2);
|
y=COLLECTEDITEM_BASEY-(fh->H/2);
|
||||||
m_spriteBank->printFT4(fh,x+2,y+2,0,0,0);
|
m_spriteBank->printFT4(fh,x+2,y+2,0,0,0);
|
||||||
m_spriteBank->printFT4(fh,x-2,y-2,0,0,0);
|
m_spriteBank->printFT4(fh,x-2,y-2,0,0,0);
|
||||||
itemX+=itemgap;
|
itemX+=COLLECTEDITEM_GAP;
|
||||||
}
|
}
|
||||||
if(isWearingHelmet())
|
if(isWearingHelmet())
|
||||||
{
|
{
|
||||||
sFrameHdr *fh=m_spriteBank->getFrameHeader(FRM__HELMET);
|
sFrameHdr *fh=m_spriteBank->getFrameHeader(FRM__HELMET);
|
||||||
m_spriteBank->printFT4(fh,itemX-(fh->W/2),itembaseY-(fh->H/2),0,0,0);
|
m_spriteBank->printFT4(fh,itemX-(fh->W/2),COLLECTEDITEM_BASEY-(fh->H/2),0,0,0);
|
||||||
itemX+=itemgap;
|
itemX+=COLLECTEDITEM_GAP;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1001,10 +1035,21 @@ int CPlayer::getHeightFromGroundNoPlatform(int _x,int _y,int _maxHeight=32)
|
||||||
---------------------------------------------------------------------- */
|
---------------------------------------------------------------------- */
|
||||||
void CPlayer::addHealth(int _health)
|
void CPlayer::addHealth(int _health)
|
||||||
{
|
{
|
||||||
m_health+=_health;
|
if(m_healthType==HEALTH_TYPE__NORMAL)
|
||||||
if(m_health>MAX_HEALTH)
|
|
||||||
{
|
{
|
||||||
m_health=MAX_HEALTH;
|
m_health+=_health;
|
||||||
|
if(m_health>MAX_HEALTH)
|
||||||
|
{
|
||||||
|
m_health=MAX_HEALTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_healthWaterLevel+=WATERHEALTHPART*_health;
|
||||||
|
if(m_healthWaterLevel>WATERMAXHEALTH)
|
||||||
|
{
|
||||||
|
m_healthWaterLevel=WATERMAXHEALTH;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1240,6 +1285,7 @@ void CPlayer::respawn()
|
||||||
m_allowConversation=false;
|
m_allowConversation=false;
|
||||||
|
|
||||||
m_health=MAX_HEALTH;
|
m_health=MAX_HEALTH;
|
||||||
|
m_healthWaterLevel=WATERMAXHEALTH;
|
||||||
m_healthReactFrames=0;
|
m_healthReactFrames=0;
|
||||||
m_invincibleFrameCount=INVINCIBLE_FRAMES__START;
|
m_invincibleFrameCount=INVINCIBLE_FRAMES__START;
|
||||||
Pos=m_respawnPos;
|
Pos=m_respawnPos;
|
||||||
|
@ -1333,6 +1379,26 @@ int CPlayer::canDoLookAround()
|
||||||
return m_currentPlayerModeClass->canDoLookAround();
|
return m_currentPlayerModeClass->canDoLookAround();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*----------------------------------------------------------------------
|
||||||
|
Function:
|
||||||
|
Purpose:
|
||||||
|
Params:
|
||||||
|
Returns:
|
||||||
|
---------------------------------------------------------------------- */
|
||||||
|
void CPlayer::inSoakUpState()
|
||||||
|
{
|
||||||
|
if(m_healthType==HEALTH_TYPE__OUT_OF_WATER&&
|
||||||
|
(m_layerCollision->getCollisionBlock(Pos.vx,Pos.vy)&COLLISION_TYPE_MASK)==COLLISION_TYPE_FLAG_WATER)
|
||||||
|
{
|
||||||
|
m_healthWaterLevel+=waterSoakUpSpeed;
|
||||||
|
if(m_healthWaterLevel>WATERMAXHEALTH)
|
||||||
|
{
|
||||||
|
m_healthWaterLevel=WATERMAXHEALTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
Function:
|
Function:
|
||||||
Purpose:
|
Purpose:
|
||||||
|
@ -1377,22 +1443,52 @@ void CPlayer::takeDamage(DAMAGE_TYPE _damage)
|
||||||
|
|
||||||
if(ouchThatHurt)
|
if(ouchThatHurt)
|
||||||
{
|
{
|
||||||
|
int died=false;
|
||||||
if(invincibleSponge){m_invincibleFrameCount=INVINCIBLE_FRAMES__HIT;return;}
|
if(invincibleSponge){m_invincibleFrameCount=INVINCIBLE_FRAMES__HIT;return;}
|
||||||
if(m_health)
|
if(m_healthType==HEALTH_TYPE__NORMAL)
|
||||||
{
|
{
|
||||||
m_invincibleFrameCount=INVINCIBLE_FRAMES__HIT;
|
|
||||||
m_healthReactFrames=25;
|
|
||||||
m_health--;
|
m_health--;
|
||||||
|
if(m_health<0)
|
||||||
|
{
|
||||||
|
died=true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CSoundMediator::playSfx(CSoundMediator::SFX_SPONGEBOB_DEFEATED_JINGLE);
|
m_healthWaterLevel-=WATERHEALTHPART;
|
||||||
setMode(PLAYER_MODE_DEAD);
|
if(m_healthWaterLevel<0)
|
||||||
|
{
|
||||||
|
died=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(died)
|
||||||
|
{
|
||||||
|
dieYouPorousFreak();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_invincibleFrameCount=INVINCIBLE_FRAMES__HIT;
|
||||||
|
m_healthReactFrames=25;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*----------------------------------------------------------------------
|
||||||
|
Function:
|
||||||
|
Purpose:
|
||||||
|
Params:
|
||||||
|
Returns:
|
||||||
|
---------------------------------------------------------------------- */
|
||||||
|
void CPlayer::dieYouPorousFreak()
|
||||||
|
{
|
||||||
|
ASSERT(m_currentMode!=PLAYER_MODE_DEAD);
|
||||||
|
|
||||||
|
CSoundMediator::playSfx(CSoundMediator::SFX_SPONGEBOB_DEFEATED_JINGLE);
|
||||||
|
setMode(PLAYER_MODE_DEAD);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
Function:
|
Function:
|
||||||
|
|
|
@ -155,6 +155,10 @@ public:
|
||||||
{
|
{
|
||||||
MAX_HEALTH=5,
|
MAX_HEALTH=5,
|
||||||
MAX_LIVES=99,
|
MAX_LIVES=99,
|
||||||
|
|
||||||
|
WATERLEVELSHIFT=4,
|
||||||
|
WATERMAXHEALTH=(255<<WATERLEVELSHIFT),
|
||||||
|
WATERHEALTHPART=WATERMAXHEALTH/(MAX_HEALTH+1),
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
|
@ -224,7 +228,8 @@ public:
|
||||||
PLAYERINPUT getPadInputHeld() {return m_padInput;}
|
PLAYERINPUT getPadInputHeld() {return m_padInput;}
|
||||||
PLAYERINPUT getPadInputDown() {return m_padInputDown;}
|
PLAYERINPUT getPadInputDown() {return m_padInputDown;}
|
||||||
class CLayerCollision *getLayerCollision() {return m_layerCollision;}
|
class CLayerCollision *getLayerCollision() {return m_layerCollision;}
|
||||||
|
|
||||||
|
void inSoakUpState();
|
||||||
void takeDamage(DAMAGE_TYPE _damage);
|
void takeDamage(DAMAGE_TYPE _damage);
|
||||||
|
|
||||||
void respawn();
|
void respawn();
|
||||||
|
@ -241,6 +246,8 @@ public:
|
||||||
|
|
||||||
int canDoLookAround();
|
int canDoLookAround();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
|
@ -276,10 +283,26 @@ private:
|
||||||
class CPlayerMode *m_currentPlayerModeClass;
|
class CPlayerMode *m_currentPlayerModeClass;
|
||||||
int m_currentMode;
|
int m_currentMode;
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
HEALTH_TYPE__NORMAL,
|
||||||
|
HEALTH_TYPE__OUT_OF_WATER,
|
||||||
|
} HEALTH_TYPE;
|
||||||
|
|
||||||
|
void setHealthType(HEALTH_TYPE _healthType) {m_healthType=_healthType;}
|
||||||
|
|
||||||
|
private:
|
||||||
int m_lives;
|
int m_lives;
|
||||||
|
HEALTH_TYPE m_healthType;
|
||||||
int m_health;
|
int m_health;
|
||||||
|
int m_healthWaterLevel;
|
||||||
int m_healthReactFrames;
|
int m_healthReactFrames;
|
||||||
|
|
||||||
|
void dieYouPorousFreak();
|
||||||
|
|
||||||
|
|
||||||
void updatePadInput();
|
void updatePadInput();
|
||||||
protected:
|
protected:
|
||||||
virtual PLAYERINPUT readPadInput();
|
virtual PLAYERINPUT readPadInput();
|
||||||
|
@ -370,6 +393,9 @@ public:
|
||||||
POWERUPUI_TEXTX=440,
|
POWERUPUI_TEXTX=440,
|
||||||
POWERUPUI_TEXTY=37,
|
POWERUPUI_TEXTY=37,
|
||||||
POWERUPUI_OT=0,
|
POWERUPUI_OT=0,
|
||||||
|
COLLECTEDITEM_BASEX=110,
|
||||||
|
COLLECTEDITEM_BASEY=60,
|
||||||
|
COLLECTEDITEM_GAP=40,
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -132,6 +132,16 @@ static PlayerMetrics s_playerMetrics=
|
||||||
int CPlayerMode::getPadInputHeld() {return m_player->getPadInputHeld();}
|
int CPlayerMode::getPadInputHeld() {return m_player->getPadInputHeld();}
|
||||||
int CPlayerMode::getPadInputDown() {return m_player->getPadInputDown();}
|
int CPlayerMode::getPadInputDown() {return m_player->getPadInputDown();}
|
||||||
|
|
||||||
|
|
||||||
|
/*----------------------------------------------------------------------
|
||||||
|
Function:
|
||||||
|
Purpose:
|
||||||
|
Params:
|
||||||
|
Returns:
|
||||||
|
---------------------------------------------------------------------- */
|
||||||
|
void CPlayerMode::inSoakUpState() {m_player->inSoakUpState();}
|
||||||
|
|
||||||
|
|
||||||
/*----------------------------------------------------------------------
|
/*----------------------------------------------------------------------
|
||||||
Function:
|
Function:
|
||||||
Purpose:
|
Purpose:
|
||||||
|
|
|
@ -86,6 +86,7 @@ public:
|
||||||
virtual void renderModeUi() {;} // Ui specific to this mode (eg: ammo)
|
virtual void renderModeUi() {;} // Ui specific to this mode (eg: ammo)
|
||||||
virtual int canDoLookAround() {return false;}
|
virtual int canDoLookAround() {return false;}
|
||||||
virtual void springPlayerUp() {;}
|
virtual void springPlayerUp() {;}
|
||||||
|
void inSoakUpState();
|
||||||
|
|
||||||
|
|
||||||
int getPadInputHeld();
|
int getPadInputHeld();
|
||||||
|
|
|
@ -118,6 +118,10 @@ void CPlayerStateSoakUp::think(CPlayerModeBase *_playerMode)
|
||||||
{
|
{
|
||||||
_playerMode->setState(STATE_GETUP);
|
_playerMode->setState(STATE_GETUP);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_playerMode->inSoakUpState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue