mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-27 22:42:57 +00:00
- Fixed: Parsing sector special bit masks must be done backwards so that later
definitions take precedence. - Added base translation tables for UDMF compatibility maps which only should handle the native line and sector types of each game. - Turned the inactive SILENT_INSTANT_FLOORS define into a compatibility option so that it can be (un)set in a map definition and the menu. SVN r966 (trunk)
This commit is contained in:
parent
b2176a4a33
commit
0869b51928
17 changed files with 196 additions and 103 deletions
|
@ -1,4 +1,10 @@
|
|||
May 12, 2008 (Changes by Graf Zahl)
|
||||
- Fixed: Parsing sector special bit masks must be done backwards so that later
|
||||
definitions take precedence.
|
||||
- Added base translation tables for UDMF compatibility maps which only should
|
||||
handle the native line and sector types of each game.
|
||||
- Turned the inactive SILENT_INSTANT_FLOORS define into a compatibility option
|
||||
so that it can be (un)set in a map definition and the menu.
|
||||
- Fixed: SPAC_AnyCross didn't work.
|
||||
- Fixed: Pushable doors must also check for SPAC_MPush.
|
||||
- Fixed: P_LoadThings2 did not adjust the byte order for the thingid field.
|
||||
|
|
|
@ -452,6 +452,7 @@ CVAR (Flag, compat_trace, compatflags, COMPATF_TRACE);
|
|||
CVAR (Flag, compat_dropoff, compatflags, COMPATF_DROPOFF);
|
||||
CVAR (Flag, compat_boomscroll, compatflags, COMPATF_BOOMSCROLL);
|
||||
CVAR (Flag, compat_invisibility,compatflags, COMPATF_INVISIBILITY);
|
||||
CVAR (Flag, compat_silentinstantfloors,compatflags, COMPATF_SILENT_INSTANT_FLOORS);
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
|
|
|
@ -88,7 +88,7 @@ typedef struct
|
|||
} mapsidedef_t;
|
||||
|
||||
// A LineDef, as used for editing, and as input to the BSP builder.
|
||||
typedef struct
|
||||
struct maplinedef_t
|
||||
{
|
||||
WORD v1;
|
||||
WORD v2;
|
||||
|
@ -97,10 +97,10 @@ typedef struct
|
|||
short tag;
|
||||
WORD sidenum[2]; // sidenum[1] will be -1 if one sided
|
||||
|
||||
} maplinedef_t;
|
||||
} ;
|
||||
|
||||
// [RH] Hexen-compatible LineDef.
|
||||
typedef struct
|
||||
struct maplinedef2_t
|
||||
{
|
||||
WORD v1;
|
||||
WORD v2;
|
||||
|
@ -108,7 +108,7 @@ typedef struct
|
|||
BYTE special;
|
||||
BYTE args[5];
|
||||
WORD sidenum[2];
|
||||
} maplinedef2_t;
|
||||
} ;
|
||||
|
||||
|
||||
//
|
||||
|
|
|
@ -282,6 +282,7 @@ enum
|
|||
COMPATF_DROPOFF = 1 << 14, // Monsters cannot move when hanging over a dropoff
|
||||
COMPATF_BOOMSCROLL = 1 << 15, // Scrolling sectors are additive like in Boom
|
||||
COMPATF_INVISIBILITY = 1 << 16, // Monsters can see semi-invisible players
|
||||
COMPATF_SILENT_INSTANT_FLOORS = 1<<17, // Instantly moving floors are not silent
|
||||
};
|
||||
|
||||
// phares 3/20/98:
|
||||
|
|
|
@ -295,6 +295,7 @@ static const char *MapInfoMapLevel[] =
|
|||
"compat_dropoff",
|
||||
"compat_boomscroll",
|
||||
"compat_invisibility",
|
||||
"compat_silent_instant_floors",
|
||||
"bordertexture",
|
||||
"f1", // [RC] F1 help
|
||||
"noinfighting",
|
||||
|
@ -445,6 +446,7 @@ MapHandlers[] =
|
|||
{ MITYPE_COMPATFLAG, COMPATF_DROPOFF},
|
||||
{ MITYPE_COMPATFLAG, COMPATF_BOOMSCROLL},
|
||||
{ MITYPE_COMPATFLAG, COMPATF_INVISIBILITY},
|
||||
{ MITYPE_COMPATFLAG, COMPATF_SILENT_INSTANT_FLOORS},
|
||||
{ MITYPE_LUMPNAME, lioffset(bordertexture), 0 },
|
||||
{ MITYPE_LUMPNAME, lioffset(f1), 0, },
|
||||
{ MITYPE_SCFLAGS, LEVEL_NOINFIGHTING, ~LEVEL_TOTALINFIGHTING },
|
||||
|
|
|
@ -1108,6 +1108,7 @@ static menuitem_t CompatibilityItems[] = {
|
|||
{ bitflag, "Monsters get stuck over dropoffs", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_DROPOFF} },
|
||||
{ bitflag, "Monsters see invisible players", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_INVISIBILITY} },
|
||||
{ bitflag, "Boom scrollers are additive", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_BOOMSCROLL} },
|
||||
{ bitflag, "Inst. moving floors are not silent", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_SILENT_INSTANT_FLOORS} },
|
||||
|
||||
{ discrete, "Interpolate monster movement", {&nomonsterinterpolation}, {2.0}, {0.0}, {0.0}, {NoYes} },
|
||||
};
|
||||
|
|
|
@ -533,8 +533,6 @@ manual_floor:
|
|||
}
|
||||
|
||||
// Do not interpolate instant movement floors.
|
||||
// Note for ZDoomGL: Check to make sure that you update the sector
|
||||
// after the floor moves, because it hasn't actually moved yet.
|
||||
bool silent = false;
|
||||
|
||||
if ((floor->m_Direction>0 && floor->m_FloorDestDist>sec->floorplane.d) || // moving up but going down
|
||||
|
@ -542,18 +540,15 @@ manual_floor:
|
|||
(floor->m_Speed >= abs(sec->floorplane.d - floor->m_FloorDestDist))) // moving in one step
|
||||
{
|
||||
stopinterpolation (INTERP_SectorFloor, sec);
|
||||
|
||||
// [Graf Zahl]
|
||||
// Don't make sounds for instant movement hacks but make an exception for
|
||||
// switches that activate their own back side.
|
||||
// I'll leave the decision about this to somebody else. In many maps
|
||||
// it helps but there are some where this omits sounds that should be there.
|
||||
#ifdef SILENT_INSTANT_FLOORS
|
||||
if (floortype != DFloor::floorRaiseInstant && floortype != DFloor::floorLowerInstant)
|
||||
if (!(i_compatflags & COMPATF_SILENT_INSTANT_FLOORS))
|
||||
{
|
||||
if (!line || GET_SPAC(line->flags) != SPAC_USE || line->backsector!=sec)
|
||||
if (!line || !(line->activation & (SPAC_Use|SPAC_Push)) || line->backsector!=sec)
|
||||
silent = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (!silent) floor->StartFloorSound ();
|
||||
|
||||
|
|
|
@ -67,10 +67,7 @@ void P_SetSlopes ();
|
|||
extern AActor *P_SpawnMapThing (FMapThing *mthing, int position);
|
||||
extern bool P_LoadBuildMap (BYTE *mapdata, size_t len, FMapThing **things, int *numthings);
|
||||
|
||||
extern void P_LoadTranslator(const char *lump);
|
||||
extern void P_TranslateLineDef (line_t *ld, maplinedef_t *mld);
|
||||
extern void P_TranslateTeleportThings (void);
|
||||
extern int P_TranslateSectorSpecial (int);
|
||||
|
||||
void P_ParseTextMap(MapData *map);
|
||||
void P_SpawnTextThings(int position);
|
||||
|
|
|
@ -99,4 +99,11 @@ void P_FreeExtraLevelData();
|
|||
// Called by startup code.
|
||||
void P_Init (void);
|
||||
|
||||
struct line_t;
|
||||
struct maplinedef_t;
|
||||
|
||||
void P_LoadTranslator(const char *lumpname);
|
||||
void P_TranslateLineDef (line_t *ld, maplinedef_t *mld);
|
||||
int P_TranslateSectorSpecial (int);
|
||||
|
||||
#endif
|
||||
|
|
193
src/p_udmf.cpp
193
src/p_udmf.cpp
|
@ -41,8 +41,6 @@
|
|||
#include "templates.h"
|
||||
#include "i_system.h"
|
||||
|
||||
extern void P_TranslateLineDef (line_t *ld, maplinedef_t *mld);
|
||||
extern int P_TranslateSectorSpecial (int);
|
||||
void P_ProcessSideTextures(bool checktranmap, side_t *sd, sector_t *sec, mapsidedef_t *msd, int special, int tag, short *alpha);
|
||||
void P_AdjustLine (line_t *ld);
|
||||
void P_FinishLoadingLineDef(line_t *ld, int alpha);
|
||||
|
@ -193,16 +191,25 @@ struct UDMFParser
|
|||
}
|
||||
if (isTranslated)
|
||||
{
|
||||
// NOTE: Handling of this is undefined in the UDMF spec yet!
|
||||
maplinedef_t mld;
|
||||
line_t ld;
|
||||
if (isExtended)
|
||||
{
|
||||
// NOTE: Handling of this is undefined in the UDMF spec
|
||||
// so it is only done for namespace ZDoomTranslated
|
||||
maplinedef_t mld;
|
||||
line_t ld;
|
||||
|
||||
mld.flags = 0;
|
||||
mld.special = th->special;
|
||||
mld.tag = th->args[0];
|
||||
P_TranslateLineDef(&ld, &mld);
|
||||
th->special = ld.special;
|
||||
memcpy(th->args, ld.args, sizeof (ld.args));
|
||||
mld.flags = 0;
|
||||
mld.special = th->special;
|
||||
mld.tag = th->args[0];
|
||||
P_TranslateLineDef(&ld, &mld);
|
||||
th->special = ld.special;
|
||||
memcpy(th->args, ld.args, sizeof (ld.args));
|
||||
}
|
||||
else // NULL the special
|
||||
{
|
||||
th->special = 0;
|
||||
memset(th->args, 0, sizeof (th->args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,6 +233,8 @@ struct UDMFParser
|
|||
sc.MustGetString();
|
||||
FString value = sc.String;
|
||||
sc.MustGetStringName(";");
|
||||
|
||||
// This switch contains all keys of the UDMF base spec
|
||||
switch(key)
|
||||
{
|
||||
case NAME_V1:
|
||||
|
@ -273,55 +282,65 @@ struct UDMFParser
|
|||
Flag(ld->flags, ML_MAPPED, value); break;
|
||||
case NAME_Monsteractivate:
|
||||
Flag(ld->flags, ML_MONSTERSCANACTIVATE, value); break;
|
||||
case NAME_Blockplayers:
|
||||
if (isExtended) Flag(ld->flags, ML_BLOCK_PLAYERS, value); break;
|
||||
case NAME_Blockeverything:
|
||||
if (isExtended) Flag(ld->flags, ML_BLOCKEVERYTHING, value); break;
|
||||
case NAME_Zoneboundary:
|
||||
if (isExtended) Flag(ld->flags, ML_ZONEBOUNDARY, value); break;
|
||||
case NAME_Jumpover:
|
||||
if (isExtended || namespc == NAME_Strife) Flag(ld->flags, ML_RAILING, value); break;
|
||||
Flag(ld->flags, ML_RAILING, value); break;
|
||||
case NAME_Blockfloating:
|
||||
if (isExtended || namespc == NAME_Strife) Flag(ld->flags, ML_BLOCK_FLOATERS, value); break;
|
||||
case NAME_Clipmidtex:
|
||||
if (isExtended) Flag(ld->flags, ML_CLIP_MIDTEX, value); break;
|
||||
case NAME_Wrapmidtex:
|
||||
if (isExtended) Flag(ld->flags, ML_WRAP_MIDTEX, value); break;
|
||||
case NAME_Midtex3d:
|
||||
if (isExtended) Flag(ld->flags, ML_3DMIDTEX, value); break;
|
||||
case NAME_Checkswitchrange:
|
||||
if (isExtended) Flag(ld->flags, ML_CHECKSWITCHRANGE, value); break;
|
||||
case NAME_Firstsideonly:
|
||||
if (isExtended) Flag(ld->flags, ML_FIRSTSIDEONLY, value); break;
|
||||
Flag(ld->flags, ML_BLOCK_FLOATERS, value); break;
|
||||
case NAME_Transparent:
|
||||
ld->Alpha = !value.CompareNoCase("true")? FRACUNIT*3/4 : FRACUNIT; break;
|
||||
case NAME_Passuse:
|
||||
passuse = !value.CompareNoCase("true");
|
||||
case NAME_Playercross:
|
||||
if (isExtended) Flag(ld->activation, SPAC_Cross, value); break;
|
||||
case NAME_Playeruse:
|
||||
if (isExtended) Flag(ld->activation, SPAC_Use, value); break;
|
||||
case NAME_Monstercross:
|
||||
if (isExtended) Flag(ld->activation, SPAC_MCross, value); break;
|
||||
case NAME_Impact:
|
||||
if (isExtended) Flag(ld->activation, SPAC_Impact, value); break;
|
||||
case NAME_Playerpush:
|
||||
if (isExtended) Flag(ld->activation, SPAC_Push, value); break;
|
||||
case NAME_Missilecross:
|
||||
if (isExtended) Flag(ld->activation, SPAC_PCross, value); break;
|
||||
case NAME_Monsteruse:
|
||||
if (isExtended) Flag(ld->activation, SPAC_MUse, value); break;
|
||||
case NAME_Monsterpush:
|
||||
if (isExtended) Flag(ld->activation, SPAC_MPush, value); break;
|
||||
passuse = !value.CompareNoCase("true"); break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// This switch contains all keys of the UDMF base spec which only apply to Hexen format specials
|
||||
if (!isTranslated) switch (key)
|
||||
{
|
||||
case NAME_Playercross:
|
||||
Flag(ld->activation, SPAC_Cross, value); break;
|
||||
case NAME_Playeruse:
|
||||
Flag(ld->activation, SPAC_Use, value); break;
|
||||
case NAME_Monstercross:
|
||||
Flag(ld->activation, SPAC_MCross, value); break;
|
||||
case NAME_Impact:
|
||||
Flag(ld->activation, SPAC_Impact, value); break;
|
||||
case NAME_Playerpush:
|
||||
Flag(ld->activation, SPAC_Push, value); break;
|
||||
case NAME_Missilecross:
|
||||
Flag(ld->activation, SPAC_PCross, value); break;
|
||||
case NAME_Monsteruse:
|
||||
Flag(ld->activation, SPAC_MUse, value); break;
|
||||
case NAME_Monsterpush:
|
||||
Flag(ld->activation, SPAC_MPush, value); break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// This switch contains all keys which are ZDoom specific
|
||||
if (isExtended) switch(key)
|
||||
{
|
||||
case NAME_Blockplayers:
|
||||
Flag(ld->flags, ML_BLOCK_PLAYERS, value); break;
|
||||
case NAME_Blockeverything:
|
||||
Flag(ld->flags, ML_BLOCKEVERYTHING, value); break;
|
||||
case NAME_Zoneboundary:
|
||||
Flag(ld->flags, ML_ZONEBOUNDARY, value); break;
|
||||
case NAME_Clipmidtex:
|
||||
Flag(ld->flags, ML_CLIP_MIDTEX, value); break;
|
||||
case NAME_Wrapmidtex:
|
||||
Flag(ld->flags, ML_WRAP_MIDTEX, value); break;
|
||||
case NAME_Midtex3d:
|
||||
Flag(ld->flags, ML_3DMIDTEX, value); break;
|
||||
case NAME_Checkswitchrange:
|
||||
Flag(ld->flags, ML_CHECKSWITCHRANGE, value); break;
|
||||
case NAME_Firstsideonly:
|
||||
Flag(ld->flags, ML_FIRSTSIDEONLY, value); break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isTranslated)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
int saved = ld->flags;
|
||||
|
||||
|
@ -589,42 +608,50 @@ struct UDMFParser
|
|||
map->Read(ML_TEXTMAP, buffer);
|
||||
sc.OpenMem(Wads.GetLumpFullName(map->lumpnum), buffer, map->Size(ML_TEXTMAP));
|
||||
sc.SetCMode(true);
|
||||
if (sc.CheckString("namespace"))
|
||||
{
|
||||
sc.MustGetStringName("=");
|
||||
sc.MustGetString();
|
||||
namespc = sc.String;
|
||||
if (namespc == NAME_ZDoom)
|
||||
{
|
||||
isTranslated = false;
|
||||
isExtended = true;
|
||||
}
|
||||
else if (namespc == NAME_Hexen)
|
||||
{
|
||||
isTranslated = false;
|
||||
}
|
||||
else if (namespc == NAME_ZDoomTranslated)
|
||||
{
|
||||
isExtended = true;
|
||||
}
|
||||
else if (namespc == NAME_Doom)
|
||||
{
|
||||
P_LoadTranslator("xlat/doom_base.txt");
|
||||
}
|
||||
else if (namespc == NAME_Heretic)
|
||||
{
|
||||
P_LoadTranslator("xlat/heretic_base.txt");
|
||||
}
|
||||
else if (namespc == NAME_Strife)
|
||||
{
|
||||
P_LoadTranslator("xlat/strife_base.txt");
|
||||
}
|
||||
else
|
||||
{
|
||||
Printf("Unknown namespace %s\n", sc.String);
|
||||
}
|
||||
sc.MustGetStringName(";");
|
||||
}
|
||||
else
|
||||
{
|
||||
Printf("Map does not define a namespace.\n");
|
||||
}
|
||||
|
||||
while (sc.GetString())
|
||||
{
|
||||
if (sc.Compare("namespace"))
|
||||
{
|
||||
sc.MustGetStringName("=");
|
||||
sc.MustGetString();
|
||||
namespc = sc.String;
|
||||
if (namespc == NAME_ZDoom)
|
||||
{
|
||||
isTranslated = false;
|
||||
isExtended = true;
|
||||
}
|
||||
else if (namespc == NAME_Hexen)
|
||||
{
|
||||
isTranslated = false;
|
||||
}
|
||||
else if (namespc == NAME_ZDoomTranslated)
|
||||
{
|
||||
isExtended = true;
|
||||
}
|
||||
else if (namespc == NAME_Doom)
|
||||
{
|
||||
}
|
||||
else if (namespc == NAME_Heretic)
|
||||
{
|
||||
}
|
||||
else if (namespc == NAME_Strife)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
sc.ScriptError("Unknown namespace %s", sc.String);
|
||||
}
|
||||
sc.MustGetStringName(";");
|
||||
}
|
||||
else if (sc.Compare("thing"))
|
||||
if (sc.Compare("thing"))
|
||||
{
|
||||
FMapThing th;
|
||||
ParseThing(&th);
|
||||
|
|
|
@ -337,7 +337,7 @@ int P_TranslateSectorSpecial (int special)
|
|||
{
|
||||
int mask = 0;
|
||||
|
||||
for(unsigned i = 0; i < SectorMasks.Size(); i++)
|
||||
for(int i = SectorMasks.Size()-1; i>=0; i--)
|
||||
{
|
||||
int newmask = special & SectorMasks[i].mask;
|
||||
if (newmask)
|
||||
|
|
|
@ -111,20 +111,20 @@ struct XlatParseContext : public FParseContext
|
|||
//==========================================================================
|
||||
bool FindToken (char *tok, int *type)
|
||||
{
|
||||
static const char tokens[][10] =
|
||||
static const char *tokens[] =
|
||||
{
|
||||
"arg2", "arg3", "arg4", "arg5", "bitmask", "clear",
|
||||
"define", "enum", "flags", "include", "lineid",
|
||||
"nobitmask", "sector", "tag"
|
||||
"nobitmask", "sector", "tag", "maxlinespecial"
|
||||
};
|
||||
static const short types[] =
|
||||
{
|
||||
XLAT_ARG2, XLAT_ARG3, XLAT_ARG4, XLAT_ARG5, XLAT_BITMASK, XLAT_CLEAR,
|
||||
XLAT_DEFINE, XLAT_ENUM, XLAT_FLAGS, XLAT_INCLUDE, XLAT_TAG,
|
||||
XLAT_NOBITMASK, XLAT_SECTOR, XLAT_TAG,
|
||||
XLAT_NOBITMASK, XLAT_SECTOR, XLAT_TAG, XLAT_MAXLINESPECIAL
|
||||
};
|
||||
|
||||
int min = 0, max = sizeof(tokens)/sizeof(tokens[0]) - 1;
|
||||
int min = 0, max = countof(tokens) - 1;
|
||||
|
||||
while (min <= max)
|
||||
{
|
||||
|
|
|
@ -18,6 +18,7 @@ external_declaration ::= linetype_declaration.
|
|||
external_declaration ::= boom_declaration.
|
||||
external_declaration ::= sector_declaration.
|
||||
external_declaration ::= sector_bitmask.
|
||||
external_declaration ::= maxlinespecial_def.
|
||||
external_declaration ::= NOP.
|
||||
|
||||
|
||||
|
@ -452,6 +453,19 @@ list_val(A) ::= exp(B) COLON exp(C).
|
|||
A.value = C;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// max line special
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
maxlinespecial_def ::= MAXLINESPECIAL EQUALS exp(mx) SEMICOLON.
|
||||
{
|
||||
// Just kill all specials higher than the max.
|
||||
// If the translator wants to redefine some later, just let it.s
|
||||
SimpleLineTranslations.Resize(mx+1);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// sector types
|
||||
|
|
17
wadsrc/xlat/doom_base.txt
Normal file
17
wadsrc/xlat/doom_base.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
include "xlat/doom.txt"
|
||||
|
||||
maxlinespecial = 272;
|
||||
|
||||
|
||||
sector bitmask 0xffe0 clear;
|
||||
|
||||
sector 15 = 0;
|
||||
sector 17 = 0;
|
||||
sector 18 = 0;
|
||||
sector 19 = 0;
|
||||
sector 20 = 0;
|
||||
sector 21 = 0;
|
||||
sector 22 = 0;
|
||||
sector 23 = 0;
|
||||
sector 24 = 0;
|
||||
|
8
wadsrc/xlat/heretic_base.txt
Normal file
8
wadsrc/xlat/heretic_base.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
include "xlat/heretic.txt"
|
||||
|
||||
maxlinespecial = 107;
|
||||
|
||||
|
||||
sector bitmask 0xffc0 clear;
|
||||
|
||||
sector 17 = 0;
|
14
wadsrc/xlat/strife_base.txt
Normal file
14
wadsrc/xlat/strife_base.txt
Normal file
|
@ -0,0 +1,14 @@
|
|||
include "xlat/strife.txt"
|
||||
|
||||
maxlinespecial = 234;
|
||||
|
||||
sector bitmask 0xffe0 clear;
|
||||
|
||||
sector 19 = 0;
|
||||
sector 20 = 0;
|
||||
sector 21 = 0;
|
||||
sector 22 = 0;
|
||||
sector 23 = 0;
|
||||
sector 24 = 0;
|
||||
|
||||
|
|
@ -71,6 +71,9 @@ xlat/heretic.txt xlat/heretic.txt
|
|||
xlat/strife.txt xlat/strife.txt
|
||||
xlat/base.txt xlat/base.txt
|
||||
xlat/defines.i xlat/defines.i
|
||||
xlat/doom_base.txt xlat/doom_base.txt
|
||||
xlat/heretic_base.txt xlat/heretic_base.txt
|
||||
xlat/strife_base.txt xlat/strife_base.txt
|
||||
|
||||
|
||||
========
|
||||
|
|
Loading…
Reference in a new issue