Add generic warning when loading other weapon mods.
(Plus small tweaks to the anti-BD script)
This commit is contained in:
parent
cb503a3189
commit
8526c2ffb0
4 changed files with 129 additions and 35 deletions
|
|
@ -1,3 +1,3 @@
|
|||
[default]
|
||||
SWWM_MODVER="\cyDEMOLITIONIST \cw1.3pre r1175 \cu(mar 04 feb 2025 21:24:19 CET)\c-";
|
||||
SWWM_SHORTVER="\cw1.3pre r1175 \cu(2025-02-04 21:24:19)\c-";
|
||||
SWWM_MODVER="\cyDEMOLITIONIST \cw1.3pre r1176 \cu(jue 06 feb 2025 11:51:14 CET)\c-";
|
||||
SWWM_SHORTVER="\cw1.3pre r1176 \cu(2025-02-06 11:51:14)\c-";
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@ Class SWWMHDoomHandler : StaticEventHandler
|
|||
SetRandomSeed[hdscreen](Random[hdscreen]()+consoleplayer+int(MSTimeF()));
|
||||
Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,
|
||||
"\cx┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\c-\n"
|
||||
"\cx┃ \cfOh my, someone appears to be \cgH \ckO \cdR \cvN \chY \ct♥ \cx┃\c-\n"
|
||||
"\cx┃ \cfWell, too bad, this mod isn't compatible with H-Doom \cx┃\c-\n"
|
||||
"\cx┃ \cfOh my, someone appears to be \cgH \ckO \cdR \cvN \chY \ct♥\cx ┃\c-\n"
|
||||
"\cx┃ \cfWell, too bad, this mod isn't compatible with H-Doom\cx ┃\c-\n"
|
||||
"\cx┃ ┃\c-\n"
|
||||
"\cx┃ \cfget bonked \cx┃\c-\n"
|
||||
"\cx┃ \cfget bonked\cx ┃\c-\n"
|
||||
"\cx┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\c-");
|
||||
S_StartSound("compat/warn",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ Class SWWMBrutalHandler : StaticEventHandler
|
|||
ui TextureID boydance[10], boykiss;
|
||||
ui int boyframe, boystate, boyloop1, boyloop2, boytimer, boyseq[12];
|
||||
bool detected;
|
||||
String which, whichshort;
|
||||
|
||||
const BOYTICRATE = 5;
|
||||
|
||||
|
|
@ -32,33 +31,21 @@ Class SWWMBrutalHandler : StaticEventHandler
|
|||
// check for brutal doom
|
||||
foreach ( cls:AllActorClasses )
|
||||
{
|
||||
if ( cls is 'PlayerPawn' )
|
||||
if ( (cls.GetClassName() == 'Doomer') || (cls.GetClassName() == 'BDoomer') || (cls.GetClassName() == 'BEDoomer') )
|
||||
{
|
||||
if ( (cls.GetClassName() == 'Doomer') || (cls.GetClassName() == 'BDoomer') || (cls.GetClassName() == 'BEDoomer') )
|
||||
{
|
||||
detected = true;
|
||||
which = "Brutal Doom";
|
||||
whichshort = "BD";
|
||||
}
|
||||
else if ( (cls.GetClassName() == 'BrutalDoomer') || (cls.GetClassName() == 'PB_PlayerPawn') )
|
||||
{
|
||||
detected = true;
|
||||
which = "Project Brutality";
|
||||
whichshort = "PB";
|
||||
}
|
||||
detected = true;
|
||||
}
|
||||
if ( !detected ) continue;
|
||||
let shnd = SWWMStaticHandler(StaticEventHandler.Find("SWWMStaticHandler"));
|
||||
shnd.isbd = true;
|
||||
shnd.bdname = which;
|
||||
Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,
|
||||
"\cx┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\c-\n"
|
||||
"\cx┃ \cfIf you have "..whichshort.." on your autoload you really shouldn't. \cx┃\c-\n"
|
||||
"\cx┃ \cfIf you manually loaded it with this mod, why would you? \cx┃\c-\n"
|
||||
"\cx┃ \cfThey're not compatible and never will be. \cx┃\c-\n"
|
||||
"\cx┃ \cfThis mod will now shit the bed once you go in-game, \cx┃\c-\n"
|
||||
"\cx┃ \cfand trust me, it's better this way. \cx┃\c-\n"
|
||||
"\cx┃ \cf<See you again, have a nice day> \cx┃\c-\n"
|
||||
"\cx┃ \cfIf you have BD on your autoload you really shouldn't.\cx ┃\c-\n"
|
||||
"\cx┃ \cfIf you manually loaded it with this mod, why would you?\cx ┃\c-\n"
|
||||
"\cx┃ \cfThey're not compatible and never will be.\cx ┃\c-\n"
|
||||
"\cx┃ \cfThis mod will now shit the bed once you go in-game,\cx ┃\c-\n"
|
||||
"\cx┃ \cfand trust me, it's better this way.\cx ┃\c-\n"
|
||||
"\cx┃ \cf<See you again, have a nice day>\cx ┃\c-\n"
|
||||
"\cx┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\c-");
|
||||
S_StartSound("compat/warn",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
||||
break;
|
||||
|
|
@ -86,7 +73,34 @@ Class SWWMBrutalHandler : StaticEventHandler
|
|||
S_StartSound("misc/spawn",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1.,ATTN_NONE);
|
||||
S_StartSound("misc/spawn",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1.,ATTN_NONE);
|
||||
S_ChangeMusic("",force:true);
|
||||
Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\n\n\cjYou like playing \cg"..which.."\cj don't you?\c-\n\n\n");
|
||||
Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,
|
||||
"\cj⠀⠀⠀⠀⢀⡾⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡴⠛⣆⠀⠀\n"
|
||||
"\cj⠀⠀⠀⢠⡟⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⠉⠀⠀⠸⣆⠀\c-\n"
|
||||
"\cj⠀⠀⢠⡟⠀⠀⠀⠀⠀⠈⢳⣄⠀⠀⠀⢠⡤⣤⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⠁⠀⠀⠀⠀⠀⢻⡀\c-\n"
|
||||
"\cj⠀⠀⣾⠀⠀⠀⠀⠀⠀⠀⠀⠙⢷⡀⠀⢸⡇⠀⠀⠉⠙⠳⢦⣄⠀⠀⠀⠀⣠⠞⠁⠀⠀⠀⠀⠀⠀⠀⠘⣇\c-\n"
|
||||
"\cj⠀⢰⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣦⡀⠻⣄⠀⠀⠀⠀⠀⠈⠻⣦⣀⡾⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿\c-\n"
|
||||
"\cj⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣨⡷⠞⠛⠛⠀⠀⠀⠀⠀⠀⠈⠻⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿\c-\n"
|
||||
"\cj⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢴⣯⣥⣤⣤⣤⣤⠴⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿\c-\n"
|
||||
"\cj⠀⠸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡇\c-\n"
|
||||
"\cj⠀⠀⢿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡾⠀\c-\n"
|
||||
"\cj⠀⠀⠈⢧⠀⠀⠀⢤⣤⡤⠤⢤⣤⣤⣤⣤⡄⠀⠀⠀⠀⠀⠀⣶⣶⣿⣿⣟⠛⠛⢿⡛⠀⠀⠀⠀⢀⡾⠁⠀\c-\n"
|
||||
"\cj⠀⠀⠀⠈⢷⡀⠀⠀⡿⠀⠀⢸⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⠀⠀⠘⡇⠀⠀⠀⣠⣟⣁⣀⠀\c-\n"
|
||||
"\cj⢻⡟⠓⠶⠶⠿⠀⢸⡇⠀⠀⠘⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⡏⠀⠀⠀⣿⠀⠀⠈⠉⠉⣩⡟⠀\c-\n"
|
||||
"\cj⠀⠻⣄⡀⠀⠀⠀⠘⣧⠀⠀⠀⢻⣿⣿⡟⠀⢀⣀⡀⠀⠀⠀⠈⠿⠿⠟⠁⠀⠀⠀⠏⠀⠀⠀⣀⡴⠋⠀⠀\c-\n"
|
||||
"\cj⠀⠀⠀⢨⡏⠠⠶⣿⣤⠶⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⢿⣷⠞⠀⠀⠀⠻⣦⠀⠀⠀\c-\n"
|
||||
"\cj⠀⠀⢠⡟⠀⠀⠀⠉⠀⠀⠀⠀⠀⠀⠀⠰⣤⣤⡴⠟⠶⠶⠶⠛⠀⠀⠀⠀⠀⠈⠁⠀⠀⠀⠀⠀⠈⢷⡀⠀\c-\n"
|
||||
"\cj⠀⠀⠿⢤⡤⠴⠶⠶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⡴⠞⠛⠓⠒⠛⠛⠋⠁⠀\c-\n"
|
||||
"\cj⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⣶⠶⣤⣤⣤⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠛⣿⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\c-\n"
|
||||
"\cj⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\c-\n"
|
||||
"\cj⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\c-\n"
|
||||
"\cj⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠼⠷⢶⡶⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀\c-\n"
|
||||
"\cj⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡾⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀\c-\n"
|
||||
"\cj⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⡆⠀⠀⠀⠀⠀⠀⠀⠀\c-\n"
|
||||
"\cj⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣇⠀⠀⠀⠀⠀⠀⠀⠀\c-\n"
|
||||
"\cj⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀\c-\n"
|
||||
"\cj⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⠀⠀⠀⠀⠀⠀⠀⠀\c-\n"
|
||||
"\cj⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀\c-\n"
|
||||
"\cjYou like playing \cgBrutal Doom\cj, don't you?\c-");
|
||||
}
|
||||
else if ( timer == 140 )
|
||||
{
|
||||
|
|
@ -196,7 +210,7 @@ Class SWWMBrutalHandler : StaticEventHandler
|
|||
if ( !detected ) return;
|
||||
Screen.Dim("White",clamp((timer+e.fractic)/70.,0.,1.),0,0,Screen.GetWidth(),Screen.GetHeight());
|
||||
if ( timer < 70 ) return;
|
||||
String str = "You like playing \cg"..which.."\c- don't you?";
|
||||
String str = "You like playing \cgBrutal Doom\c-, don't you?";
|
||||
int boxh = fnt?((fnt.GetHeight()+16)*CleanYFac):0;
|
||||
double scl = (Screen.GetHeight()-boxh)/2048.;
|
||||
if ( boykiss.IsValid() ) Screen.DrawTexture(boykiss,false,Screen.GetWidth()/2,(Screen.GetHeight()-boxh)/2,DTA_CenterOffset,true,DTA_ScaleX,scl,DTA_ScaleY,scl);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ Class SWWMStaticHandler : StaticEventHandler
|
|||
ui int timer, msgpick;
|
||||
// broccoli doccoli
|
||||
bool isbd;
|
||||
String bdname;
|
||||
// versioning
|
||||
bool tainted;
|
||||
String taintver;
|
||||
|
|
@ -129,6 +128,74 @@ Class SWWMStaticHandler : StaticEventHandler
|
|||
SaveAchievements();
|
||||
}
|
||||
|
||||
private bool CheckOtherMods()
|
||||
{
|
||||
// this should cover as much ground as possible, just checking for any custom player classes that are not
|
||||
// part of the vanilla set (excluding ones such as, for example, morph targets)
|
||||
foreach ( cls:AllActorClasses )
|
||||
{
|
||||
if ( !(cls is 'PlayerPawn') ) continue;
|
||||
if ( cls is 'PlayerChunk' ) continue;
|
||||
switch ( cls.GetClassName() )
|
||||
{
|
||||
case 'PlayerPawn':
|
||||
case 'DoomPlayer':
|
||||
case 'ChexPlayer':
|
||||
case 'HereticPlayer':
|
||||
case 'ChickenPlayer':
|
||||
case 'FighterPlayer':
|
||||
case 'ClericPlayer':
|
||||
case 'MagePlayer':
|
||||
case 'PigPlayer':
|
||||
case 'StrifePlayer':
|
||||
case 'Demolitionist':
|
||||
case 'SWWMVoodooDoll':
|
||||
// do nothing
|
||||
break;
|
||||
default:
|
||||
let def = GetDefaultByType((Class<PlayerPawn>)(cls));
|
||||
// if this class has a morph weapon defined, skip it
|
||||
if ( def.MorphWeapon )
|
||||
{
|
||||
Console.Printf("skip me: %s",cls.GetClassName());
|
||||
break;
|
||||
}
|
||||
// we have to check if there are any discrepancies between this class's start item list and
|
||||
// its parents
|
||||
let pdef = GetDefaultByType((Class<PlayerPawn>)(cls.GetParentClass()));
|
||||
let di = def.GetDropItems();
|
||||
let pdi = pdef.GetDropItems();
|
||||
// no items, just skip
|
||||
if ( !di )
|
||||
{
|
||||
Console.Printf("skip me: %s",cls.GetClassName());
|
||||
break;
|
||||
}
|
||||
do
|
||||
{
|
||||
// list sizes don't match
|
||||
if ( (di && !pdi) || (!di && pdi) )
|
||||
{
|
||||
Console.Printf("match me: %s",cls.GetClassName());
|
||||
return true;
|
||||
}
|
||||
// mismatch in item names
|
||||
if ( di.name != pdi.name )
|
||||
{
|
||||
Console.Printf("match me: %s",cls.GetClassName());
|
||||
return true;
|
||||
}
|
||||
di = di.next;
|
||||
pdi = pdi.next;
|
||||
}
|
||||
while ( di || pdi );
|
||||
Console.Printf("skip me: %s",cls.GetClassName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
override void OnRegister()
|
||||
{
|
||||
// fix voice type cvar
|
||||
|
|
@ -174,15 +241,28 @@ Class SWWMStaticHandler : StaticEventHandler
|
|||
Console.Printf("\cgWARNING:\c- Multiplayer is no longer supported, desyncs and other issues WILL happen. You are on your own.");
|
||||
S_StartSound("compat/warn",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
||||
}
|
||||
bool checked = CheckOtherMods();
|
||||
if ( checked )
|
||||
{
|
||||
// warn for combining with other weapon mods
|
||||
Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,
|
||||
"\cx┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\c-\n"
|
||||
"\cx┃ \cr[\cgWARNING\cr]\cx ┃\c-\n"
|
||||
"\cx┃ \cjYou appear to be loading this alongside another weapon mod.\cx ┃\c-\n"
|
||||
"\cx┃ \cjIssues are \cfVERY LIKELY\cj to happen.\cx ┃\c-\n"
|
||||
"\cx┃ \cr[\cgYOU ARE ON YOUR OWN\cr]\cx ┃\c-\n"
|
||||
"\cx┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\c-");
|
||||
S_StartSound("compat/warn",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
||||
}
|
||||
// warning for unsupported
|
||||
if ( Wads.FindLumpFullName("swwmgamesupported",0,true) != -1 ) return;
|
||||
Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,
|
||||
"\cx┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\c-\n"
|
||||
"\cx┃ \cr[\cgWARNING\cr] \cx┃\c-\n"
|
||||
"\cx┃ \chSWWM \czGZ \cjis \cfNOT\cj compatible with the loaded IWAD. \cx┃\c-\n"
|
||||
"\cx┃ \cjOnly \cfDoom\cj, \cfHeretic\cj and \cfHexen\cj are supported. \cx┃\c-\n"
|
||||
"\cx┃ \cjIssues \cfCAN\cj and \cfWILL\cj happen. \cx┃\c-\n"
|
||||
"\cx┃ \cr[\cgYOU ARE ON YOUR OWN\cr] \cx┃\c-\n"
|
||||
"\cx┃ \cr[\cgWARNING\cr]\cx ┃\c-\n"
|
||||
"\cx┃ \chSWWM \czGZ \cjis \cfNOT\cj compatible with the loaded IWAD.\cx ┃\c-\n"
|
||||
"\cx┃ \cjOnly \cfDoom\cj, \cfHeretic\cj and \cfHexen\cj are supported.\cx ┃\c-\n"
|
||||
"\cx┃ \cjIssues \cfCAN\cj and \cfWILL\cj happen.\cx ┃\c-\n"
|
||||
"\cx┃ \cr[\cgYOU ARE ON YOUR OWN\cr]\cx ┃\c-\n"
|
||||
"\cx┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\c-");
|
||||
S_StartSound("compat/warn",CHAN_YABLEWIT,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
||||
}
|
||||
|
|
@ -695,7 +775,7 @@ Class SWWMStaticHandler : StaticEventHandler
|
|||
}
|
||||
else if ( timer == 140 )
|
||||
{
|
||||
if ( isbd ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cfYou shouldn't have tried running this with "..bdname..".\c-");
|
||||
if ( isbd ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cfYou shouldn't have tried running this with Brutal Doom.\c-");
|
||||
else Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cfYou should probably screenshot this error and show it to Marisa.\c-");
|
||||
Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cfLoaded Version:\n \cj%s\c-",StringTable.Localize("$SWWM_SHORTVER"));
|
||||
if ( tainted ) Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,"\cfSavegame Version:\n \cj%s\c-",taintver);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue