diff --git a/specs/udmf_zdoom.txt b/specs/udmf_zdoom.txt index 797d092efa..553c753603 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 aac1b7161a..233bb8958e 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 b52cb65713..2ed0047405 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 fb7cac3166..10ff7d0fc4 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 d714eefce4..a0780fb92b 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 03fa70dcdd..06a91259a5 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 1ae3c4b5ef..9259561b09 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 dd772a7451..25108c0dfd 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 93e4d5697a..36ae3ad8f4 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 90d847fcc8..81912150b5 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 05785f90b6..50db1e4b63 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 27532e7e98..5dddef1051 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 71c0ff6453..8b0bf35e97 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 6d7bdc9271..0b6b55ea26 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 bc95198f27..dc4fff1a71 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 9cd0a19ecb..28de568183 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 6b17c9629d..30c44b69c0 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 2fb040a933..5841e6b994 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 7699fe376d..38d9cd3751 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 0dd29c10e2..7ac614f2ef 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 394203c75b..ee70dcee5d 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 58aaefc17c..9b3a257ec8 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 d14c69aadf..d6da38961e 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 0ba75fc4c7..b9300f6543 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 8726abe944..63c6155004 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 3ae7088230..bd2990dec3 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 3a76002faa..265dc079b4 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 736384e62b..5542130e6c 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 5bb3ace700..75ef5eb512 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 df19017c90..63900301e5 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 6b4a76bfa6..d22eac8000 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 39017b51e6..d0daae8efe 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