mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-11 07:12:02 +00:00
Merge branch 'master' of https://github.com/rheit/zdoom
This commit is contained in:
commit
8362a4516f
26 changed files with 610 additions and 197 deletions
|
@ -12,9 +12,6 @@ if( ZD_CMAKE_COMPILER_IS_GNUC_COMPATIBLE )
|
|||
set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra" )
|
||||
endif()
|
||||
|
||||
# Enable fast flag for gdtoa
|
||||
set( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${ZD_FASTMATH_FLAG}" )
|
||||
|
||||
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
|
||||
add_definitions( -DINFNAN_CHECK -DMULTIPLE_THREADS )
|
||||
|
||||
|
|
|
@ -742,6 +742,7 @@ public:
|
|||
bool IsHostile (AActor *other);
|
||||
|
||||
inline bool IsNoClip2() const;
|
||||
void CheckPortalTransition(bool islinked);
|
||||
|
||||
// What species am I?
|
||||
virtual FName GetSpecies();
|
||||
|
|
|
@ -146,6 +146,7 @@ static FCompatOption Options[] =
|
|||
{ "floormove", COMPATF2_FLOORMOVE, SLOT_COMPAT2 },
|
||||
{ "soundcutoff", COMPATF2_SOUNDCUTOFF, SLOT_COMPAT2 },
|
||||
{ "pointonline", COMPATF2_POINTONLINE, SLOT_COMPAT2 },
|
||||
{ "multiexit", COMPATF2_MULTIEXIT, SLOT_COMPAT2 },
|
||||
|
||||
{ NULL, 0, 0 }
|
||||
};
|
||||
|
|
|
@ -572,7 +572,8 @@ CUSTOM_CVAR(Int, compatmode, 0, CVAR_ARCHIVE|CVAR_NOINITCALL)
|
|||
break;
|
||||
|
||||
case 4: // Old ZDoom compat mode
|
||||
v = COMPATF_SOUNDTARGET|COMPATF_LIGHT;
|
||||
v = COMPATF_SOUNDTARGET | COMPATF_LIGHT;
|
||||
w = COMPATF2_MULTIEXIT;
|
||||
break;
|
||||
|
||||
case 5: // MBF compat mode
|
||||
|
@ -627,6 +628,7 @@ CVAR (Flag, compat_badangles, compatflags2, COMPATF2_BADANGLES);
|
|||
CVAR (Flag, compat_floormove, compatflags2, COMPATF2_FLOORMOVE);
|
||||
CVAR (Flag, compat_soundcutoff, compatflags2, COMPATF2_SOUNDCUTOFF);
|
||||
CVAR (Flag, compat_pointonline, compatflags2, COMPATF2_POINTONLINE);
|
||||
CVAR (Flag, compat_multiexit, compatflags2, COMPATF2_MULTIEXIT);
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
|
|
|
@ -341,6 +341,7 @@ enum
|
|||
COMPATF2_FLOORMOVE = 1 << 1, // Use the same floor motion behavior as Doom.
|
||||
COMPATF2_SOUNDCUTOFF = 1 << 2, // Cut off sounds when an actor vanishes instead of making it owner-less
|
||||
COMPATF2_POINTONLINE = 1 << 3, // Use original but buggy P_PointOnLineSide() and P_PointOnDivlineSide()
|
||||
COMPATF2_MULTIEXIT = 1 << 4, // Level exit can be triggered multiple times (required by Daedalus's travel tubes, thanks to a faulty script)
|
||||
};
|
||||
|
||||
// Emulate old bugs for select maps. These are not exposed by a cvar
|
||||
|
|
|
@ -535,7 +535,7 @@ void G_ChangeLevel(const char *levelname, int position, int flags, int nextSkill
|
|||
Printf (TEXTCOLOR_RED "Unloading scripts cannot exit the level again.\n");
|
||||
return;
|
||||
}
|
||||
if (gameaction == ga_completed) // do not exit multiple times.
|
||||
if (gameaction == ga_completed && !(i_compatflags2 & COMPATF2_MULTIEXIT)) // do not exit multiple times.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1328,6 +1328,7 @@ MapFlagHandlers[] =
|
|||
{ "compat_floormove", MITYPE_COMPATFLAG, 0, COMPATF2_FLOORMOVE },
|
||||
{ "compat_soundcutoff", MITYPE_COMPATFLAG, 0, COMPATF2_SOUNDCUTOFF },
|
||||
{ "compat_pointonline", MITYPE_COMPATFLAG, 0, COMPATF2_POINTONLINE },
|
||||
{ "compat_multiexit", MITYPE_COMPATFLAG, 0, COMPATF2_MULTIEXIT },
|
||||
{ "cd_start_track", MITYPE_EATNEXT, 0, 0 },
|
||||
{ "cd_end1_track", MITYPE_EATNEXT, 0, 0 },
|
||||
{ "cd_end2_track", MITYPE_EATNEXT, 0, 0 },
|
||||
|
|
|
@ -550,3 +550,11 @@ void AHexenArmor::AbsorbDamage (int damage, FName damageType, int &newdamage)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void AHexenArmor::DepleteOrDestroy()
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Slots[i] = 0;
|
||||
}
|
||||
}
|
|
@ -528,7 +528,11 @@ void DBaseDecal::Spread (const FDecalTemplate *tpl, side_t *wall, fixed_t x, fix
|
|||
GetWallStuff (wall, v1, ldx, ldy);
|
||||
rorg = Length (x - v1->x, y - v1->y);
|
||||
|
||||
tex = TexMan[PicNum];
|
||||
if ((tex = TexMan[PicNum]) == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int dwidth = tex->GetWidth ();
|
||||
|
||||
DecalWidth = dwidth * ScaleX;
|
||||
|
|
|
@ -508,6 +508,7 @@ public:
|
|||
virtual AInventory *CreateTossable ();
|
||||
virtual bool HandlePickup (AInventory *item);
|
||||
virtual void AbsorbDamage (int damage, FName damageType, int &newdamage);
|
||||
void DepleteOrDestroy();
|
||||
|
||||
fixed_t Slots[5];
|
||||
fixed_t SlotsIncrement[4];
|
||||
|
|
|
@ -927,7 +927,7 @@ void cht_Take (player_t *player, const char *name, int amount)
|
|||
AInventory *ammo = player->mo->FindInventory(static_cast<PClassActor *>(type));
|
||||
|
||||
if (ammo)
|
||||
ammo->Amount = 0;
|
||||
ammo->DepleteOrDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -943,10 +943,10 @@ void cht_Take (player_t *player, const char *name, int amount)
|
|||
|
||||
if (type->IsDescendantOf (RUNTIME_CLASS (AArmor)))
|
||||
{
|
||||
AActor *armor = player->mo->FindInventory(static_cast<PClassActor *>(type));
|
||||
AInventory *armor = player->mo->FindInventory(static_cast<PClassActor *>(type));
|
||||
|
||||
if (armor)
|
||||
armor->Destroy ();
|
||||
armor->DepleteOrDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -280,7 +280,10 @@ xx(Cast) // 'damage type' for the cast call
|
|||
|
||||
// Special names for thingdef_exp.cpp
|
||||
xx(Random)
|
||||
xx(FRandom)
|
||||
xx(Random2)
|
||||
xx(RandomPick)
|
||||
xx(FRandomPick)
|
||||
xx(Cos)
|
||||
xx(Sin)
|
||||
xx(Alpha)
|
||||
|
|
132
src/p_map.cpp
132
src/p_map.cpp
|
@ -748,6 +748,30 @@ int P_GetMoveFactor(const AActor *mo, int *frictionp)
|
|||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Checks if the line intersects with the actor
|
||||
// returns
|
||||
// - 1 when above/below
|
||||
// - 0 when intersecting
|
||||
// - -1 when outside the portal
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static int LineIsAbove(line_t *line, AActor *actor)
|
||||
{
|
||||
AActor *point = line->frontsector->SkyBoxes[sector_t::floor];
|
||||
if (point == NULL) return -1;
|
||||
return point->threshold >= actor->Top();
|
||||
}
|
||||
|
||||
static int LineIsBelow(line_t *line, AActor *actor)
|
||||
{
|
||||
AActor *point = line->frontsector->SkyBoxes[sector_t::ceiling];
|
||||
if (point == NULL) return -1;
|
||||
return point->threshold <= actor->Z();
|
||||
}
|
||||
|
||||
//
|
||||
// MOVEMENT ITERATOR FUNCTIONS
|
||||
//
|
||||
|
@ -788,6 +812,13 @@ bool PIT_CheckLine(FMultiBlockLinesIterator &mit, FMultiBlockLinesIterator::Chec
|
|||
|
||||
if (!ld->backsector)
|
||||
{ // One sided line
|
||||
if (((cres.portalflags & FFCF_NOFLOOR) && LineIsAbove(cres.line, tm.thing) != 0) ||
|
||||
((cres.portalflags & FFCF_NOCEILING) && LineIsBelow(cres.line, tm.thing) != 0))
|
||||
{
|
||||
// this blocking line is in a different vertical layer and does not intersect with the actor that is being checked.
|
||||
// Since a one-sided line does not have an opening there's nothing left to do about it.
|
||||
return true;
|
||||
}
|
||||
if (tm.thing->flags2 & MF2_BLASTED)
|
||||
{
|
||||
P_DamageMobj(tm.thing, NULL, NULL, tm.thing->Mass >> 5, NAME_Melee);
|
||||
|
@ -818,17 +849,52 @@ bool PIT_CheckLine(FMultiBlockLinesIterator &mit, FMultiBlockLinesIterator::Chec
|
|||
((Projectile) && (ld->flags & ML_BLOCKPROJECTILE)) || // block projectiles
|
||||
((tm.thing->flags & MF_FLOAT) && (ld->flags & ML_BLOCK_FLOATERS))) // block floaters
|
||||
{
|
||||
if (tm.thing->flags2 & MF2_BLASTED)
|
||||
if (cres.portalflags & FFCF_NOFLOOR)
|
||||
{
|
||||
P_DamageMobj(tm.thing, NULL, NULL, tm.thing->Mass >> 5, NAME_Melee);
|
||||
int state = LineIsAbove(cres.line, tm.thing);
|
||||
if (state == -1) return true;
|
||||
if (state == 1)
|
||||
{
|
||||
// the line should not block but we should set the ceilingz to the portal boundary so that we can't float up into that line.
|
||||
fixed_t portalz = cres.line->frontsector->SkyBoxes[sector_t::floor]->threshold;
|
||||
if (portalz < tm.ceilingz)
|
||||
{
|
||||
tm.ceilingz = portalz;
|
||||
tm.ceilingsector = cres.line->frontsector;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (cres.portalflags & FFCF_NOCEILING)
|
||||
{
|
||||
// same, but for downward portals
|
||||
int state = LineIsBelow(cres.line, tm.thing);
|
||||
if (state == -1) return true;
|
||||
if (state == 1)
|
||||
{
|
||||
fixed_t portalz = cres.line->frontsector->SkyBoxes[sector_t::ceiling]->threshold;
|
||||
if (portalz > tm.floorz)
|
||||
{
|
||||
tm.floorz = portalz;
|
||||
tm.floorsector = cres.line->frontsector;
|
||||
tm.floorterrain = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tm.thing->flags2 & MF2_BLASTED)
|
||||
{
|
||||
P_DamageMobj(tm.thing, NULL, NULL, tm.thing->Mass >> 5, NAME_Melee);
|
||||
}
|
||||
tm.thing->BlockingLine = ld;
|
||||
// Calculate line side based on the actor's original position, not the new one.
|
||||
CheckForPushSpecial(ld, P_PointOnLineSide(cres.position.x, cres.position.y, ld), tm.thing);
|
||||
return false;
|
||||
}
|
||||
tm.thing->BlockingLine = ld;
|
||||
// Calculate line side based on the actor's original position, not the new one.
|
||||
CheckForPushSpecial(ld, P_PointOnLineSide(cres.position.x, cres.position.y, ld), tm.thing);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fixedvec2 ref = FindRefPoint(ld, cres.position);
|
||||
FLineOpening open;
|
||||
|
||||
|
@ -867,33 +933,39 @@ bool PIT_CheckLine(FMultiBlockLinesIterator &mit, FMultiBlockLinesIterator::Chec
|
|||
}
|
||||
|
||||
// adjust floor / ceiling heights
|
||||
if (open.top < tm.ceilingz)
|
||||
if (!(cres.portalflags & FFCF_NOCEILING))
|
||||
{
|
||||
tm.ceilingz = open.top;
|
||||
tm.ceilingsector = open.topsec;
|
||||
tm.ceilingpic = open.ceilingpic;
|
||||
tm.ceilingline = ld;
|
||||
tm.thing->BlockingLine = ld;
|
||||
if (open.top < tm.ceilingz)
|
||||
{
|
||||
tm.ceilingz = open.top;
|
||||
tm.ceilingsector = open.topsec;
|
||||
tm.ceilingpic = open.ceilingpic;
|
||||
tm.ceilingline = ld;
|
||||
tm.thing->BlockingLine = ld;
|
||||
}
|
||||
}
|
||||
|
||||
if (open.bottom > tm.floorz)
|
||||
if (!(cres.portalflags & FFCF_NOFLOOR))
|
||||
{
|
||||
tm.floorz = open.bottom;
|
||||
tm.floorsector = open.bottomsec;
|
||||
tm.floorpic = open.floorpic;
|
||||
tm.floorterrain = open.floorterrain;
|
||||
tm.touchmidtex = open.touchmidtex;
|
||||
tm.abovemidtex = open.abovemidtex;
|
||||
tm.thing->BlockingLine = ld;
|
||||
}
|
||||
else if (open.bottom == tm.floorz)
|
||||
{
|
||||
tm.touchmidtex |= open.touchmidtex;
|
||||
tm.abovemidtex |= open.abovemidtex;
|
||||
}
|
||||
if (open.bottom > tm.floorz)
|
||||
{
|
||||
tm.floorz = open.bottom;
|
||||
tm.floorsector = open.bottomsec;
|
||||
tm.floorpic = open.floorpic;
|
||||
tm.floorterrain = open.floorterrain;
|
||||
tm.touchmidtex = open.touchmidtex;
|
||||
tm.abovemidtex = open.abovemidtex;
|
||||
tm.thing->BlockingLine = ld;
|
||||
}
|
||||
else if (open.bottom == tm.floorz)
|
||||
{
|
||||
tm.touchmidtex |= open.touchmidtex;
|
||||
tm.abovemidtex |= open.abovemidtex;
|
||||
}
|
||||
|
||||
if (open.lowfloor < tm.dropoffz)
|
||||
tm.dropoffz = open.lowfloor;
|
||||
if (open.lowfloor < tm.dropoffz)
|
||||
tm.dropoffz = open.lowfloor;
|
||||
}
|
||||
|
||||
// if contacted a special line, add it to the list
|
||||
spechit_t spec;
|
||||
|
@ -903,7 +975,7 @@ bool PIT_CheckLine(FMultiBlockLinesIterator &mit, FMultiBlockLinesIterator::Chec
|
|||
spec.refpos = cres.position;
|
||||
spechit.Push(spec);
|
||||
}
|
||||
if (ld->portalindex >= 0)
|
||||
if (ld->portalindex >= 0 && ld->portalindex != UINT_MAX)
|
||||
{
|
||||
spec.line = ld;
|
||||
spec.refpos = cres.position;
|
||||
|
|
|
@ -3288,6 +3288,48 @@ void AActor::SetRoll(angle_t r, bool interpolate)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void AActor::CheckPortalTransition(bool islinked)
|
||||
{
|
||||
bool moved = false;
|
||||
while (!Sector->PortalBlocksMovement(sector_t::ceiling))
|
||||
{
|
||||
AActor *port = Sector->SkyBoxes[sector_t::ceiling];
|
||||
if (Z() > port->threshold)
|
||||
{
|
||||
fixedvec3 oldpos = Pos();
|
||||
if (islinked && !moved) UnlinkFromWorld();
|
||||
SetXYZ(PosRelative(port->Sector));
|
||||
PrevX += X() - oldpos.x;
|
||||
PrevY += Y() - oldpos.y;
|
||||
PrevZ += Z() - oldpos.z;
|
||||
Sector = P_PointInSector(X(), Y());
|
||||
moved = true;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
if (!moved)
|
||||
{
|
||||
while (!Sector->PortalBlocksMovement(sector_t::floor))
|
||||
{
|
||||
AActor *port = Sector->SkyBoxes[sector_t::floor];
|
||||
if (Z() < port->threshold && floorz < port->threshold)
|
||||
{
|
||||
fixedvec3 oldpos = Pos();
|
||||
if (islinked && !moved) UnlinkFromWorld();
|
||||
SetXYZ(PosRelative(port->Sector));
|
||||
PrevX += X() - oldpos.x;
|
||||
PrevY += Y() - oldpos.y;
|
||||
PrevZ += Z() - oldpos.z;
|
||||
Sector = P_PointInSector(X(), Y());
|
||||
moved = true;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
}
|
||||
if (islinked && moved) LinkToWorld();
|
||||
}
|
||||
|
||||
//
|
||||
// P_MobjThinker
|
||||
//
|
||||
|
@ -3355,6 +3397,7 @@ void AActor::Tick ()
|
|||
UnlinkFromWorld ();
|
||||
flags |= MF_NOBLOCKMAP;
|
||||
SetXYZ(Vec3Offset(velx, vely, velz));
|
||||
CheckPortalTransition(false);
|
||||
SetMovement(velx, vely, velz);
|
||||
LinkToWorld ();
|
||||
}
|
||||
|
@ -3822,6 +3865,8 @@ void AActor::Tick ()
|
|||
Crash();
|
||||
}
|
||||
|
||||
CheckPortalTransition(true);
|
||||
|
||||
UpdateWaterLevel (oldz);
|
||||
|
||||
// [RH] Don't advance if predicting a player
|
||||
|
|
|
@ -581,8 +581,11 @@ void R_InterpolateView (player_t *player, fixed_t frac, InterpolationViewer *ivi
|
|||
iview->oviewpitch = iview->nviewpitch;
|
||||
iview->oviewangle = iview->nviewangle;
|
||||
}
|
||||
viewx = iview->oviewx + FixedMul (frac, iview->nviewx - iview->oviewx);
|
||||
viewy = iview->oviewy + FixedMul (frac, iview->nviewy - iview->oviewy);
|
||||
int oldgroup = R_PointInSubsector(iview->oviewx, iview->oviewy)->sector->PortalGroup;
|
||||
int newgroup = R_PointInSubsector(iview->nviewx, iview->nviewy)->sector->PortalGroup;
|
||||
fixedvec2 disp = Displacements(oldgroup, newgroup);
|
||||
viewx = iview->oviewx + FixedMul (frac, iview->nviewx - iview->oviewx - disp.x);
|
||||
viewy = iview->oviewy + FixedMul (frac, iview->nviewy - iview->oviewy - disp.y);
|
||||
viewz = iview->oviewz + FixedMul (frac, iview->nviewz - iview->oviewz);
|
||||
if (player != NULL &&
|
||||
!(player->cheats & CF_INTERPVIEW) &&
|
||||
|
@ -637,6 +640,34 @@ void R_InterpolateView (player_t *player, fixed_t frac, InterpolationViewer *ivi
|
|||
|
||||
// Due to interpolation this is not necessarily the same as the sector the camera is in.
|
||||
viewsector = R_PointInSubsector(viewx, viewy)->sector;
|
||||
bool moved = false;
|
||||
while (!viewsector->PortalBlocksMovement(sector_t::ceiling))
|
||||
{
|
||||
AActor *point = viewsector->SkyBoxes[sector_t::ceiling];
|
||||
if (viewz > point->threshold)
|
||||
{
|
||||
viewx += point->scaleX;
|
||||
viewy += point->scaleY;
|
||||
viewsector = R_PointInSubsector(viewx, viewy)->sector;
|
||||
moved = true;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
if (!moved)
|
||||
{
|
||||
while (!viewsector->PortalBlocksMovement(sector_t::floor))
|
||||
{
|
||||
AActor *point = viewsector->SkyBoxes[sector_t::floor];
|
||||
if (viewz < point->threshold)
|
||||
{
|
||||
viewx += point->scaleX;
|
||||
viewy += point->scaleY;
|
||||
viewsector = R_PointInSubsector(viewx, viewy)->sector;
|
||||
moved = true;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
|
|
@ -185,12 +185,6 @@ std2:
|
|||
'#include' { RET(TK_Include); }
|
||||
'fixed_t' { RET(TK_Fixed_t); }
|
||||
'angle_t' { RET(TK_Angle_t); }
|
||||
'abs' { RET(TK_Abs); }
|
||||
'random' { RET(TK_Random); }
|
||||
'random2' { RET(TK_Random2); }
|
||||
'frandom' { RET(TK_FRandom); }
|
||||
'randompick' { RET(TK_RandomPick); }
|
||||
'frandompick' { RET(TK_FRandomPick); }
|
||||
|
||||
L (L|D)* { RET(TK_Identifier); }
|
||||
|
||||
|
|
|
@ -113,10 +113,6 @@ xx(TK_Stop, "'stop'")
|
|||
xx(TK_Include, "'include'")
|
||||
xx(TK_Fixed_t, "'fixed_t'")
|
||||
xx(TK_Angle_t, "'angle_t'")
|
||||
xx(TK_Abs, "'abs'")
|
||||
xx(TK_Random, "'random'")
|
||||
xx(TK_Random2, "'random2'")
|
||||
xx(TK_FRandom, "'frandom'")
|
||||
|
||||
xx(TK_Is, "'is'")
|
||||
xx(TK_Replaces, "'replaces'")
|
||||
|
@ -126,8 +122,6 @@ xx(TK_Array, "'array'")
|
|||
xx(TK_In, "'in'")
|
||||
xx(TK_SizeOf, "'sizeof'")
|
||||
xx(TK_AlignOf, "'alignof'")
|
||||
xx(TK_RandomPick, "'randompick'")
|
||||
xx(TK_FRandomPick, "'frandompick'")
|
||||
xx(TK_States, "'states'")
|
||||
xx(TK_Loop, "'loop'")
|
||||
xx(TK_Fail, "'fail'")
|
||||
|
|
|
@ -5381,13 +5381,13 @@ enum RadiusGiveFlags
|
|||
static bool DoRadiusGive(AActor *self, AActor *thing, PClassActor *item, int amount, fixed_t distance, int flags, PClassActor *filter, FName species, fixed_t mindist)
|
||||
{
|
||||
// [MC] We only want to make an exception for missiles here. Nothing else.
|
||||
bool missilePass = !!((flags & RGF_MISSILES) && thing->isMissile());
|
||||
bool missilePass = !!((flags & RGF_MISSILES) && thing->flags & MF_MISSILE);
|
||||
if (thing == self)
|
||||
{
|
||||
if (!(flags & RGF_GIVESELF))
|
||||
return false;
|
||||
}
|
||||
else if (thing->isMissile())
|
||||
else if (thing->flags & MF_MISSILE)
|
||||
{
|
||||
if (!missilePass)
|
||||
return false;
|
||||
|
|
|
@ -52,6 +52,12 @@
|
|||
|
||||
FRandom pr_exrandom ("EX_Random");
|
||||
|
||||
static FxExpression *ParseRandom(FScanner &sc, FName identifier, PClassActor *cls);
|
||||
static FxExpression *ParseRandomPick(FScanner &sc, FName identifier, PClassActor *cls);
|
||||
static FxExpression *ParseRandom2(FScanner &sc, PClassActor *cls);
|
||||
static FxExpression *ParseAbs(FScanner &sc, PClassActor *cls);
|
||||
static FxExpression *ParseMinMax(FScanner &sc, FName identifier, PClassActor *cls);
|
||||
|
||||
//
|
||||
// ParseExpression
|
||||
// [GRB] Parses an expression and stores it into Expression array
|
||||
|
@ -349,146 +355,63 @@ static FxExpression *ParseExpression0 (FScanner &sc, PClassActor *cls)
|
|||
// a cheap way to get them working when people use "name" instead of 'name'.
|
||||
return new FxConstant(FName(sc.String), scpos);
|
||||
}
|
||||
else if (sc.CheckToken(TK_Random))
|
||||
{
|
||||
FRandom *rng;
|
||||
|
||||
if (sc.CheckToken('['))
|
||||
{
|
||||
sc.MustGetToken(TK_Identifier);
|
||||
rng = FRandom::StaticFindRNG(sc.String);
|
||||
sc.MustGetToken(']');
|
||||
}
|
||||
else
|
||||
{
|
||||
rng = &pr_exrandom;
|
||||
}
|
||||
sc.MustGetToken('(');
|
||||
|
||||
FxExpression *min = ParseExpressionM (sc, cls);
|
||||
sc.MustGetToken(',');
|
||||
FxExpression *max = ParseExpressionM (sc, cls);
|
||||
sc.MustGetToken(')');
|
||||
|
||||
return new FxRandom(rng, min, max, sc);
|
||||
}
|
||||
else if (sc.CheckToken(TK_RandomPick) || sc.CheckToken(TK_FRandomPick))
|
||||
{
|
||||
bool floaty = sc.TokenType == TK_FRandomPick;
|
||||
FRandom *rng;
|
||||
TArray<FxExpression*> list;
|
||||
list.Clear();
|
||||
int index = 0;
|
||||
|
||||
if (sc.CheckToken('['))
|
||||
{
|
||||
sc.MustGetToken(TK_Identifier);
|
||||
rng = FRandom::StaticFindRNG(sc.String);
|
||||
sc.MustGetToken(']');
|
||||
}
|
||||
else
|
||||
{
|
||||
rng = &pr_exrandom;
|
||||
}
|
||||
sc.MustGetToken('(');
|
||||
|
||||
for (;;)
|
||||
{
|
||||
FxExpression *expr = ParseExpressionM(sc, cls);
|
||||
list.Push(expr);
|
||||
if (sc.CheckToken(')'))
|
||||
break;
|
||||
sc.MustGetToken(',');
|
||||
}
|
||||
return new FxRandomPick(rng, list, floaty, sc);
|
||||
}
|
||||
else if (sc.CheckToken(TK_FRandom))
|
||||
{
|
||||
FRandom *rng;
|
||||
|
||||
if (sc.CheckToken('['))
|
||||
{
|
||||
sc.MustGetToken(TK_Identifier);
|
||||
rng = FRandom::StaticFindRNG(sc.String);
|
||||
sc.MustGetToken(']');
|
||||
}
|
||||
else
|
||||
{
|
||||
rng = &pr_exrandom;
|
||||
}
|
||||
sc.MustGetToken('(');
|
||||
|
||||
FxExpression *min = ParseExpressionM (sc, cls);
|
||||
sc.MustGetToken(',');
|
||||
FxExpression *max = ParseExpressionM (sc, cls);
|
||||
sc.MustGetToken(')');
|
||||
|
||||
return new FxFRandom(rng, min, max, sc);
|
||||
}
|
||||
else if (sc.CheckToken(TK_Random2))
|
||||
{
|
||||
FRandom *rng;
|
||||
|
||||
if (sc.CheckToken('['))
|
||||
{
|
||||
sc.MustGetToken(TK_Identifier);
|
||||
rng = FRandom::StaticFindRNG(sc.String);
|
||||
sc.MustGetToken(']');
|
||||
}
|
||||
else
|
||||
{
|
||||
rng = &pr_exrandom;
|
||||
}
|
||||
|
||||
sc.MustGetToken('(');
|
||||
|
||||
FxExpression *mask = NULL;
|
||||
|
||||
if (!sc.CheckToken(')'))
|
||||
{
|
||||
mask = ParseExpressionM(sc, cls);
|
||||
sc.MustGetToken(')');
|
||||
}
|
||||
return new FxRandom2(rng, mask, sc);
|
||||
}
|
||||
else if (sc.CheckToken(TK_Abs))
|
||||
{
|
||||
sc.MustGetToken('(');
|
||||
FxExpression *x = ParseExpressionM (sc, cls);
|
||||
sc.MustGetToken(')');
|
||||
return new FxAbs(x);
|
||||
}
|
||||
else if (sc.CheckToken(TK_Identifier))
|
||||
{
|
||||
FName identifier = FName(sc.String);
|
||||
FArgumentList *args;
|
||||
PFunction *func;
|
||||
|
||||
switch (identifier)
|
||||
{
|
||||
case NAME_Random:
|
||||
case NAME_FRandom:
|
||||
return ParseRandom(sc, identifier, cls);
|
||||
case NAME_RandomPick:
|
||||
case NAME_FRandomPick:
|
||||
return ParseRandomPick(sc, identifier, cls);
|
||||
case NAME_Random2:
|
||||
return ParseRandom2(sc, cls);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (sc.CheckToken('('))
|
||||
{
|
||||
FArgumentList *args = new FArgumentList;
|
||||
PFunction *func = dyn_cast<PFunction>(cls->Symbols.FindSymbol(identifier, true));
|
||||
try
|
||||
switch (identifier)
|
||||
{
|
||||
// There is an action function ACS_NamedExecuteWithResult which must be ignored here for this to work.
|
||||
if (func != NULL && identifier != NAME_ACS_NamedExecuteWithResult)
|
||||
case NAME_Min:
|
||||
case NAME_Max:
|
||||
return ParseMinMax(sc, identifier, cls);
|
||||
case NAME_Abs:
|
||||
return ParseAbs(sc, cls);
|
||||
default:
|
||||
args = new FArgumentList;
|
||||
func = dyn_cast<PFunction>(cls->Symbols.FindSymbol(identifier, true));
|
||||
try
|
||||
{
|
||||
sc.UnGet();
|
||||
ParseFunctionParameters(sc, cls, *args, func, "", NULL);
|
||||
return new FxVMFunctionCall(func, args, sc);
|
||||
}
|
||||
else if (!sc.CheckToken(')'))
|
||||
{
|
||||
do
|
||||
// There is an action function ACS_NamedExecuteWithResult which must be ignored here for this to work.
|
||||
if (func != NULL && identifier != NAME_ACS_NamedExecuteWithResult)
|
||||
{
|
||||
args->Push(ParseExpressionM (sc, cls));
|
||||
sc.UnGet();
|
||||
ParseFunctionParameters(sc, cls, *args, func, "", NULL);
|
||||
return new FxVMFunctionCall(func, args, sc);
|
||||
}
|
||||
while (sc.CheckToken(','));
|
||||
sc.MustGetToken(')');
|
||||
else if (!sc.CheckToken(')'))
|
||||
{
|
||||
do
|
||||
{
|
||||
args->Push(ParseExpressionM (sc, cls));
|
||||
}
|
||||
while (sc.CheckToken(','));
|
||||
sc.MustGetToken(')');
|
||||
}
|
||||
return new FxFunctionCall(NULL, identifier, args, sc);
|
||||
}
|
||||
return new FxFunctionCall(NULL, identifier, args, sc);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
delete args;
|
||||
throw;
|
||||
catch (...)
|
||||
{
|
||||
delete args;
|
||||
throw;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -504,4 +427,97 @@ static FxExpression *ParseExpression0 (FScanner &sc, PClassActor *cls)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static FRandom *ParseRNG(FScanner &sc)
|
||||
{
|
||||
FRandom *rng;
|
||||
|
||||
if (sc.CheckToken('['))
|
||||
{
|
||||
sc.MustGetToken(TK_Identifier);
|
||||
rng = FRandom::StaticFindRNG(sc.String);
|
||||
sc.MustGetToken(']');
|
||||
}
|
||||
else
|
||||
{
|
||||
rng = &pr_exrandom;
|
||||
}
|
||||
return rng;
|
||||
}
|
||||
|
||||
static FxExpression *ParseRandom(FScanner &sc, FName identifier, PClassActor *cls)
|
||||
{
|
||||
FRandom *rng = ParseRNG(sc);
|
||||
|
||||
sc.MustGetToken('(');
|
||||
FxExpression *min = ParseExpressionM (sc, cls);
|
||||
sc.MustGetToken(',');
|
||||
FxExpression *max = ParseExpressionM (sc, cls);
|
||||
sc.MustGetToken(')');
|
||||
|
||||
if (identifier == NAME_Random)
|
||||
{
|
||||
return new FxRandom(rng, min, max, sc);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FxFRandom(rng, min, max, sc);
|
||||
}
|
||||
}
|
||||
|
||||
static FxExpression *ParseRandomPick(FScanner &sc, FName identifier, PClassActor *cls)
|
||||
{
|
||||
bool floaty = identifier == NAME_FRandomPick;
|
||||
FRandom *rng;
|
||||
TArray<FxExpression*> list;
|
||||
list.Clear();
|
||||
int index = 0;
|
||||
|
||||
rng = ParseRNG(sc);
|
||||
sc.MustGetToken('(');
|
||||
|
||||
for (;;)
|
||||
{
|
||||
FxExpression *expr = ParseExpressionM(sc, cls);
|
||||
list.Push(expr);
|
||||
if (sc.CheckToken(')'))
|
||||
break;
|
||||
sc.MustGetToken(',');
|
||||
}
|
||||
return new FxRandomPick(rng, list, floaty, sc);
|
||||
}
|
||||
|
||||
static FxExpression *ParseRandom2(FScanner &sc, PClassActor *cls)
|
||||
{
|
||||
FRandom *rng = ParseRNG(sc);
|
||||
FxExpression *mask = NULL;
|
||||
|
||||
sc.MustGetToken('(');
|
||||
|
||||
if (!sc.CheckToken(')'))
|
||||
{
|
||||
mask = ParseExpressionM(sc, cls);
|
||||
sc.MustGetToken(')');
|
||||
}
|
||||
return new FxRandom2(rng, mask, sc);
|
||||
}
|
||||
|
||||
static FxExpression *ParseAbs(FScanner &sc, PClassActor *cls)
|
||||
{
|
||||
FxExpression *x = ParseExpressionM (sc, cls);
|
||||
sc.MustGetToken(')');
|
||||
return new FxAbs(x);
|
||||
}
|
||||
|
||||
static FxExpression *ParseMinMax(FScanner &sc, FName identifier, PClassActor *cls)
|
||||
{
|
||||
TArray<FxExpression*> list;
|
||||
for (;;)
|
||||
{
|
||||
FxExpression *expr = ParseExpressionM(sc, cls);
|
||||
list.Push(expr);
|
||||
if (sc.CheckToken(')'))
|
||||
break;
|
||||
sc.MustGetToken(',');
|
||||
}
|
||||
return new FxMinMax(list, identifier, sc);
|
||||
}
|
||||
|
|
|
@ -629,6 +629,24 @@ public:
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
class FxMinMax : public FxExpression
|
||||
{
|
||||
TDeletingArray<FxExpression *> choices;
|
||||
FName Type;
|
||||
|
||||
public:
|
||||
FxMinMax(TArray<FxExpression*> &expr, FName type, const FScriptPosition &pos);
|
||||
FxExpression *Resolve(FCompileContext&);
|
||||
|
||||
ExpEmit Emit(VMFunctionBuilder *build);
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
class FxRandom : public FxExpression
|
||||
{
|
||||
protected:
|
||||
|
|
|
@ -2038,6 +2038,225 @@ ExpEmit FxAbs::Emit(VMFunctionBuilder *build)
|
|||
return out;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
FxMinMax::FxMinMax(TArray<FxExpression*> &expr, FName type, const FScriptPosition &pos)
|
||||
: FxExpression(pos), Type(type)
|
||||
{
|
||||
assert(expr.Size() > 0);
|
||||
assert(type == NAME_Min || type == NAME_Max);
|
||||
|
||||
ValueType = VAL_Unknown;
|
||||
choices.Resize(expr.Size());
|
||||
for (unsigned i = 0; i < expr.Size(); ++i)
|
||||
{
|
||||
choices[i] = expr[i];
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
FxExpression *FxMinMax::Resolve(FCompileContext &ctx)
|
||||
{
|
||||
unsigned int i;
|
||||
int intcount, floatcount;
|
||||
|
||||
CHECKRESOLVED();
|
||||
|
||||
// Determine if float or int
|
||||
intcount = floatcount = 0;
|
||||
for (i = 0; i < choices.Size(); ++i)
|
||||
{
|
||||
RESOLVE(choices[i], ctx);
|
||||
ABORT(choices[i]);
|
||||
|
||||
if (choices[i]->ValueType == VAL_Float)
|
||||
{
|
||||
floatcount++;
|
||||
}
|
||||
else if (choices[i]->ValueType == VAL_Int)
|
||||
{
|
||||
intcount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ScriptPosition.Message(MSG_ERROR, "Arguments must be of type int or float");
|
||||
delete this;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (floatcount != 0)
|
||||
{
|
||||
ValueType = VAL_Float;
|
||||
if (intcount != 0)
|
||||
{ // There are some ints that need to be cast to floats
|
||||
for (i = 0; i < choices.Size(); ++i)
|
||||
{
|
||||
if (choices[i]->ValueType == VAL_Int)
|
||||
{
|
||||
choices[i] = new FxFloatCast(choices[i]);
|
||||
RESOLVE(choices[i], ctx);
|
||||
ABORT(choices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ValueType = VAL_Int;
|
||||
}
|
||||
|
||||
// If at least two arguments are constants, they can be solved now.
|
||||
|
||||
// Look for first constant
|
||||
for (i = 0; i < choices.Size(); ++i)
|
||||
{
|
||||
if (choices[i]->isConstant())
|
||||
{
|
||||
ExpVal best = static_cast<FxConstant *>(choices[i])->GetValue();
|
||||
// Compare against remaining constants, which are removed.
|
||||
// The best value gets stored in this one.
|
||||
for (unsigned j = i + 1; j < choices.Size(); )
|
||||
{
|
||||
if (!choices[j]->isConstant())
|
||||
{
|
||||
j++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ExpVal value = static_cast<FxConstant *>(choices[j])->GetValue();
|
||||
assert(value.Type == ValueType.Type);
|
||||
if (Type == NAME_Min)
|
||||
{
|
||||
if (value.Type == VAL_Float)
|
||||
{
|
||||
if (value.Float < best.Float)
|
||||
{
|
||||
best.Float = value.Float;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value.Int < best.Int)
|
||||
{
|
||||
best.Int = value.Int;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value.Type == VAL_Float)
|
||||
{
|
||||
if (value.Float > best.Float)
|
||||
{
|
||||
best.Float = value.Float;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value.Int > best.Int)
|
||||
{
|
||||
best.Int = value.Int;
|
||||
}
|
||||
}
|
||||
}
|
||||
delete choices[j];
|
||||
choices[j] = NULL;
|
||||
choices.Delete(j);
|
||||
}
|
||||
}
|
||||
FxExpression *x = new FxConstant(best, ScriptPosition);
|
||||
if (i == 0 && choices.Size() == 1)
|
||||
{ // Every choice was constant
|
||||
delete this;
|
||||
return x;
|
||||
}
|
||||
delete choices[i];
|
||||
choices[i] = x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
static void EmitLoad(VMFunctionBuilder *build, const ExpEmit resultreg, const ExpVal &value)
|
||||
{
|
||||
if (resultreg.RegType == REGT_FLOAT)
|
||||
{
|
||||
build->Emit(OP_LKF, resultreg.RegNum, build->GetConstantFloat(value.GetFloat()));
|
||||
}
|
||||
else
|
||||
{
|
||||
build->EmitLoadInt(resultreg.RegNum, value.GetInt());
|
||||
}
|
||||
}
|
||||
|
||||
ExpEmit FxMinMax::Emit(VMFunctionBuilder *build)
|
||||
{
|
||||
unsigned i;
|
||||
int opcode, opA;
|
||||
|
||||
assert(choices.Size() > 0);
|
||||
assert(OP_LTF_RK == OP_LTF_RR+1);
|
||||
assert(OP_LT_RK == OP_LT_RR+1);
|
||||
assert(OP_LEF_RK == OP_LEF_RR+1);
|
||||
assert(OP_LE_RK == OP_LE_RR+1);
|
||||
|
||||
if (Type == NAME_Min)
|
||||
{
|
||||
opcode = ValueType.Type == VAL_Float ? OP_LEF_RR : OP_LE_RR;
|
||||
opA = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
opcode = ValueType.Type == VAL_Float ? OP_LTF_RR : OP_LT_RR;
|
||||
opA = 0;
|
||||
}
|
||||
|
||||
ExpEmit bestreg;
|
||||
|
||||
// Get first value into a register. This will also be the result register.
|
||||
if (choices[0]->isConstant())
|
||||
{
|
||||
bestreg = ExpEmit(build, ValueType.Type == VAL_Float ? REGT_FLOAT : REGT_INT);
|
||||
EmitLoad(build, bestreg, static_cast<FxConstant *>(choices[0])->GetValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
bestreg = choices[0]->Emit(build);
|
||||
}
|
||||
|
||||
// Compare every choice. Better matches get copied to the bestreg.
|
||||
for (i = 1; i < choices.Size(); ++i)
|
||||
{
|
||||
ExpEmit checkreg = choices[i]->Emit(build);
|
||||
assert(checkreg.RegType == bestreg.RegType);
|
||||
build->Emit(opcode + checkreg.Konst, opA, bestreg.RegNum, checkreg.RegNum);
|
||||
build->Emit(OP_JMP, 1);
|
||||
if (checkreg.Konst)
|
||||
{
|
||||
build->Emit(bestreg.RegType == REGT_FLOAT ? OP_LKF : OP_LK, bestreg.RegNum, checkreg.RegNum);
|
||||
}
|
||||
else
|
||||
{
|
||||
build->Emit(bestreg.RegType == REGT_FLOAT ? OP_MOVEF : OP_MOVE, bestreg.RegNum, checkreg.RegNum, 0);
|
||||
checkreg.Free(build);
|
||||
}
|
||||
}
|
||||
return bestreg;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
|
@ -2269,16 +2488,7 @@ ExpEmit FxRandomPick::Emit(VMFunctionBuilder *build)
|
|||
build->BackpatchToHere(jumptable + i);
|
||||
if (choices[i]->isConstant())
|
||||
{
|
||||
if (ValueType == VAL_Int)
|
||||
{
|
||||
int val = static_cast<FxConstant *>(choices[i])->GetValue().GetInt();
|
||||
build->EmitLoadInt(resultreg.RegNum, val);
|
||||
}
|
||||
else
|
||||
{
|
||||
double val = static_cast<FxConstant *>(choices[i])->GetValue().GetFloat();
|
||||
build->Emit(OP_LKF, resultreg.RegNum, build->GetConstantFloat(val));
|
||||
}
|
||||
EmitLoad(build, resultreg, static_cast<FxConstant *>(choices[i])->GetValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -5,6 +5,13 @@
|
|||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level='asInvoker' uiAccess='false' />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
||||
<dpiAware>true</dpiAware>
|
||||
|
|
|
@ -311,8 +311,8 @@ ACTOR Actor native //: Thinker
|
|||
action native state A_JumpIfHigherOrLower(state high, state low, float offsethigh = 0, float offsetlow = 0, bool includeHeight = true, int ptr = AAPTR_TARGET);
|
||||
action native A_SetSpecies(name species, int ptr = AAPTR_DEFAULT);
|
||||
action native A_SetRipperLevel(int level);
|
||||
action native A_SetRipMin(int min);
|
||||
action native A_SetRipMax(int max);
|
||||
action native A_SetRipMin(int mininum);
|
||||
action native A_SetRipMax(int maximum);
|
||||
action native A_SetChaseThreshold(int threshold, bool def = false, int ptr = AAPTR_DEFAULT);
|
||||
action native state A_CheckProximity(state jump, class<Actor> classname, float distance, int count = 1, int flags = 0, int ptr = AAPTR_DEFAULT);
|
||||
action native state A_CheckBlock(state block, int flags = 0, int ptr = AAPTR_DEFAULT);
|
||||
|
|
|
@ -411,6 +411,11 @@ D0139194F7817BF06F3988DFC47DB38D // Whispers of Satan map29
|
|||
nopassover
|
||||
}
|
||||
|
||||
5397C3B7D9B33AAF526D15A81A762828 // daedalus.wad Travel tubes (they are all identical)
|
||||
{
|
||||
multiexit
|
||||
}
|
||||
|
||||
D7F6E9F08C39A17026349A04F8C0B0BE // Return to Hadron, e1m9
|
||||
19D03FFC875589E21EDBB7AB74EF4AEF // Return to Hadron, e1m9, 2016.01.03 update
|
||||
{
|
||||
|
|
|
@ -2035,6 +2035,7 @@ CMPTMNU_SHORTTEX = "Find shortest textures like Doom";
|
|||
CMPTMNU_STAIRS = "Use buggier stair building";
|
||||
CMPTMNU_FLOORMOVE = "Use Doom's floor motion behavior";
|
||||
CMPTMNU_POINTONLINE = "Use Doom's point-on-line algorithm";
|
||||
CMPTMNU_MULTIEXIT = "Level exit can be triggered more than once.";
|
||||
CMPTMNU_PHYSICSBEHAVIOR = "Physics Behavior";
|
||||
CMPTMNU_NOPASSOVER = "Actors are infinitely tall";
|
||||
CMPTMNU_BOOMSCROLL = "Boom scrollers are additive";
|
||||
|
|
|
@ -1316,6 +1316,7 @@ OptionMenu "CompatibilityOptions"
|
|||
Option "$CMPTMNU_STAIRS", "compat_stairs", "YesNo"
|
||||
Option "$CMPTMNU_FLOORMOVE", "compat_floormove", "YesNo"
|
||||
Option "$CMPTMNU_POINTONLINE", "compat_pointonline", "YesNo"
|
||||
Option "$CMPTMNU_MULTIEXIT", "compat_multiexit", "YesNo"
|
||||
|
||||
StaticText " "
|
||||
StaticText "$CMPTMNU_PHYSICSBEHAVIOR",1
|
||||
|
|
Loading…
Reference in a new issue