diff --git a/specs/udmf_zdoom.txt b/specs/udmf_zdoom.txt index 797d092ef..553c75360 100644 --- a/specs/udmf_zdoom.txt +++ b/specs/udmf_zdoom.txt @@ -197,6 +197,7 @@ Note: All fields default to false unless mentioned otherwise. desaturation = ; // Color desaturation factor. 0 = none, 1 = full, default = 0. silent = ; // Actors in this sector make no sound, nofallingdamage = ; // Falling damage is disabled in this sector + noattack = ; // Blocks monster attacks in this sector. dropactors = ; // Actors drop with instantly moving floors (*) norespawn = ; // Players can not respawn in this sector soundsequence = ; // The sound sequence to play when this sector moves. Placing a @@ -262,8 +263,8 @@ Note: All fields default to false unless mentioned otherwise. gravity = ; // Set per-actor gravity. Positive values are multiplied with the class's property, // negative values are used as their absolute. Default = 1.0. - health = ; // Set per-actor health. Positive values are multiplied with the class's property, - // negative values are used as their absolute. Default = 1. + health = ; // Set per-actor health as an absolute value. Default = actor default. + healthfactor = ; // Set per-actor health as a factor to the original. Default = 1. renderstyle = ; // Set per-actor render style, overriding the class default. Possible values can be "normal", // "none", "add" or "additive", "subtract" or "subtractive", "stencil", "translucentstencil", diff --git a/src/actionspecials.h b/src/actionspecials.h index aac1b7161..233bb8958 100644 --- a/src/actionspecials.h +++ b/src/actionspecials.h @@ -47,7 +47,7 @@ DEFINE_SPECIAL(Ceiling_CrushRaiseAndStay, 45, 3, 4, 4) DEFINE_SPECIAL(Floor_CrushStop, 46, 1, 1, 1) DEFINE_SPECIAL(Ceiling_MoveToValue, 47, 3, 5, 5) DEFINE_SPECIAL(Sector_Attach3dMidtex, 48, -1, -1, 3) -DEFINE_SPECIAL(GlassBreak, 49, 0, 1, 1) +DEFINE_SPECIAL(GlassBreak, 49, 0, 1, 2) DEFINE_SPECIAL(ExtraFloor_LightOnly, 50, -1, -1, 2) DEFINE_SPECIAL(Sector_SetLink, 51, 4, 4, 4) DEFINE_SPECIAL(Scroll_Wall, 52, 5, 5, 5) diff --git a/src/actor.h b/src/actor.h index b52cb6571..2ed004740 100644 --- a/src/actor.h +++ b/src/actor.h @@ -430,6 +430,7 @@ enum ActorRenderFlag RF_ABSMASKPITCH = 0x00800000, // [MC] The mask rotation does not offset by the actor's pitch. RF_INTERPOLATEANGLES = 0x01000000, // [MC] Allow interpolation of the actor's angle, pitch and roll. RF_MAYBEINVISIBLE = 0x02000000, + RF_DONTINTERPOLATE = 0x04000000, // no render interpolation ever! }; // This translucency value produces the closest match to Heretic's TINTTAB. @@ -467,6 +468,7 @@ enum ActorBounceFlag BOUNCE_MBF = 1<<12, // This in itself is not a valid mode, but replaces MBF's MF_BOUNCE flag. BOUNCE_AutoOffFloorOnly = 1<<13, // like BOUNCE_AutoOff, but only on floors BOUNCE_UseBounceState = 1<<14, // Use Bounce[.*] states + BOUNCE_NotOnShootables = 1<<15, // do not bounce off shootable actors if we are a projectile. Explode instead. BOUNCE_TypeMask = BOUNCE_Walls | BOUNCE_Floors | BOUNCE_Ceilings | BOUNCE_Actors | BOUNCE_AutoOff | BOUNCE_HereticType | BOUNCE_MBF, @@ -1321,7 +1323,8 @@ public: } DVector3 InterpolatedPosition(double ticFrac) const { - return Prev + (ticFrac * (Pos() - Prev)); + if (renderflags & RF_DONTINTERPOLATE) return Pos(); + else return Prev + (ticFrac * (Pos() - Prev)); } DRotator InterpolatedAngles(double ticFrac) const { diff --git a/src/d_net.cpp b/src/d_net.cpp index fb7cac316..10ff7d0fc 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -2666,12 +2666,13 @@ void Net_DoCommand (int type, BYTE **stream, int player) case DEM_NETEVENT: { - FString ename = ReadString(stream); + const char *ename = ReadString(stream); int argn = ReadByte(stream); int arg[3] = { 0, 0, 0 }; - for (int i = 0; i < argn; i++) + for (int i = 0; i < 3; i++) arg[i] = ReadLong(stream); E_Console(player, ename, arg[0], arg[1], arg[2]); + delete[] ename; } break; @@ -2721,6 +2722,10 @@ void Net_SkipCommand (int type, BYTE **stream) skip = strlen ((char *)(*stream)) + 5; break; + case DEM_NETEVENT: + skip = strlen((char *)(*stream)) + 13; + break; + case DEM_SUMMON2: case DEM_SUMMONFRIEND2: case DEM_SUMMONFOE2: diff --git a/src/events.cpp b/src/events.cpp index d714eefce..a0780fb92 100755 --- a/src/events.cpp +++ b/src/events.cpp @@ -1139,7 +1139,7 @@ CCMD(netevent) Net_WriteByte(DEM_NETEVENT); Net_WriteString(argv[1]); Net_WriteByte(argn); - for (int i = 0; i < argn; i++) + for (int i = 0; i < 3; i++) Net_WriteLong(arg[i]); } } diff --git a/src/g_level.h b/src/g_level.h index 03fa70dcd..06a91259a 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -524,6 +524,7 @@ struct FSkillInfo bool EasyBossBrain; bool EasyKey; + bool NoMenu; int RespawnCounter; int RespawnLimit; double Aggressiveness; diff --git a/src/g_shared/a_morph.cpp b/src/g_shared/a_morph.cpp index 1ae3c4b5e..9259561b0 100644 --- a/src/g_shared/a_morph.cpp +++ b/src/g_shared/a_morph.cpp @@ -701,3 +701,30 @@ void AMorphedMonster::Tick () Super::Tick (); } } + + +DEFINE_ACTION_FUNCTION(AActor, A_Morph) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_CLASS(type, AActor); + PARAM_INT_DEF(duration); + PARAM_INT_DEF(flags); + PARAM_CLASS_DEF(enter_flash, AActor); + PARAM_CLASS_DEF(exit_flash, AActor); + bool res = false; + if (self->player) + { + if (type->IsKindOf(RUNTIME_CLASS(APlayerPawn))) + { + res = P_MorphPlayer(self->player, self->player, type, duration, flags, enter_flash, exit_flash); + } + } + else + { + if (type->IsKindOf(RUNTIME_CLASS(AMorphedMonster))) + { + res = P_MorphMonster(self, type, duration, flags, enter_flash, exit_flash); + } + } + ACTION_RETURN_BOOL(res); +} diff --git a/src/g_skill.cpp b/src/g_skill.cpp index dd772a745..25108c0df 100644 --- a/src/g_skill.cpp +++ b/src/g_skill.cpp @@ -60,6 +60,7 @@ void FMapInfoParser::ParseSkill () bool thisisdefault = false; bool acsreturnisset = false; + skill.NoMenu = false; skill.AmmoFactor = 1.; skill.DoubleAmmoFactor = 2.; skill.DropAmmoFactor = -1.; @@ -149,6 +150,10 @@ void FMapInfoParser::ParseSkill () { skill.AutoUseHealth = true; } + else if (sc.Compare("nomenu")) + { + skill.NoMenu = true; + } else if (sc.Compare("respawntime")) { ParseAssign(); @@ -508,6 +513,7 @@ FSkillInfo &FSkillInfo::operator=(const FSkillInfo &other) { Name = other.Name; AmmoFactor = other.AmmoFactor; + NoMenu = other.NoMenu; DoubleAmmoFactor = other.DoubleAmmoFactor; DropAmmoFactor = other.DropAmmoFactor; DamageFactor = other.DamageFactor; diff --git a/src/gl/data/gl_data.cpp b/src/gl/data/gl_data.cpp index 93e4d5697..36ae3ad8f 100644 --- a/src/gl/data/gl_data.cpp +++ b/src/gl/data/gl_data.cpp @@ -109,6 +109,7 @@ void AdjustSpriteOffsets() { char str[9]; Wads.GetLumpName(str, i); + str[8] = 0; FTextureID texid = TexMan.CheckForTexture(str, FTexture::TEX_Sprite, 0); if (texid.isValid() && Wads.GetLumpFile(TexMan[texid]->SourceLump) > FWadCollection::IWAD_FILENUM) { diff --git a/src/info.cpp b/src/info.cpp index 90d847fcc..81912150b 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -767,6 +767,13 @@ DEFINE_ACTION_FUNCTION(_DamageTypeDefinition, IgnoreArmor) ACTION_RETURN_BOOL(DamageTypeDefinition::IgnoreArmor(type)); } +FString DamageTypeDefinition::GetObituary(FName type) +{ + DamageTypeDefinition *dtd = Get(type); + if (dtd) return dtd->Obituary; + return ""; +} + //========================================================================== // @@ -859,6 +866,12 @@ void FMapInfoParser::ParseDamageDefinition() dtd.DefaultFactor = sc.Float; if (dtd.DefaultFactor == 0) dtd.ReplaceFactor = true; } + if (sc.Compare("OBITUARY")) + { + sc.MustGetStringName("="); + sc.MustGetString(); + dtd.Obituary = sc.String; + } else if (sc.Compare("REPLACEFACTOR")) { dtd.ReplaceFactor = true; diff --git a/src/info.h b/src/info.h index 05785f90b..50db1e4b6 100644 --- a/src/info.h +++ b/src/info.h @@ -214,6 +214,7 @@ struct DamageTypeDefinition public: DamageTypeDefinition() { Clear(); } + FString Obituary; double DefaultFactor; bool ReplaceFactor; bool NoArmor; @@ -221,6 +222,7 @@ public: void Apply(FName type); void Clear() { + Obituary = ""; DefaultFactor = 1.; ReplaceFactor = false; NoArmor = false; @@ -228,6 +230,7 @@ public: static bool IgnoreArmor(FName type); static int ApplyMobjDamageFactor(int damage, FName type, DmgFactors const * const factors); + static FString GetObituary(FName type); private: static double GetMobjDamageFactor(FName type, DmgFactors const * const factors); diff --git a/src/intermission/intermission.cpp b/src/intermission/intermission.cpp index 27532e7e9..5dddef105 100644 --- a/src/intermission/intermission.cpp +++ b/src/intermission/intermission.cpp @@ -68,6 +68,7 @@ IMPLEMENT_POINTERS_END extern int NoWipe; +CVAR(Bool, nointerscrollabort, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); //========================================================================== // // @@ -647,7 +648,7 @@ void DIntermissionScreenScroller::Init(FIntermissionAction *desc, bool first) int DIntermissionScreenScroller::Responder (event_t *ev) { int res = Super::Responder(ev); - if (res == -1) + if (res == -1 && !nointerscrollabort) { mBackground = mSecondPic; mTicker = mScrollDelay + mScrollTime; diff --git a/src/menu/menu.cpp b/src/menu/menu.cpp index 71c0ff645..8b0bf35e9 100644 --- a/src/menu/menu.cpp +++ b/src/menu/menu.cpp @@ -64,6 +64,7 @@ CVAR (Float, mouse_sensitivity, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, show_messages, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, show_obituaries, true, CVAR_ARCHIVE) CVAR(Bool, m_showinputgrid, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, m_blockcontrollers, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR (Float, snd_menuvolume, 0.6f, CVAR_ARCHIVE) @@ -582,6 +583,9 @@ bool M_Responder (event_t *ev) } else if (menuactive != MENU_WaitKey && (ev->type == EV_KeyDown || ev->type == EV_KeyUp)) { + // eat blocked controller events without dispatching them. + if (ev->data1 >= KEY_FIRSTJOYBUTTON && m_blockcontrollers) return true; + keyup = ev->type == EV_KeyUp; ch = ev->data1; diff --git a/src/menu/menu.h b/src/menu/menu.h index 6d7bdc927..0b6b55ea2 100644 --- a/src/menu/menu.h +++ b/src/menu/menu.h @@ -339,7 +339,6 @@ void M_ActivateMenu(DMenu *menu); void M_ClearMenus (); void M_ParseMenuDefs(); void M_StartupSkillMenu(FGameStartup *gs); -int M_GetDefaultSkill(); void M_StartControlPanel (bool makeSound); void M_SetMenu(FName menu, int param = -1); void M_StartMessage(const char *message, int messagemode, FName action = NAME_None); diff --git a/src/menu/menudef.cpp b/src/menu/menudef.cpp index bc95198f2..dc4fff1a7 100644 --- a/src/menu/menudef.cpp +++ b/src/menu/menudef.cpp @@ -1327,6 +1327,43 @@ void M_StartupSkillMenu(FGameStartup *gs) { static int done = -1; bool success = false; + TArray MenuSkills; + TArray SkillIndices; + if (MenuSkills.Size() == 0) + { + for (unsigned ind = 0; ind < AllSkills.Size(); ind++) + { + if (!AllSkills[ind].NoMenu) + { + MenuSkills.Push(&AllSkills[ind]); + SkillIndices.Push(ind); + } + } + } + if (MenuSkills.Size() == 0) I_Error("No valid skills for menu found. At least one must be defined."); + + int defskill = DefaultSkill; + if ((unsigned int)defskill >= MenuSkills.Size()) + { + defskill = SkillIndices[(MenuSkills.Size() - 1) / 2]; + } + if (AllSkills[defskill].NoMenu) + { + for (defskill = 0; defskill < (int)AllSkills.Size(); defskill++) + { + if (!AllSkills[defskill].NoMenu) break; + } + } + int defindex = 0; + for (unsigned i = 0; i < MenuSkills.Size(); i++) + { + if (MenuSkills[i] == &AllSkills[defskill]) + { + defindex = i; + break; + } + } + DMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Skillmenu); if (desc != nullptr) { @@ -1350,12 +1387,7 @@ void M_StartupSkillMenu(FGameStartup *gs) if (done != restart) { done = restart; - int defskill = DefaultSkill; - if ((unsigned int)defskill >= AllSkills.Size()) - { - defskill = (AllSkills.Size() - 1) / 2; - } - ld->mSelectedItem = ld->mItems.Size() + defskill; + ld->mSelectedItem = ld->mItems.Size() + defindex; int posy = y; int topy = posy; @@ -1368,9 +1400,9 @@ void M_StartupSkillMenu(FGameStartup *gs) } // center the menu on the screen if the top space is larger than the bottom space - int totalheight = posy + AllSkills.Size() * ld->mLinespacing - topy; + int totalheight = posy + MenuSkills.Size() * ld->mLinespacing - topy; - if (totalheight < 190 || AllSkills.Size() == 1) + if (totalheight < 190 || MenuSkills.Size() == 1) { int newtop = (200 - totalheight + topy) / 2; int topdelta = newtop - topy; @@ -1393,9 +1425,9 @@ void M_StartupSkillMenu(FGameStartup *gs) } unsigned firstitem = ld->mItems.Size(); - for(unsigned int i = 0; i < AllSkills.Size(); i++) + for(unsigned int i = 0; i < MenuSkills.Size(); i++) { - FSkillInfo &skill = AllSkills[i]; + FSkillInfo &skill = *MenuSkills[i]; DMenuItemBase *li; // Using a different name for skills that must be confirmed makes handling this easier. FName action = (skill.MustConfirm && !AllEpisodes[gs->Episode].mNoSkill) ? @@ -1409,22 +1441,22 @@ void M_StartupSkillMenu(FGameStartup *gs) if (skill.PicName.Len() != 0 && pItemText == nullptr) { FTextureID tex = GetMenuTexture(skill.PicName); - li = CreateListMenuItemPatch(ld->mXpos, y, ld->mLinespacing, skill.Shortcut, tex, action, i); + li = CreateListMenuItemPatch(ld->mXpos, y, ld->mLinespacing, skill.Shortcut, tex, action, SkillIndices[i]); } else { EColorRange color = (EColorRange)skill.GetTextColor(); if (color == CR_UNTRANSLATED) color = ld->mFontColor; li = CreateListMenuItemText(x, y, ld->mLinespacing, skill.Shortcut, - pItemText? *pItemText : skill.MenuName, ld->mFont, color,ld->mFontColor2, action, i); + pItemText? *pItemText : skill.MenuName, ld->mFont, color,ld->mFontColor2, action, SkillIndices[i]); } ld->mItems.Push(li); GC::WriteBarrier(*desc, li); y += ld->mLinespacing; } - if (AllEpisodes[gs->Episode].mNoSkill || AllSkills.Size() == 1) + if (AllEpisodes[gs->Episode].mNoSkill || MenuSkills.Size() == 1) { - ld->mAutoselect = firstitem + M_GetDefaultSkill(); + ld->mAutoselect = firstitem + defindex; } else { @@ -1443,7 +1475,7 @@ fail: MenuDescriptors[NAME_Skillmenu] = od; od->mMenuName = NAME_Skillmenu; od->mTitle = "$MNU_CHOOSESKILL"; - od->mSelectedItem = 0; + od->mSelectedItem = defindex; od->mScrollPos = 0; od->mClass = nullptr; od->mPosition = -15; @@ -1457,9 +1489,9 @@ fail: od = static_cast(*desc); od->mItems.Clear(); } - for(unsigned int i = 0; i < AllSkills.Size(); i++) + for(unsigned int i = 0; i < MenuSkills.Size(); i++) { - FSkillInfo &skill = AllSkills[i]; + FSkillInfo &skill = *MenuSkills[i]; DMenuItemBase *li; // Using a different name for skills that must be confirmed makes handling this easier. const char *action = (skill.MustConfirm && !AllEpisodes[gs->Episode].mNoSkill) ? @@ -1470,29 +1502,13 @@ fail: { pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass); } - li = CreateOptionMenuItemSubmenu(pItemText? *pItemText : skill.MenuName, action, i); + li = CreateOptionMenuItemSubmenu(pItemText? *pItemText : skill.MenuName, action, SkillIndices[i]); od->mItems.Push(li); GC::WriteBarrier(od, li); if (!done) { done = true; - od->mSelectedItem = M_GetDefaultSkill(); + od->mSelectedItem = defindex; } } } - -//============================================================================= -// -// Returns the default skill level. -// -//============================================================================= - -int M_GetDefaultSkill() -{ - int defskill = DefaultSkill; - if ((unsigned int)defskill >= AllSkills.Size()) - { - defskill = (AllSkills.Size() - 1) / 2; - } - return defskill; -} diff --git a/src/namedef.h b/src/namedef.h index 9cd0a19ec..28de56818 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -46,6 +46,7 @@ xx(Shadow) xx(Subtract) xx(Subtractive) xx(FillColor) +xx(HealthFactor) // Healingradius types xx(Mana) @@ -773,6 +774,7 @@ xx(BuiltinGetDefault) xx(BuiltinClassCast) xx(BuiltinFormat) xx(Damage) +xx(Noattack) // basic type names xx(Default) diff --git a/src/nodebuild.cpp b/src/nodebuild.cpp index 6b17c9629..30c44b69c 100644 --- a/src/nodebuild.cpp +++ b/src/nodebuild.cpp @@ -818,41 +818,57 @@ void FNodeBuilder::SplitSegs (DWORD set, node_t &node, DWORD splitseg, DWORD &ou newvert.y += fixed_t(frac * double(Vertices[seg->v2].y - newvert.y)); vertnum = VertexMap->SelectVertexClose (newvert); - if (vertnum == (unsigned int)seg->v1 || vertnum == (unsigned int)seg->v2) + if (vertnum != (unsigned int)seg->v1 && vertnum != (unsigned int)seg->v2) { - Printf("SelectVertexClose selected endpoint of seg %u\n", set); + seg2 = SplitSeg(set, vertnum, sidev[0]); + + Segs[seg2].next = outset0; + outset0 = seg2; + Segs[set].next = outset1; + outset1 = set; + _count0++; + _count1++; + + // Also split the seg on the back side + if (Segs[set].partner != DWORD_MAX) + { + int partner1 = Segs[set].partner; + int partner2 = SplitSeg(partner1, vertnum, sidev[1]); + // The newly created seg stays in the same set as the + // back seg because it has not been considered for splitting + // yet. If it had been, then the front seg would have already + // been split, and we would not be in this default case. + // Moreover, the back seg may not even be in the set being + // split, so we must not move its pieces into the out sets. + Segs[partner1].next = partner2; + Segs[partner2].partner = seg2; + Segs[seg2].partner = partner2; + } + + if (GLNodes) + { + AddIntersection(node, vertnum); + } } - - seg2 = SplitSeg (set, vertnum, sidev[0]); - - Segs[seg2].next = outset0; - outset0 = seg2; - Segs[set].next = outset1; - outset1 = set; - _count0++; - _count1++; - - // Also split the seg on the back side - if (Segs[set].partner != DWORD_MAX) + else { - int partner1 = Segs[set].partner; - int partner2 = SplitSeg (partner1, vertnum, sidev[1]); - // The newly created seg stays in the same set as the - // back seg because it has not been considered for splitting - // yet. If it had been, then the front seg would have already - // been split, and we would not be in this default case. - // Moreover, the back seg may not even be in the set being - // split, so we must not move its pieces into the out sets. - Segs[partner1].next = partner2; - Segs[partner2].partner = seg2; - Segs[seg2].partner = partner2; + // all that matters here is to prevent a crash so we must make sure that we do not end up with all segs being sorted to the same side - even if this may not be correct. + // But if we do not do that this code would not be able to move on. Just discarding the seg is also not an option because it won't guarantee that we achieve an actual split. + if (_count0 == 0) + { + side = 0; + seg->next = outset0; + outset0 = set; + _count0++; + } + else + { + side = 1; + seg->next = outset1; + outset1 = set; + _count1++; + } } - - if (GLNodes) - { - AddIntersection (node, vertnum); - } - break; } if (side >= 0 && GLNodes) diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp index 2fb040a93..5841e6b99 100644 --- a/src/p_enemy.cpp +++ b/src/p_enemy.cpp @@ -349,7 +349,7 @@ bool AActor::CheckMeleeRange () double dist; - if (!pl) + if (!pl || (Sector->Flags & SECF_NOATTACK)) return false; dist = Distance2D (pl); @@ -398,7 +398,7 @@ bool P_CheckMeleeRange2 (AActor *actor) double dist; - if (!actor->target) + if (!actor->target || (actor->Sector->Flags & SECF_NOATTACK)) { return false; } @@ -445,6 +445,8 @@ bool P_CheckMissileRange (AActor *actor) { double dist; + if ((actor->Sector->Flags & SECF_NOATTACK)) return false; + if (!P_CheckSight (actor, actor->target, SF_SEEPASTBLOCKEVERYTHING)) return false; diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index 7699fe376..38d9cd375 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -216,15 +216,20 @@ void ClientObituary (AActor *self, AActor *inflictor, AActor *attacker, int dmgf } } - switch (mod) + FString obit = DamageTypeDefinition::GetObituary(mod); + if (obit.IsNotEmpty()) messagename = obit; + else { - case NAME_Suicide: messagename = "OB_SUICIDE"; break; - case NAME_Falling: messagename = "OB_FALLING"; break; - case NAME_Crush: messagename = "OB_CRUSH"; break; - case NAME_Exit: messagename = "OB_EXIT"; break; - case NAME_Drowning: messagename = "OB_WATER"; break; - case NAME_Slime: messagename = "OB_SLIME"; break; - case NAME_Fire: if (attacker == NULL) messagename = "OB_LAVA"; break; + switch (mod) + { + case NAME_Suicide: messagename = "OB_SUICIDE"; break; + case NAME_Falling: messagename = "OB_FALLING"; break; + case NAME_Crush: messagename = "OB_CRUSH"; break; + case NAME_Exit: messagename = "OB_EXIT"; break; + case NAME_Drowning: messagename = "OB_WATER"; break; + case NAME_Slime: messagename = "OB_SLIME"; break; + case NAME_Fire: if (attacker == NULL) messagename = "OB_LAVA"; break; + } } // Check for being killed by a voodoo doll. diff --git a/src/p_linkedsectors.cpp b/src/p_linkedsectors.cpp index 0dd29c10e..7ac614f2e 100644 --- a/src/p_linkedsectors.cpp +++ b/src/p_linkedsectors.cpp @@ -329,11 +329,13 @@ bool P_AddSectorLinks(sector_t *control, int tag, INTBOOL ceiling, int movetype) FSectorTagIterator itr(tag); while ((sec = itr.Next()) >= 0) { - // Don't attach to self! - if (control != &level.sectors[sec]) + // Don't attach to self (but allow attaching to this sector's oposite plane. + if (control == &level.sectors[sec]) { - AddSingleSector(scrollplane, &level.sectors[sec], movetype); + if (ceiling == sector_t::floor && movetype & LINK_FLOOR) continue; + if (ceiling == sector_t::ceiling && movetype & LINK_CEILING) continue; } + AddSingleSector(scrollplane, &level.sectors[sec], movetype); } } else diff --git a/src/p_lnspec.cpp b/src/p_lnspec.cpp index 394203c75..ee70dcee5 100644 --- a/src/p_lnspec.cpp +++ b/src/p_lnspec.cpp @@ -3177,7 +3177,7 @@ FUNC(LS_ClearForceField) } FUNC(LS_GlassBreak) -// GlassBreak (bNoJunk) +// GlassBreak (bNoJunk, junkID) { bool switched; bool quest1, quest2; @@ -3197,7 +3197,6 @@ FUNC(LS_GlassBreak) { if (!arg0) { // Break some glass - AActor *glass; DVector2 linemid((ln->v1->fX() + ln->v2->fX()) / 2, (ln->v1->fY() + ln->v2->fY()) / 2); @@ -3209,18 +3208,32 @@ FUNC(LS_GlassBreak) y += (ln->frontsector->centerspot.y - y) / 5; */ + auto type = SpawnableThings.CheckKey(arg1); for (int i = 0; i < 7; ++i) { - glass = Spawn("GlassJunk", DVector3(linemid, ONFLOORZ), ALLOW_REPLACE); - - glass->AddZ(24.); - glass->SetState (glass->SpawnState + (pr_glass() % glass->health)); - - glass->Angles.Yaw = pr_glass() * (360 / 256.); - glass->VelFromAngle(pr_glass() & 3); - glass->Vel.Z = (pr_glass() & 7); - // [RH] Let the shards stick around longer than they did in Strife. - glass->tics += pr_glass(); + AActor *glass = nullptr; + if (arg1 > 0) + { + if (type != nullptr) + { + glass = Spawn(*type, DVector3(linemid, ONFLOORZ), ALLOW_REPLACE); + glass->AddZ(24.); + } + } + else + { + glass = Spawn("GlassJunk", DVector3(linemid, ONFLOORZ), ALLOW_REPLACE); + glass->AddZ(24.); + glass->SetState(glass->SpawnState + (pr_glass() % glass->health)); + } + if (glass != nullptr) + { + glass->Angles.Yaw = pr_glass() * (360 / 256.); + glass->VelFromAngle(pr_glass() & 3); + glass->Vel.Z = (pr_glass() & 7); + // [RH] Let the shards stick around longer than they did in Strife. + glass->tics += pr_glass(); + } } } if (quest1 || quest2) diff --git a/src/p_map.cpp b/src/p_map.cpp index 58aaefc17..9b3a257ec 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -3537,6 +3537,11 @@ bool P_BounceActor(AActor *mo, AActor *BlockingMobj, bool ontop) if ((mo->flags & MF_MISSILE) && (mo->flags2 & MF2_RIP) && BlockingMobj->flags & MF_SHOOTABLE) return true; + if (BlockingMobj->flags & MF_SHOOTABLE && mo->BounceFlags & BOUNCE_NotOnShootables) + { + mo->bouncecount = 1; // let it explode now. + } + if (mo->bouncecount>0 && --mo->bouncecount == 0) { if (mo->flags & MF_MISSILE) diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index d14c69aad..d6da38961 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -902,7 +902,7 @@ bool AActor::TakeInventory(PClassActor *itemclass, int amount, bool fromdecorate // Do not take ammo if the "no take infinite/take as ammo depletion" flag is set // and infinite ammo is on if (notakeinfinite && - ((dmflags & DF_INFINITE_AMMO) || (player && FindInventory(NAME_PowerInfiniteAmmo))) && item->IsKindOf(NAME_Ammo)) + ((dmflags & DF_INFINITE_AMMO) || (player && FindInventory(NAME_PowerInfiniteAmmo, true))) && item->IsKindOf(NAME_Ammo)) { // Nothing to do here, except maybe res = false;? Would it make sense? result = false; @@ -4437,7 +4437,8 @@ void AActor::Tick () return; // freed itself } } - else + + if (tics == -1 || state->GetCanRaise()) { int respawn_monsters = G_SkillProperty(SKILLP_Respawn); // check for nightmare respawn diff --git a/src/p_udmf.cpp b/src/p_udmf.cpp index 0ba75fc4c..b9300f654 100644 --- a/src/p_udmf.cpp +++ b/src/p_udmf.cpp @@ -515,6 +515,7 @@ public: FString arg0str, arg1str; memset(th, 0, sizeof(*th)); + double healthfactor = 1; th->Gravity = 1; th->RenderStyle = STYLE_Count; th->Alpha = -1; @@ -738,38 +739,52 @@ public: break; case NAME_Alpha: + CHECK_N(Zd | Zdt) th->Alpha = CheckFloat(key); break; case NAME_FillColor: + CHECK_N(Zd | Zdt) th->fillcolor = CheckInt(key); break; case NAME_Health: + CHECK_N(Zd | Zdt) th->health = CheckInt(key); break; + case NAME_HealthFactor: + CHECK_N(Zd | Zdt) + healthfactor = CheckFloat(key); + break; + case NAME_Score: + CHECK_N(Zd | Zdt) th->score = CheckInt(key); break; case NAME_Pitch: + CHECK_N(Zd | Zdt) th->pitch = (short)CheckInt(key); break; case NAME_Roll: + CHECK_N(Zd | Zdt) th->roll = (short)CheckInt(key); break; case NAME_ScaleX: + CHECK_N(Zd | Zdt) th->Scale.X = CheckFloat(key); break; case NAME_ScaleY: + CHECK_N(Zd | Zdt) th->Scale.Y = CheckFloat(key); break; case NAME_Scale: + CHECK_N(Zd | Zdt) th->Scale.X = th->Scale.Y = CheckFloat(key); break; @@ -793,6 +808,7 @@ public: { th->args[1] = -FName(arg1str); } + th->health = int(th->health * healthfactor); // Thing specials are only valid in namespaces with Hexen-type specials // and in ZDoomTranslated - which will use the translator on them. if (namespc == NAME_ZDoomTranslated) @@ -1658,6 +1674,10 @@ public: sec->planes[sector_t::ceiling].GlowHeight = (float)CheckFloat(key); break; + case NAME_Noattack: + Flag(sec->Flags, SECF_NOATTACK, key); + break; + case NAME_MoreIds: // delay parsing of the tag string until parsing of the sector is complete // This ensures that the ID is always the first tag in the list. diff --git a/src/r_defs.h b/src/r_defs.h index 8726abe94..63c615500 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -493,6 +493,7 @@ enum SECF_ENDGODMODE = 256, // getting damaged by this sector ends god mode SECF_ENDLEVEL = 512, // ends level when health goes below 10 SECF_HAZARD = 1024, // Change to Strife's delayed damage handling. + SECF_NOATTACK = 2048, // monsters cannot start attacks in this sector. SECF_WASSECRET = 1 << 30, // a secret that was discovered SECF_SECRET = 1 << 31, // a secret sector diff --git a/src/resourcefiles/file_zip.cpp b/src/resourcefiles/file_zip.cpp index 3ae708823..bd2990dec 100644 --- a/src/resourcefiles/file_zip.cpp +++ b/src/resourcefiles/file_zip.cpp @@ -217,12 +217,67 @@ bool FZipFile::Open(bool quiet) char *dirptr = (char*)directory; FZipLump *lump_p = Lumps; + + // Check if all files have the same prefix so that this can be stripped out. + FString name0; + if (NumLumps > 1) for (DWORD i = 0; i < NumLumps; i++) + { + FZipCentralDirectoryInfo *zip_fh = (FZipCentralDirectoryInfo *)dirptr; + + int len = LittleShort(zip_fh->NameLength); + FString name(dirptr + sizeof(FZipCentralDirectoryInfo), len); + + dirptr += sizeof(FZipCentralDirectoryInfo) + + LittleShort(zip_fh->NameLength) + + LittleShort(zip_fh->ExtraLength) + + LittleShort(zip_fh->CommentLength); + + if (dirptr > ((char*)directory) + dirsize) // This directory entry goes beyond the end of the file. + { + free(directory); + if (!quiet) Printf(TEXTCOLOR_RED "\n%s: Central directory corrupted.", Filename); + return false; + } + + name.ToLower(); + if (i == 0) + { + // check for special names, if one of these gets found this must be treated as a normal zip. + bool isspecial = !name.Compare("flats/") || + name.IndexOf("/") < 0 || + !name.Compare("textures/") || + !name.Compare("hires/") || + !name.Compare("sprites/") || + !name.Compare("voxels/") || + !name.Compare("colormaps/") || + !name.Compare("acs/") || + !name.Compare("voices/") || + !name.Compare("patches/") || + !name.Compare("graphics/") || + !name.Compare("sounds/") || + !name.Compare("music/"); + if (isspecial) break; + name0 = name; + } + else + { + if (name.IndexOf(name0) != 0) + { + name0 = ""; + break; + } + } + } + + dirptr = (char*)directory; + lump_p = Lumps; for (DWORD i = 0; i < NumLumps; i++) { FZipCentralDirectoryInfo *zip_fh = (FZipCentralDirectoryInfo *)dirptr; int len = LittleShort(zip_fh->NameLength); FString name(dirptr + sizeof(FZipCentralDirectoryInfo), len); + if (name0.IsNotEmpty()) name = name.Mid(name0.Len()); dirptr += sizeof(FZipCentralDirectoryInfo) + LittleShort(zip_fh->NameLength) + LittleShort(zip_fh->ExtraLength) + diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index 3a76002fa..265dc079b 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -339,6 +339,7 @@ static FFlagDef ActorFlagDefs[]= DEFINE_FLAG(RF, XFLIP, AActor, renderflags), DEFINE_FLAG(RF, YFLIP, AActor, renderflags), DEFINE_FLAG(RF, INTERPOLATEANGLES, AActor, renderflags), + DEFINE_FLAG(RF, DONTINTERPOLATE, AActor, renderflags), // Bounce flags DEFINE_FLAG2(BOUNCE_Walls, BOUNCEONWALLS, AActor, BounceFlags), @@ -355,6 +356,7 @@ static FFlagDef ActorFlagDefs[]= DEFINE_FLAG2(BOUNCE_MBF, MBFBOUNCER, AActor, BounceFlags), DEFINE_FLAG2(BOUNCE_AutoOffFloorOnly, BOUNCEAUTOOFFFLOORONLY, AActor, BounceFlags), DEFINE_FLAG2(BOUNCE_UseBounceState, USEBOUNCESTATE, AActor, BounceFlags), + DEFINE_FLAG2(BOUNCE_NotOnShootables, DONTBOUNCEONSHOOTABLES, AActor, BounceFlags), }; // These won't be accessible through bitfield variables diff --git a/src/wi_stuff.cpp b/src/wi_stuff.cpp index 736384e62..5542130e6 100644 --- a/src/wi_stuff.cpp +++ b/src/wi_stuff.cpp @@ -1155,7 +1155,8 @@ public: void WI_initShowNextLoc () { - if (wbs->next_ep == -1) + auto info = FindLevelInfo(wbs->next, false); + if (info == nullptr) { // Last map in episode - there is no next location! WI_End(); diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 5bb3ace70..75ef5eb51 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -1777,6 +1777,7 @@ MOUSEMNU_LOOKSTRAFE = "Lookstrafe"; JOYMNU_CONFIG = "CONFIGURE CONTROLLER"; JOYMNU_OPTIONS = "CONTROLLER OPTIONS"; +JOYMNU_NOMENU = "Block controller input in menu"; // Player Setup Menu MNU_PLAYERSETUP = "PLAYER SETUP"; @@ -1900,7 +1901,7 @@ MISCMNU_DEHLOAD = "Load *.deh/*.bex lumps"; MISCMNU_CACHENODES = "Cache nodes"; MISCMNU_CACHETIME = "Time threshold for node caching"; MISCMNU_CLEARNODECACHE = "Clear node cache"; - +MISCMNU_INTERSCROLL = "Allow skipping of intermission scrollers"; // Automap Options AUTOMAPMNU_TITLE = "AUTOMAP OPTIONS"; AUTOMAPMNU_COLORSET = "Map color set"; diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index df19017c9..63900301e 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -563,6 +563,7 @@ OptionMenu "JoystickOptionsDefaults" { Title "$JOYMNU_OPTIONS" Option "$JOYMNU_ENABLE", "use_joystick", "YesNo" + Option "$JOYMNU_NOMENU", "m_blockcontrollers", "YesNo" IfOption(Windows) { Option "$JOYMNU_DINPUT", "joy_dinput", "YesNo" @@ -972,6 +973,7 @@ OptionMenu "MiscOptions" Option "$MISCMNU_SAVELOADCONFIRMATION", "saveloadconfirmation", "OnOff" Slider "$MISCMNU_AUTOSAVECOUNT", "autosavecount", 1, 20, 1, 0 Option "$MISCMNU_DEHLOAD", "dehload", "dehopt" + Option "$MISCMNU_INTERSCROLL", "nointerscrollabort", "OffOn" StaticText " " Option "$MISCMNU_CACHENODES", "gl_cachenodes", "OnOff" Slider "$MISCMNU_CACHETIME", "gl_cachetime", 0.0, 2.0, 0.1 diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 6b4a76bfa..d22eac800 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -797,6 +797,7 @@ class Actor : Thinker native deprecated native void A_BasicAttack(int meleedamage, sound meleesound, class missiletype, double missileheight); action native bool, Actor A_ThrowGrenade(class itemtype, double zheight = 0, double xyvel = 0, double zvel = 0, bool useammo = true); native void A_Weave(int xspeed, int yspeed, double xdist, double ydist); + native bool A_Morph(class type, int duration = 0, int flags = 0, class enter_flash = null, class exit_flash = null); action native state, bool A_Teleport(statelabel teleportstate = null, class targettype = "BossSpot", class fogtype = "TeleportFog", int flags = 0, double mindist = 128, double maxdist = 0, int ptr = AAPTR_DEFAULT); diff --git a/wadsrc/static/zscript/constants.txt b/wadsrc/static/zscript/constants.txt index 39017b51e..d0daae8ef 100644 --- a/wadsrc/static/zscript/constants.txt +++ b/wadsrc/static/zscript/constants.txt @@ -214,20 +214,23 @@ enum ESelectWeaponFlags // Morph constants enum EMorphFlags { - MRF_ADDSTAMINA = 1, - MRF_FULLHEALTH = 2, - MRF_UNDOBYTOMEOFPOWER = 4, - MRF_UNDOBYCHAOSDEVICE = 8, - MRF_FAILNOTELEFRAG = 16, - MRF_FAILNOLAUGH = 32, - MRF_WHENINVULNERABLE = 64, - MRF_LOSEACTUALWEAPON = 128, - MRF_NEWTIDBEHAVIOUR = 256, - MRF_UNDOBYDEATH = 512, - MRF_UNDOBYDEATHFORCED = 1024, - MRF_UNDOBYDEATHSAVES = 2048, - MRF_UNDOALWAYS = 4096, - MRF_TRANSFERTRANSLATION = 8192, + MRF_OLDEFFECTS = 0x00000000, + MRF_ADDSTAMINA = 0x00000001, + MRF_FULLHEALTH = 0x00000002, + MRF_UNDOBYTOMEOFPOWER = 0x00000004, + MRF_UNDOBYCHAOSDEVICE = 0x00000008, + MRF_FAILNOTELEFRAG = 0x00000010, + MRF_FAILNOLAUGH = 0x00000020, + MRF_WHENINVULNERABLE = 0x00000040, + MRF_LOSEACTUALWEAPON = 0x00000080, + MRF_NEWTIDBEHAVIOUR = 0x00000100, + MRF_UNDOBYDEATH = 0x00000200, + MRF_UNDOBYDEATHFORCED = 0x00000400, + MRF_UNDOBYDEATHSAVES = 0x00000800, + MRF_UNDOBYTIMEOUT = 0x00001000, + MRF_UNDOALWAYS = 0x00002000, + MRF_TRANSFERTRANSLATION = 0x00004000, + MRF_STANDARDUNDOING = MRF_UNDOBYTOMEOFPOWER | MRF_UNDOBYCHAOSDEVICE | MRF_UNDOBYTIMEOUT, }; // Flags for A_RailAttack and A_CustomRailgun