diff --git a/src/am_map.cpp b/src/am_map.cpp index fdbacd34f..045d48589 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -360,6 +360,20 @@ static FColorCVar *cv_overlay[] = { &am_ovsecretsectorcolor }; +CCMD(am_restorecolors) +{ + for (unsigned i = 0; i < countof(cv_standard); i++) + { + cv_standard[i]->ResetToDefault(); + } + for (unsigned i = 0; i < countof(cv_overlay); i++) + { + cv_overlay[i]->ResetToDefault(); + } +} + + + #define NOT_USED 1,0,0 // use almost black as indicator for an unused color static unsigned char DoomColors[]= { diff --git a/src/c_cmds.cpp b/src/c_cmds.cpp index 8a9b5311b..803f2eae4 100644 --- a/src/c_cmds.cpp +++ b/src/c_cmds.cpp @@ -420,7 +420,7 @@ CCMD (take) CCMD (gameversion) { - Printf ("%s @ %s\nCommit %s", GetVersionString(), GetGitTime(), GetGitHash()); + Printf ("%s @ %s\nCommit %s\n", GetVersionString(), GetGitTime(), GetGitHash()); } CCMD (print) diff --git a/src/compatibility.cpp b/src/compatibility.cpp index d60677f64..498fefb2a 100644 --- a/src/compatibility.cpp +++ b/src/compatibility.cpp @@ -145,6 +145,7 @@ static FCompatOption Options[] = { "badangles", COMPATF2_BADANGLES, SLOT_COMPAT2 }, { "floormove", COMPATF2_FLOORMOVE, SLOT_COMPAT2 }, { "soundcutoff", COMPATF2_SOUNDCUTOFF, SLOT_COMPAT2 }, + { "pointonline", COMPATF2_POINTONLINE, SLOT_COMPAT2 }, { NULL, 0, 0 } }; diff --git a/src/d_iwad.cpp b/src/d_iwad.cpp index 3fc365e8c..a8d60e1ce 100644 --- a/src/d_iwad.cpp +++ b/src/d_iwad.cpp @@ -435,6 +435,11 @@ int FIWadManager::IdentifyVersion (TArray &wadfiles, const char *iwad, } } } + TArray gog_paths = I_GetGogPaths(); + for (i = 0; i < gog_paths.Size(); ++i) + { + CheckIWAD (gog_paths[i], &wads[0]); + } TArray steam_path = I_GetSteamPath(); for (i = 0; i < steam_path.Size(); ++i) { diff --git a/src/d_main.cpp b/src/d_main.cpp index dad3e5712..fddb2c86a 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -563,7 +563,7 @@ CUSTOM_CVAR(Int, compatmode, 0, CVAR_ARCHIVE|CVAR_NOINITCALL) COMPATF_TRACE|COMPATF_MISSILECLIP|COMPATF_SOUNDTARGET|COMPATF_NO_PASSMOBJ|COMPATF_LIMITPAIN| COMPATF_DEHHEALTH|COMPATF_INVISIBILITY|COMPATF_CROSSDROPOFF|COMPATF_CORPSEGIBS|COMPATF_HITSCAN| COMPATF_WALLRUN|COMPATF_NOTOSSDROPS|COMPATF_LIGHT|COMPATF_MASKEDMIDTEX; - w = COMPATF2_BADANGLES|COMPATF2_FLOORMOVE; + w = COMPATF2_BADANGLES|COMPATF2_FLOORMOVE|COMPATF2_POINTONLINE; break; case 3: // Boom compat mode @@ -582,6 +582,7 @@ CUSTOM_CVAR(Int, compatmode, 0, CVAR_ARCHIVE|CVAR_NOINITCALL) case 6: // Boom with some added settings to reenable some 'broken' behavior v = COMPATF_TRACE|COMPATF_SOUNDTARGET|COMPATF_BOOMSCROLL|COMPATF_MISSILECLIP|COMPATF_NO_PASSMOBJ| COMPATF_INVISIBILITY|COMPATF_CORPSEGIBS|COMPATF_HITSCAN|COMPATF_WALLRUN|COMPATF_NOTOSSDROPS; + w = COMPATF2_POINTONLINE; break; } @@ -624,6 +625,7 @@ CVAR (Flag, compat_maskedmidtex, compatflags, COMPATF_MASKEDMIDTEX); 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); //========================================================================== // diff --git a/src/d_net.cpp b/src/d_net.cpp index d91eca80d..8ecab7537 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -2082,6 +2082,33 @@ static int KillAll(PClassActor *cls) } return killcount; +} + +static int RemoveClass(const PClass *cls) +{ + AActor *actor; + int removecount = 0; + bool player = false; + TThinkerIterator iterator(cls); + while ((actor = iterator.Next())) + { + if (actor->IsA(cls)) + { + // [MC]Do not remove LIVE players. + if (actor->player != NULL) + { + player = true; + continue; + } + removecount++; + actor->ClearCounters(); + actor->Destroy(); + } + } + if (player) + Printf("Cannot remove live players!\n"); + return removecount; + } // [RH] Execute a special "ticcmd". The type byte should // have already been read, and the stream is positioned @@ -2555,6 +2582,27 @@ void Net_DoCommand (int type, BYTE **stream, int player) } break; + case DEM_REMOVE: + { + char *classname = ReadString(stream); + int removecount = 0; + PClassActor *cls = PClass::FindActor(classname); + if (cls != NULL && cls->IsKindOf(RUNTIME_CLASS(PClassActor))) + { + removecount = RemoveClass(cls); + const PClass *cls_rep = cls->GetReplacement(); + if (cls != cls_rep) + { + removecount += RemoveClass(cls_rep); + } + Printf("Removed %d actors of type %s.\n", removecount, classname); + } + else + { + Printf("%s is not an actor class.\n", classname); + } + } + break; case DEM_CONVREPLY: case DEM_CONVCLOSE: @@ -2680,6 +2728,7 @@ void Net_SkipCommand (int type, BYTE **stream) case DEM_SUMMONFRIEND: case DEM_SUMMONFOE: case DEM_SUMMONMBF: + case DEM_REMOVE: case DEM_SPRAY: case DEM_MORPHEX: case DEM_KILLCLASSCHEAT: diff --git a/src/d_protocol.h b/src/d_protocol.h index 8b6e777d5..73b042470 100644 --- a/src/d_protocol.h +++ b/src/d_protocol.h @@ -164,6 +164,7 @@ enum EDemoCommand DEM_RUNNAMEDSCRIPT, // 65 String: Script name, Byte: Arg count + Always flag; each arg is a 4-byte int DEM_REVERTCAMERA, // 66 DEM_SETSLOTPNUM, // 67 Byte: player number, the rest is the same as DEM_SETSLOT + DEM_REMOVE, // 68 }; // The following are implemented by cht_DoCheat in m_cheat.cpp diff --git a/src/doomdef.h b/src/doomdef.h index c8aa66c17..802591386 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -340,6 +340,7 @@ enum COMPATF2_BADANGLES = 1 << 0, // It is impossible to face directly NSEW. 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() }; // Emulate old bugs for select maps. These are not exposed by a cvar diff --git a/src/g_doomedmap.cpp b/src/g_doomedmap.cpp index 338267deb..b72627c37 100644 --- a/src/g_doomedmap.cpp +++ b/src/g_doomedmap.cpp @@ -135,7 +135,7 @@ CCMD (dumpmapthings) } else { - Printf("%6d none", infos[i]->Key); + Printf("%6d none\n", infos[i]->Key); } } diff --git a/src/g_level.cpp b/src/g_level.cpp index 899f96d4f..15d88ccaf 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -1791,8 +1791,14 @@ void G_ReadSnapshots (PNGHandle *png) DWORD snapver; arc << snapver; - arc << namelen; - arc.Read (mapname, namelen); + if (SaveVersion < 4508) + { + arc << namelen; + arc.Read(mapname, namelen); + mapname[namelen] = 0; + MapName = mapname; + } + else arc << MapName; TheDefaultLevelInfo.snapshotVer = snapver; TheDefaultLevelInfo.snapshot = new FCompressedMemFile; TheDefaultLevelInfo.snapshot->Serialize (arc); diff --git a/src/g_mapinfo.cpp b/src/g_mapinfo.cpp index 6931b269f..0c2de6e05 100644 --- a/src/g_mapinfo.cpp +++ b/src/g_mapinfo.cpp @@ -1333,6 +1333,7 @@ MapFlagHandlers[] = { "compat_badangles", MITYPE_COMPATFLAG, 0, COMPATF2_BADANGLES }, { "compat_floormove", MITYPE_COMPATFLAG, 0, COMPATF2_FLOORMOVE }, { "compat_soundcutoff", MITYPE_COMPATFLAG, 0, COMPATF2_SOUNDCUTOFF }, + { "compat_pointonline", MITYPE_COMPATFLAG, 0, COMPATF2_POINTONLINE }, { "cd_start_track", MITYPE_EATNEXT, 0, 0 }, { "cd_end1_track", MITYPE_EATNEXT, 0, 0 }, { "cd_end2_track", MITYPE_EATNEXT, 0, 0 }, diff --git a/src/g_shared/a_artifacts.cpp b/src/g_shared/a_artifacts.cpp index 0dfde2b4e..26ff15bec 100644 --- a/src/g_shared/a_artifacts.cpp +++ b/src/g_shared/a_artifacts.cpp @@ -72,7 +72,7 @@ bool APowerupGiver::Use (bool pickup) power->Strength = Strength; } - power->ItemFlags |= ItemFlags & (IF_ALWAYSPICKUP|IF_ADDITIVETIME); + power->ItemFlags |= ItemFlags & (IF_ALWAYSPICKUP|IF_ADDITIVETIME|IF_NOTELEPORTFREEZE); if (power->CallTryPickup (Owner)) { return true; @@ -342,6 +342,18 @@ void APowerup::OwnerDied () Destroy (); } +//=========================================================================== +// +// AInventory :: GetNoTeleportFreeze +// +//=========================================================================== + +bool APowerup::GetNoTeleportFreeze () +{ + if (ItemFlags & IF_NOTELEPORTFREEZE) return true; + return Super::GetNoTeleportFreeze(); +} + // Invulnerability Powerup --------------------------------------------------- IMPLEMENT_CLASS (APowerInvulnerable) diff --git a/src/g_shared/a_artifacts.h b/src/g_shared/a_artifacts.h index 3f51f975d..5881fc52f 100644 --- a/src/g_shared/a_artifacts.h +++ b/src/g_shared/a_artifacts.h @@ -18,6 +18,7 @@ public: virtual AInventory *CreateTossable (); virtual void Serialize (FArchive &arc); virtual void OwnerDied (); + virtual bool GetNoTeleportFreeze(); virtual PalEntry GetBlend (); virtual bool DrawPowerup (int x, int y); diff --git a/src/g_shared/a_pickups.cpp b/src/g_shared/a_pickups.cpp index 0019c8aca..b25f98130 100644 --- a/src/g_shared/a_pickups.cpp +++ b/src/g_shared/a_pickups.cpp @@ -896,6 +896,25 @@ fixed_t AInventory::GetSpeedFactor () } } +//=========================================================================== +// +// AInventory :: GetNoTeleportFreeze +// +//=========================================================================== + +bool AInventory::GetNoTeleportFreeze () +{ + // do not check the flag here because it's only active when used on PowerUps, not on PowerupGivers. + if (Inventory != NULL) + { + return Inventory->GetNoTeleportFreeze(); + } + else + { + return false; + } +} + //=========================================================================== // // AInventory :: AlterWeaponSprite diff --git a/src/g_shared/a_pickups.h b/src/g_shared/a_pickups.h index 7bd94f9fb..37eb51939 100644 --- a/src/g_shared/a_pickups.h +++ b/src/g_shared/a_pickups.h @@ -128,6 +128,7 @@ enum IF_TOSSED = 1<<22, // Was spawned by P_DropItem (i.e. as a monster drop) IF_ALWAYSRESPAWN = 1<<23, // Always respawn, regardless of dmflag IF_TRANSFER = 1<<24, // All inventory items that the inventory item contains is also transfered to the pickuper + IF_NOTELEPORTFREEZE = 1<<25, // does not 'freeze' the player right after teleporting. }; @@ -205,6 +206,7 @@ public: virtual void AbsorbDamage (int damage, FName damageType, int &newdamage); virtual void ModifyDamage (int damage, FName damageType, int &newdamage, bool passive); virtual fixed_t GetSpeedFactor(); + virtual bool GetNoTeleportFreeze(); virtual int AlterWeaponSprite (visstyle_t *vis); virtual PalEntry GetBlend (); diff --git a/src/menu/joystickmenu.cpp b/src/menu/joystickmenu.cpp index a7d950ed5..676bc1767 100644 --- a/src/menu/joystickmenu.cpp +++ b/src/menu/joystickmenu.cpp @@ -345,8 +345,8 @@ void UpdateJoystickMenu(IJoystickConfig *selected) for(unsigned i=0;imItems.Size();i++) { delete opt->mItems[i]; - opt->mItems.Clear(); } + opt->mItems.Clear(); int i; int itemnum = -1; diff --git a/src/menu/optionmenuitems.h b/src/menu/optionmenuitems.h index 5bc015369..e87e78483 100644 --- a/src/menu/optionmenuitems.h +++ b/src/menu/optionmenuitems.h @@ -1145,18 +1145,3 @@ private: float mMaximum; float mStep; }; -#ifndef NO_IMP -CCMD(am_restorecolors) -{ - if (DMenu::CurrentMenu != NULL && DMenu::CurrentMenu->IsKindOf(RUNTIME_CLASS(DOptionMenu))) - { - DOptionMenu *m = (DOptionMenu*)DMenu::CurrentMenu; - const FOptionMenuDescriptor *desc = m->GetDescriptor(); - // Find the color cvars by scanning the MapColors menu. - for (unsigned i = 0; i < desc->mItems.Size(); ++i) - { - desc->mItems[i]->SetValue(FOptionMenuItemColorPicker::CPF_RESET, 0); - } - } -} -#endif diff --git a/src/oplsynth/nukedopl3.cpp b/src/oplsynth/nukedopl3.cpp index 4d143f4a5..c99c5c55b 100644 --- a/src/oplsynth/nukedopl3.cpp +++ b/src/oplsynth/nukedopl3.cpp @@ -1,5 +1,5 @@ /* -* Copyright (C) 2013-2014 Nuke.YKT +* Copyright (C) 2013-2015 Nuke.YKT(Alexey Khokholov) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -27,53 +27,31 @@ OPL2 ROMs. */ -//version 1.5 +//version 1.6 /* Changelog: v1.1: - Vibrato's sign fix + Vibrato's sign fix. v1.2: - Operator key fix - Corrected 4-operator mode - Corrected rhythm mode - Some small fixes - v1.2.1: - Small envelope generator fix - Removed EX_Get function(not used) - v1.3: - Complete rewrite - (Not released) - v1.4: - New envelope and waveform generator + Operator key fix. + Corrected 4-operator mode. + Corrected rhythm mode. + Some small fixes. + v1.2.1: + Small envelope generator fix. + v1.3: + Complete rewrite. + v1.4: + New envelope and waveform generator. Some small fixes. - (Not released) v1.4.1: - Envelope generator rate calculation fix - (Not released) + Envelope generator rate calculation fix. v1.4.2: Version for ZDoom. v1.5: - Optimizations -*/ - - -/* Verified: - Noise generator. - Waveform generator. - Envelope generator increase table. - Tremolo. -*/ - -/* TODO: - Verify: - kslrom[15] value(is it 128?). - Sustain level = 15. - Vibrato, Phase generator. - Rhythm part. - Envelope generator state switching(decay->sustain when egt = 1 and decay->release). - Feedback. - Register write. - 4-operator. + Optimizations. + v1.6: + Improved emulation output. */ #include @@ -216,19 +194,17 @@ envelope_sinfunc envelope_sin[8] = { }; void envelope_gen_off(opl_slot *slott); -void envelope_gen_change(opl_slot *slott); void envelope_gen_attack(opl_slot *slott); void envelope_gen_decay(opl_slot *slott); void envelope_gen_sustain(opl_slot *slott); void envelope_gen_release(opl_slot *slott); -envelope_genfunc envelope_gen[6] = { +envelope_genfunc envelope_gen[5] = { envelope_gen_off, envelope_gen_attack, envelope_gen_decay, envelope_gen_sustain, - envelope_gen_release, - envelope_gen_change + envelope_gen_release }; enum envelope_gen_num { @@ -252,7 +228,7 @@ Bit8u envelope_calc_rate(opl_slot *slot, Bit8u reg_rate) { } void envelope_update_ksl(opl_slot *slot) { - Bit16s ksl = (kslrom[slot->channel->f_num >> 6] << 1) - ((slot->channel->block ^ 0x07) << 5) - 0x20; + Bit16s ksl = (kslrom[slot->channel->f_num >> 6] << 2) - ((0x08 - slot->channel->block) << 5); if (ksl < 0) { ksl = 0; } @@ -281,28 +257,25 @@ void envelope_gen_off(opl_slot *slot) { slot->eg_rout = 0x1ff; } -void envelope_gen_change(opl_slot *slot) { - slot->eg_gen = slot->eg_gennext; - envelope_update_rate(slot); -} - void envelope_gen_attack(opl_slot *slot) { + if (slot->eg_rout == 0x00) { + slot->eg_gen = envelope_gen_num_decay; + envelope_update_rate(slot); + return; + } slot->eg_rout += ((~slot->eg_rout) *slot->eg_inc) >> 3; if (slot->eg_rout < 0x00) { slot->eg_rout = 0x00; } - if (!slot->eg_rout) { - slot->eg_gen = envelope_gen_num_change; - slot->eg_gennext = envelope_gen_num_decay; - } } void envelope_gen_decay(opl_slot *slot) { - slot->eg_rout += slot->eg_inc; if (slot->eg_rout >= slot->reg_sl << 4) { - slot->eg_gen = envelope_gen_num_change; - slot->eg_gennext = envelope_gen_num_sustain; + slot->eg_gen = envelope_gen_num_sustain; + envelope_update_rate(slot); + return; } + slot->eg_rout += slot->eg_inc; } void envelope_gen_sustain(opl_slot *slot) { @@ -312,11 +285,13 @@ void envelope_gen_sustain(opl_slot *slot) { } void envelope_gen_release(opl_slot *slot) { - slot->eg_rout += slot->eg_inc; if (slot->eg_rout >= 0x1ff) { - slot->eg_gen = envelope_gen_num_change; - slot->eg_gennext = envelope_gen_num_off; + slot->eg_gen = envelope_gen_num_off; + slot->eg_rout = 0x1ff; + envelope_update_rate(slot); + return; } + slot->eg_rout += slot->eg_inc; } void envelope_calc(opl_slot *slot) { @@ -324,10 +299,7 @@ void envelope_calc(opl_slot *slot) { rate_h = slot->eg_rate >> 2; rate_l = slot->eg_rate & 3; Bit8u inc = 0; - if (slot->eg_gen == envelope_gen_num_attack && rate_h == 0x0f) { - inc = 8; - } - else if (eg_incsh[rate_h] > 0) { + if (eg_incsh[rate_h] > 0) { if ((slot->chip->timer & ((1 << eg_incsh[rate_h]) - 1)) == 0) { inc = eg_incstep[eg_incdesc[rate_h]][rate_l][((slot->chip->timer) >> eg_incsh[rate_h]) & 0x07]; } @@ -336,14 +308,19 @@ void envelope_calc(opl_slot *slot) { inc = eg_incstep[eg_incdesc[rate_h]][rate_l][slot->chip->timer & 0x07] << (-eg_incsh[rate_h]); } slot->eg_inc = inc; - envelope_gen[slot->eg_gen](slot); slot->eg_out = slot->eg_rout + (slot->reg_tl << 2) + (slot->eg_ksl >> kslshift[slot->reg_ksl]) + *slot->trem; + envelope_gen[slot->eg_gen](slot); } void eg_keyon(opl_slot *slot, Bit8u type) { if (!slot->key) { - slot->eg_gen = envelope_gen_num_change; - slot->eg_gennext = envelope_gen_num_attack; + slot->eg_gen = envelope_gen_num_attack; + envelope_update_rate(slot); + if ((slot->eg_rate >> 2) == 0x0f) { + slot->eg_gen = envelope_gen_num_decay; + envelope_update_rate(slot); + slot->eg_rout = 0x00; + } slot->pg_phase = 0x00; } slot->key |= type; @@ -353,8 +330,8 @@ void eg_keyoff(opl_slot *slot, Bit8u type) { if (slot->key) { slot->key &= (~type); if (!slot->key) { - slot->eg_gen = envelope_gen_num_change; - slot->eg_gennext = envelope_gen_num_release; + slot->eg_gen = envelope_gen_num_release; + envelope_update_rate(slot); } } } @@ -366,7 +343,7 @@ void eg_keyoff(opl_slot *slot, Bit8u type) { void pg_generate(opl_slot *slot) { Bit16u f_num = slot->channel->f_num; if (slot->reg_vib) { - Bit8u f_num_high = f_num >> (7 + vib_table[(slot->chip->timer >> 10)&0x07] + (0x01 - slot->chip->dvb)); + Bit8u f_num_high = f_num >> (7 + vib_table[(slot->chip->timer >> 10) & 0x07] + (0x01 - slot->chip->dvb)); f_num += f_num_high * vibsgn_table[(slot->chip->timer >> 10) & 0x07]; } slot->pg_phase += (((f_num << slot->channel->block) >> 1) * mt[slot->reg_mult]) >> 1; @@ -387,7 +364,7 @@ void n_generate(opl_chip *chip) { // Slot // -void slot_write20(opl_slot *slot,Bit8u data) { +void slot_write20(opl_slot *slot, Bit8u data) { if ((data >> 7) & 0x01) { slot->trem = &slot->chip->tremval; } @@ -434,14 +411,14 @@ void slot_generatephase(opl_slot *slot, Bit16u phase) { } void slot_generate(opl_slot *slot) { - slot->out = envelope_sin[slot->reg_wf]((slot->pg_phase >> 9) + (*slot->mod), slot->eg_out); + slot->out = envelope_sin[slot->reg_wf]((Bit16u)(slot->pg_phase >> 9) + (*slot->mod), slot->eg_out); } void slot_generatezm(opl_slot *slot) { - slot->out = envelope_sin[slot->reg_wf]((slot->pg_phase >> 9), slot->eg_out); + slot->out = envelope_sin[slot->reg_wf]((Bit16u)(slot->pg_phase >> 9), slot->eg_out); } -void slot_calgfb(opl_slot *slot) { +void slot_calcfb(opl_slot *slot) { slot->prout[1] = slot->prout[0]; slot->prout[0] = slot->out; if (slot->channel->fb != 0x00) { @@ -461,57 +438,61 @@ void chan_setupalg(opl_channel *channel); void chan_updaterhythm(opl_chip *chip, Bit8u data) { chip->rhy = data & 0x3f; if (chip->rhy & 0x20) { - chip->channel[6].out[0] = &chip->slot[13].out; - chip->channel[6].out[1] = &chip->slot[13].out; - chip->channel[6].out[2] = &chip->zeromod; - chip->channel[6].out[3] = &chip->zeromod; - chip->channel[7].out[0] = &chip->slot[14].out; - chip->channel[7].out[1] = &chip->slot[14].out; - chip->channel[7].out[2] = &chip->slot[15].out; - chip->channel[7].out[3] = &chip->slot[15].out; - chip->channel[8].out[0] = &chip->slot[16].out; - chip->channel[8].out[1] = &chip->slot[16].out; - chip->channel[8].out[2] = &chip->slot[17].out; - chip->channel[8].out[3] = &chip->slot[17].out; + opl_channel *channel6 = &chip->channel[6]; + opl_channel *channel7 = &chip->channel[7]; + opl_channel *channel8 = &chip->channel[8]; + channel6->out[0] = &channel6->slots[1]->out; + channel6->out[1] = &channel6->slots[1]->out; + channel6->out[2] = &chip->zeromod; + channel6->out[3] = &chip->zeromod; + channel7->out[0] = &channel7->slots[0]->out; + channel7->out[1] = &channel7->slots[0]->out; + channel7->out[2] = &channel7->slots[1]->out; + channel7->out[3] = &channel7->slots[1]->out; + channel8->out[0] = &channel8->slots[0]->out; + channel8->out[1] = &channel8->slots[0]->out; + channel8->out[2] = &channel8->slots[1]->out; + channel8->out[3] = &channel8->slots[1]->out; for (Bit8u chnum = 6; chnum < 9; chnum++) { chip->channel[chnum].chtype = ch_drum; } + chan_setupalg(channel6); //hh if (chip->rhy & 0x01) { - eg_keyon(&chip->slot[14], egk_drum); + eg_keyon(channel7->slots[0], egk_drum); } else { - eg_keyoff(&chip->slot[14], egk_drum); + eg_keyoff(channel7->slots[0], egk_drum); } //tc if (chip->rhy & 0x02) { - eg_keyon(&chip->slot[17], egk_drum); + eg_keyon(channel8->slots[1], egk_drum); } else { - eg_keyoff(&chip->slot[17], egk_drum); + eg_keyoff(channel8->slots[1], egk_drum); } //tom if (chip->rhy & 0x04) { - eg_keyon(&chip->slot[16], egk_drum); + eg_keyon(channel8->slots[0], egk_drum); } else { - eg_keyoff(&chip->slot[16], egk_drum); + eg_keyoff(channel8->slots[0], egk_drum); } //sd if (chip->rhy & 0x08) { - eg_keyon(&chip->slot[15], egk_drum); + eg_keyon(channel7->slots[1], egk_drum); } else { - eg_keyoff(&chip->slot[15], egk_drum); + eg_keyoff(channel7->slots[1], egk_drum); } //bd if (chip->rhy & 0x10) { - eg_keyon(&chip->slot[12], egk_drum); - eg_keyon(&chip->slot[13], egk_drum); + eg_keyon(channel6->slots[0], egk_drum); + eg_keyon(channel6->slots[1], egk_drum); } else { - eg_keyoff(&chip->slot[12], egk_drum); - eg_keyoff(&chip->slot[13], egk_drum); + eg_keyoff(channel6->slots[0], egk_drum); + eg_keyoff(channel6->slots[1], egk_drum); } } else { @@ -566,6 +547,16 @@ void chan_writeb0(opl_channel *channel, Bit8u data) { void chan_setupalg(opl_channel *channel) { if (channel->chtype == ch_drum) { + switch (channel->alg & 0x01) { + case 0x00: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->slots[0]->out; + break; + case 0x01: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->chip->zeromod; + break; + } return; } if (channel->alg & 0x08) { @@ -672,49 +663,39 @@ void chan_writec0(opl_channel *channel, Bit8u data) { } } -void chan_generaterhythm(opl_chip *chip) { - if (chip->rhy & 0x20) { - opl_channel *channel6 = &chip->channel[6]; - opl_channel *channel7 = &chip->channel[7]; - opl_channel *channel8 = &chip->channel[8]; - slot_generate(channel6->slots[0]); - slot_generate(channel6->slots[1]); - Bit16u phase14 = channel7->slots[0]->pg_phase & 0x3ff; - Bit16u phase17 = channel8->slots[1]->pg_phase & 0x3ff; - Bit16u phase = 0x00; - //hh tc phase bit - Bit16u phasebit = ((phase14 & 0x08) | (((phase14 >> 5) ^ phase14) & 0x04) | (((phase17 >> 2) ^ phase17) & 0x08)) ? 0x01 : 0x00; - //hh - phase = (phasebit << 9) | (0x34 << ((phasebit ^ (chip->noise & 0x01) << 1))); - slot_generatephase(channel7->slots[0], phase); - //sd - phase = (0x100 << ((phase14 >> 8) & 0x01)) ^ ((chip->noise & 0x01) << 8); - slot_generatephase(channel7->slots[1], phase); - //tt - slot_generatezm(channel8->slots[0]); - //tc - phase = 0x100 | (phasebit << 9); - slot_generatephase(channel8->slots[1], phase); - } +void chan_generaterhythm1(opl_chip *chip) { + opl_channel *channel6 = &chip->channel[6]; + opl_channel *channel7 = &chip->channel[7]; + opl_channel *channel8 = &chip->channel[8]; + slot_generate(channel6->slots[0]); + Bit16u phase14 = (channel7->slots[0]->pg_phase >> 9) & 0x3ff; + Bit16u phase17 = (channel8->slots[1]->pg_phase >> 9) & 0x3ff; + Bit16u phase = 0x00; + //hh tc phase bit + Bit16u phasebit = ((phase14 & 0x08) | (((phase14 >> 5) ^ phase14) & 0x04) | (((phase17 >> 2) ^ phase17) & 0x08)) ? 0x01 : 0x00; + //hh + phase = (phasebit << 9) | (0x34 << ((phasebit ^ (chip->noise & 0x01) << 1))); + slot_generatephase(channel7->slots[0], phase); + //tt + slot_generatezm(channel8->slots[0]); } -void chan_generate(opl_channel *channel) { - if (channel->chtype == ch_drum) { - return; - } - if (channel->alg & 0x08) { - return; - } - if (channel->alg & 0x04) { - slot_generate(channel->pair->slots[0]); - slot_generate(channel->pair->slots[1]); - slot_generate(channel->slots[0]); - slot_generate(channel->slots[1]); - } - else { - slot_generate(channel->slots[0]); - slot_generate(channel->slots[1]); - } +void chan_generaterhythm2(opl_chip *chip) { + opl_channel *channel6 = &chip->channel[6]; + opl_channel *channel7 = &chip->channel[7]; + opl_channel *channel8 = &chip->channel[8]; + slot_generate(channel6->slots[1]); + Bit16u phase14 = (channel7->slots[0]->pg_phase >> 9) & 0x3ff; + Bit16u phase17 = (channel8->slots[1]->pg_phase >> 9) & 0x3ff; + Bit16u phase = 0x00; + //hh tc phase bit + Bit16u phasebit = ((phase14 & 0x08) | (((phase14 >> 5) ^ phase14) & 0x04) | (((phase17 >> 2) ^ phase17) & 0x08)) ? 0x01 : 0x00; + //sd + phase = (0x100 << ((phase14 >> 8) & 0x01)) ^ ((chip->noise & 0x01) << 8); + slot_generatephase(channel7->slots[1], phase); + //tc + phase = 0x100 | (phasebit << 9); + slot_generatephase(channel8->slots[1], phase); } void chan_enable(opl_channel *channel) { @@ -782,21 +763,132 @@ Bit16s limshort(Bit32s a) { return (Bit16s)a; } +void chip_generate(opl_chip *chip, Bit16s *buff) { + buff[1] = limshort(chip->mixbuff[1]); + + for (Bit8u ii = 0; ii < 12; ii++) { + slot_calcfb(&chip->slot[ii]); + pg_generate(&chip->slot[ii]); + envelope_calc(&chip->slot[ii]); + slot_generate(&chip->slot[ii]); + } + + for (Bit8u ii = 12; ii < 15; ii++) { + slot_calcfb(&chip->slot[ii]); + pg_generate(&chip->slot[ii]); + envelope_calc(&chip->slot[ii]); + } + + if (chip->rhy & 0x20) { + chan_generaterhythm1(chip); + } + else { + slot_generate(&chip->slot[12]); + slot_generate(&chip->slot[13]); + slot_generate(&chip->slot[14]); + } + + chip->mixbuff[0] = 0; + for (Bit8u ii = 0; ii < 18; ii++) { + Bit16s accm = 0; + for (Bit8u jj = 0; jj < 4; jj++) { + accm += *chip->channel[ii].out[jj]; + } + if (chip->FullPan) { + chip->mixbuff[0] += (Bit16s)(accm * chip->channel[ii].fcha); + } + else { + chip->mixbuff[0] += (Bit16s)(accm & chip->channel[ii].cha); + } + } + + for (Bit8u ii = 15; ii < 18; ii++) { + slot_calcfb(&chip->slot[ii]); + pg_generate(&chip->slot[ii]); + envelope_calc(&chip->slot[ii]); + } + + if (chip->rhy & 0x20) { + chan_generaterhythm2(chip); + } + else { + slot_generate(&chip->slot[15]); + slot_generate(&chip->slot[16]); + slot_generate(&chip->slot[17]); + } + + buff[0] = limshort(chip->mixbuff[0]); + + for (Bit8u ii = 18; ii < 33; ii++) { + slot_calcfb(&chip->slot[ii]); + pg_generate(&chip->slot[ii]); + envelope_calc(&chip->slot[ii]); + slot_generate(&chip->slot[ii]); + } + + chip->mixbuff[1] = 0; + for (Bit8u ii = 0; ii < 18; ii++) { + Bit16s accm = 0; + for (Bit8u jj = 0; jj < 4; jj++) { + accm += *chip->channel[ii].out[jj]; + } + if (chip->FullPan) { + chip->mixbuff[1] += (Bit16s)(accm * chip->channel[ii].fchb); + } + else { + chip->mixbuff[1] += (Bit16s)(accm & chip->channel[ii].chb); + } + } + + for (Bit8u ii = 33; ii < 36; ii++) { + slot_calcfb(&chip->slot[ii]); + pg_generate(&chip->slot[ii]); + envelope_calc(&chip->slot[ii]); + slot_generate(&chip->slot[ii]); + } + + n_generate(chip); + + if ((chip->timer & 0x3f) == 0x3f) { + if (!chip->tremdir) { + if (chip->tremtval == 105) { + chip->tremtval--; + chip->tremdir = 1; + } + else { + chip->tremtval++; + } + } + else { + if (chip->tremtval == 0) { + chip->tremtval++; + chip->tremdir = 0; + } + else { + chip->tremtval--; + } + } + chip->tremval = (chip->tremtval >> 2) >> ((1 - chip->dam) << 1); + } + + chip->timer++; +} + void NukedOPL3::Reset() { memset(&opl3, 0, sizeof(opl_chip)); for (Bit8u slotnum = 0; slotnum < 36; slotnum++) { - opl3.slot[slotnum].channel = &opl3.channel[slotnum / 2]; opl3.slot[slotnum].chip = &opl3; opl3.slot[slotnum].mod = &opl3.zeromod; opl3.slot[slotnum].eg_rout = 0x1ff; opl3.slot[slotnum].eg_out = 0x1ff; opl3.slot[slotnum].eg_gen = envelope_gen_num_off; - opl3.slot[slotnum].eg_gennext = envelope_gen_num_off; opl3.slot[slotnum].trem = (Bit8u*)&opl3.zeromod; } for (Bit8u channum = 0; channum < 18; channum++) { - opl3.channel[channum].slots[0] = &opl3.slot[2 * channum]; - opl3.channel[channum].slots[1] = &opl3.slot[2 * channum + 1]; + opl3.channel[channum].slots[0] = &opl3.slot[ch_slot[channum]]; + opl3.channel[channum].slots[1] = &opl3.slot[ch_slot[channum] + 3]; + opl3.slot[ch_slot[channum]].channel = &opl3.channel[channum]; + opl3.slot[ch_slot[channum] + 3].channel = &opl3.channel[channum]; if ((channum % 9) < 3) { opl3.channel[channum].pair = &opl3.channel[channum + 3]; } @@ -816,6 +908,8 @@ void NukedOPL3::Reset() { chan_setupalg(&opl3.channel[channum]); } opl3.noise = 0x306600; + opl3.timer = 0; + opl3.FullPan = FullPan; } void NukedOPL3::WriteReg(int reg, int v) { @@ -903,58 +997,11 @@ void NukedOPL3::WriteReg(int reg, int v) { } void NukedOPL3::Update(float* sndptr, int numsamples) { - Bit32s outa, outb; + Bit16s buffer[2]; for (Bit32u i = 0; i < (Bit32u)numsamples; i++) { - outa = 0; - outb = 0; - for (Bit8u ii = 0; ii < 36; ii++) { - slot_calgfb(&opl3.slot[ii]); - } - chan_generaterhythm(&opl3); - for (Bit8u ii = 0; ii < 18; ii++) { - chan_generate(&opl3.channel[ii]); - Bit16s accm = 0; - for (Bit8u jj = 0; jj < 4; jj++) { - accm += *opl3.channel[ii].out[jj]; - } - if (FullPan) { - outa += (Bit16s)(accm * opl3.channel[ii].fcha); - outb += (Bit16s)(accm * opl3.channel[ii].fchb); - } - else { - outa += (Bit16s)(accm & opl3.channel[ii].cha); - outb += (Bit16s)(accm & opl3.channel[ii].chb); - } - } - for (Bit8u ii = 0; ii < 36; ii++) { - envelope_calc(&opl3.slot[ii]); - pg_generate(&opl3.slot[ii]); - } - n_generate(&opl3); - opl3.timer++; - if (!(opl3.timer & 0x3f)) { - if (!opl3.tremdir) { - if (opl3.tremtval == 105) { - opl3.tremtval--; - opl3.tremdir = 1; - } - else { - opl3.tremtval++; - } - } - else { - if (opl3.tremtval == 0) { - opl3.tremtval++; - opl3.tremdir = 0; - } - else { - opl3.tremtval--; - } - } - opl3.tremval = (opl3.tremtval >> 2) >> ((1 - opl3.dam) << 1); - } - *sndptr++ += (float)(outa / 10240.0); - *sndptr++ += (float)(outb / 10240.0); + chip_generate(&opl3, buffer); + *sndptr++ += (float)(buffer[0] / 10240.0); + *sndptr++ += (float)(buffer[1] / 10240.0); } } diff --git a/src/oplsynth/nukedopl3.h b/src/oplsynth/nukedopl3.h index ccf37fe14..849646f5f 100644 --- a/src/oplsynth/nukedopl3.h +++ b/src/oplsynth/nukedopl3.h @@ -1,5 +1,5 @@ /* -* Copyright (C) 2013-2014 Nuke.YKT +* Copyright (C) 2013-2015 Nuke.YKT(Alexey Khokholov) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -27,7 +27,7 @@ OPL2 ROMs. */ -//version 1.5 +//version 1.6 #include "opl.h" #include "muslib.h" @@ -116,7 +116,7 @@ static const Bit8u mt[16] = { 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, // ksl table // -static const Bit8u kslrom[16] = { 0, 64, 80, 90, 96, 102, 106, 110, 112, 116, 118, 120, 122, 124, 126, 127 }; +static const Bit8u kslrom[16] = { 0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64 }; static const Bit8u kslshift[4] = { 8, 1, 2, 0 }; @@ -133,7 +133,7 @@ static const Bit8s vibsgn_table[8] = { 1, 1, 1, 1, -1, -1, -1, -1 }; static const Bit8u eg_incstep[3][4][8] = { { { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 } }, - { { 0, 1, 0, 1, 0, 1, 0, 1 }, { 1, 1, 0, 1, 0, 1, 0, 1 }, { 1, 1, 0, 1, 1, 1, 0, 1 }, { 1, 1, 1, 1, 1, 1, 0, 1 } }, + { { 0, 1, 0, 1, 0, 1, 0, 1 }, { 0, 1, 0, 1, 1, 1, 0, 1 }, { 0, 1, 1, 1, 0, 1, 1, 1 }, { 0, 1, 1, 1, 1, 1, 1, 1 } }, { { 1, 1, 1, 1, 1, 1, 1, 1 }, { 2, 2, 1, 1, 1, 1, 1, 1 }, { 2, 2, 1, 1, 2, 2, 1, 1 }, { 2, 2, 2, 2, 2, 2, 1, 1 } } }; @@ -149,8 +149,8 @@ static const Bit8s eg_incsh[16] = { // address decoding // -static const Bit8s ad_slot[0x20] = { 0, 2, 4, 1, 3, 5, -1, -1, 6, 8, 10, 7, 9, 11, -1, -1, 12, 14, 16, 13, 15, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; - +static const Bit8s ad_slot[0x20] = { 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1, 12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; +static const Bit8u ch_slot[18] = { 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32 }; struct opl_chip; struct opl_slot; @@ -167,7 +167,6 @@ struct opl_slot { Bit16s eg_out; Bit8u eg_inc; Bit8u eg_gen; - Bit8u eg_gennext; Bit8u eg_rate; Bit8u eg_ksl; Bit8u *trem; @@ -217,6 +216,8 @@ struct opl_chip { Bit8u tremdir; Bit32u noise; Bit16s zeromod; + Bit32s mixbuff[2]; + Bit8u FullPan; }; diff --git a/src/p_acs.cpp b/src/p_acs.cpp index ca5601e50..23cd39206 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -75,6 +75,7 @@ #include "actorptrselect.h" #include "farchive.h" #include "decallib.h" +#include "version.h" #include "g_shared/a_pickups.h" @@ -1417,7 +1418,7 @@ FBehavior *FBehavior::StaticLoadModule (int lumpnum, FileReader *fr, int len) else { delete behavior; - Printf(TEXTCOLOR_RED "%s: invalid ACS module", Wads.GetLumpFullName(lumpnum)); + Printf(TEXTCOLOR_RED "%s: invalid ACS module\n", Wads.GetLumpFullName(lumpnum)); return NULL; } } @@ -5854,6 +5855,7 @@ doplaysound: if (funcIndex == ACSF_PlayActorSound) int flags = args[5]; const char *statename = argCount > 6 ? FBehavior::StaticLookupString(args[6]) : ""; bool exact = argCount > 7 ? !!args[7] : false; + fixed_t heightoffset = argCount > 8 ? args[8] : 0; FState *state = argCount > 6 ? activator->GetClass()->FindStateByString(statename, exact) : 0; @@ -5871,7 +5873,7 @@ doplaysound: if (funcIndex == ACSF_PlayActorSound) if (!reference) return false; - if (P_Thing_Warp(activator, reference, xofs, yofs, zofs, angle, flags)) + if (P_Thing_Warp(activator, reference, xofs, yofs, zofs, angle, flags, heightoffset)) { if (state && argCount > 6) { @@ -6023,6 +6025,7 @@ int DLevelScript::RunScript () int sp = 0; int *pc = this->pc; ACSFormat fmt = activeBehavior->GetFormat(); + FBehavior* const savedActiveBehavior = activeBehavior; unsigned int runaway = 0; // used to prevent infinite loops int pcd; FString work; @@ -6056,6 +6059,7 @@ int DLevelScript::RunScript () { default: Printf ("Unknown P-Code %d in %s\n", pcd, ScriptPresentation(script).GetChars()); + activeBehavior = savedActiveBehavior; // fall through case PCD_TERMINATE: DPrintf ("%s finished\n", ScriptPresentation(script).GetChars()); @@ -7262,9 +7266,9 @@ int DLevelScript::RunScript () sp--; break; - case PCD_DROP: case PCD_SETRESULTVALUE: resultValue = STACK(1); + case PCD_DROP: //fall through. sp--; break; @@ -8871,7 +8875,22 @@ scriptwait: break; case PCD_STRLEN: - STACK(1) = SDWORD(strlen(FBehavior::StaticLookupString (STACK(1)))); + { + const char *str = FBehavior::StaticLookupString(STACK(1)); + if (str != NULL) + { + STACK(1) = SDWORD(strlen(str)); + break; + } + + static bool StrlenInvalidPrintedAlready = false; + if (!StrlenInvalidPrintedAlready) + { + Printf(PRINT_BOLD, "Warning: ACS function strlen called with invalid string argument.\n"); + StrlenInvalidPrintedAlready = true; + } + STACK(1) = 0; + } break; case PCD_GETCVAR: @@ -9331,6 +9350,10 @@ scriptwait: } break; + case PCD_CONSOLECOMMAND: + Printf (TEXTCOLOR_RED GAMENAME " doesn't support execution of console commands from scripts\n"); + sp -= 3; + break; } } diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index 3fc7d53f1..10d7080a3 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -1,1838 +1,1855 @@ -// Emacs style mode select -*- C++ -*- -//----------------------------------------------------------------------------- -// -// $Id:$ -// -// Copyright (C) 1993-1996 by id Software, Inc. -// -// This source is available for distribution and/or modification -// only under the terms of the DOOM Source Code License as -// published by id Software. All rights reserved. -// -// The source is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License -// for more details. -// -// $Log:$ -// -// DESCRIPTION: -// Handling interactions (i.e., collisions). -// -//----------------------------------------------------------------------------- - - - - -// Data. -#include "doomdef.h" -#include "gstrings.h" - -#include "doomstat.h" - -#include "m_random.h" -#include "i_system.h" -#include "announcer.h" - -#include "am_map.h" - -#include "c_console.h" -#include "c_dispatch.h" - -#include "p_local.h" - -#include "p_lnspec.h" -#include "p_effect.h" -#include "p_acs.h" - -#include "b_bot.h" //Added by MC: - -#include "ravenshared.h" -#include "a_hexenglobal.h" -#include "a_sharedglobal.h" -#include "a_pickups.h" -#include "gi.h" -#include "templates.h" -#include "sbar.h" -#include "s_sound.h" -#include "g_level.h" -#include "d_net.h" -#include "d_netinf.h" - -static FRandom pr_obituary ("Obituary"); -static FRandom pr_botrespawn ("BotRespawn"); -static FRandom pr_killmobj ("ActorDie"); -FRandom pr_damagemobj ("ActorTakeDamage"); -static FRandom pr_lightning ("LightningDamage"); -static FRandom pr_poison ("PoisonDamage"); -static FRandom pr_switcher ("SwitchTarget"); -static FRandom pr_kickbackdir ("KickbackDir"); - -CVAR (Bool, cl_showsprees, true, CVAR_ARCHIVE) -CVAR (Bool, cl_showmultikills, true, CVAR_ARCHIVE) -EXTERN_CVAR (Bool, show_obituaries) - - -FName MeansOfDeath; -bool FriendlyFire; - -// -// GET STUFF -// - -// -// P_TouchSpecialThing -// -void P_TouchSpecialThing (AActor *special, AActor *toucher) -{ - fixed_t delta = special->z - toucher->z; - - // The pickup is at or above the toucher's feet OR - // The pickup is below the toucher. - if (delta > toucher->height || delta < MIN(-32*FRACUNIT, -special->height)) - { // out of reach - return; - } - - // Dead thing touching. - // Can happen with a sliding player corpse. - if (toucher->health <= 0) - return; - - //Added by MC: Finished with this destination. - if (toucher->player != NULL && toucher->player->Bot != NULL && special == toucher->player->Bot->dest) - { - toucher->player->Bot->prev = toucher->player->Bot->dest; - toucher->player->Bot->dest = NULL; - } - - special->Touch (toucher); -} - - -// [RH] -// SexMessage: Replace parts of strings with gender-specific pronouns -// -// The following expansions are performed: -// %g -> he/she/it -// %h -> him/her/it -// %p -> his/her/its -// %o -> other (victim) -// %k -> killer -// -void SexMessage (const char *from, char *to, int gender, const char *victim, const char *killer) -{ - static const char *genderstuff[3][3] = - { - { "he", "him", "his" }, - { "she", "her", "her" }, - { "it", "it", "its" } - }; - static const int gendershift[3][3] = - { - { 2, 3, 3 }, - { 3, 3, 3 }, - { 2, 2, 3 } - }; - const char *subst = NULL; - - do - { - if (*from != '%') - { - *to++ = *from; - } - else - { - int gendermsg = -1; - - switch (from[1]) - { - case 'g': gendermsg = 0; break; - case 'h': gendermsg = 1; break; - case 'p': gendermsg = 2; break; - case 'o': subst = victim; break; - case 'k': subst = killer; break; - } - if (subst != NULL) - { - size_t len = strlen (subst); - memcpy (to, subst, len); - to += len; - from++; - subst = NULL; - } - else if (gendermsg < 0) - { - *to++ = '%'; - } - else - { - strcpy (to, genderstuff[gender][gendermsg]); - to += gendershift[gender][gendermsg]; - from++; - } - } - } while (*from++); -} - -// [RH] -// ClientObituary: Show a message when a player dies -// -void ClientObituary (AActor *self, AActor *inflictor, AActor *attacker, int dmgflags) -{ - FName mod; - const char *message; - const char *messagename; - char gendermessage[1024]; - bool friendly; - int gender; - - // No obituaries for non-players, voodoo dolls or when not wanted - if (self->player == NULL || self->player->mo != self || !show_obituaries) - return; - - gender = self->player->userinfo.GetGender(); - - // Treat voodoo dolls as unknown deaths - if (inflictor && inflictor->player && inflictor->player->mo != inflictor) - MeansOfDeath = NAME_None; - - if (multiplayer && !deathmatch) - FriendlyFire = true; - - friendly = FriendlyFire; - mod = MeansOfDeath; - message = NULL; - messagename = NULL; - - if (attacker == NULL || attacker->player != NULL) - { - if (mod == NAME_Telefrag) - { - if (AnnounceTelefrag (attacker, self)) - return; - } - else - { - if (AnnounceKill (attacker, self)) - return; - } - } - - 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. - if (inflictor && inflictor->player && inflictor->player->mo != inflictor) - { - messagename = "OB_VOODOO"; - } - - if (messagename != NULL) - message = GStrings(messagename); - - if (attacker != NULL && message == NULL) - { - if (attacker == self) - { - message = GStrings("OB_KILLEDSELF"); - } - else if (attacker->player == NULL) - { - if (mod == NAME_Telefrag) - { - message = GStrings("OB_MONTELEFRAG"); - } - else if (mod == NAME_Melee && attacker->GetClass()->HitObituary.IsNotEmpty()) - { - message = attacker->GetClass()->HitObituary; - } - else if (attacker->GetClass()->Obituary.IsNotEmpty()) - { - message = attacker->GetClass()->Obituary; - } - } - } - - if (message == NULL && attacker != NULL && attacker->player != NULL) - { - if (friendly) - { - attacker->player->fragcount -= 2; - attacker->player->frags[attacker->player - players]++; - self = attacker; - gender = self->player->userinfo.GetGender(); - mysnprintf (gendermessage, countof(gendermessage), "OB_FRIENDLY%c", '1' + (pr_obituary() & 3)); - message = GStrings(gendermessage); - } - else - { - if (mod == NAME_Telefrag) message = GStrings("OB_MPTELEFRAG"); - if (message == NULL) - { - if (inflictor != NULL && inflictor->GetClass()->Obituary.IsNotEmpty()) - { - message = inflictor->GetClass()->Obituary; - } - if (message == NULL && (dmgflags & DMG_PLAYERATTACK) && attacker->player->ReadyWeapon != NULL) - { - message = attacker->player->ReadyWeapon->GetClass()->Obituary; - } - if (message == NULL) - { - switch (mod) - { - case NAME_BFGSplash: messagename = "OB_MPBFG_SPLASH"; break; - case NAME_Railgun: messagename = "OB_RAILGUN"; break; - } - if (messagename != NULL) - message = GStrings(messagename); - } - if (message == NULL) - { - message = attacker->GetClass()->Obituary; - } - } - } - } - else attacker = self; // for the message creation - - if (message != NULL && message[0] == '$') - { - message = GStrings[message+1]; - } - - if (message == NULL) - { - message = GStrings("OB_DEFAULT"); - } - - // [CK] Don't display empty strings - if (message == NULL || strlen(message) <= 0) - return; - - SexMessage (message, gendermessage, gender, - self->player->userinfo.GetName(), attacker->player->userinfo.GetName()); - Printf (PRINT_MEDIUM, "%s\n", gendermessage); -} - - -// -// KillMobj -// -EXTERN_CVAR (Int, fraglimit) - -void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) -{ - // Handle possible unmorph on death - bool wasgibbed = (health < GetGibHealth()); - AActor *realthis = NULL; - int realstyle = 0; - int realhealth = 0; - if (P_MorphedDeath(this, &realthis, &realstyle, &realhealth)) - { - if (!(realstyle & MORPH_UNDOBYDEATHSAVES)) - { - if (wasgibbed) - { - int realgibhealth = realthis->GetGibHealth(); - if (realthis->health >= realgibhealth) - { - realthis->health = realgibhealth -1; // if morphed was gibbed, so must original be (where allowed)l - } - } - realthis->Die(source, inflictor, dmgflags); - } - return; - } - - // [SO] 9/2/02 -- It's rather funny to see an exploded player body with the invuln sparkle active :) - effects &= ~FX_RESPAWNINVUL; - //flags &= ~MF_INVINCIBLE; - - if (debugfile && this->player) - { - static int dieticks[MAXPLAYERS]; // [ZzZombo] not used? Except if for peeking in debugger... - int pnum = int(this->player-players); - dieticks[pnum] = gametic; - fprintf (debugfile, "died (%d) on tic %d (%s)\n", pnum, gametic, - this->player->cheats&CF_PREDICTING?"predicting":"real"); - } - - // [RH] Notify this actor's items. - for (AInventory *item = Inventory; item != NULL; ) - { - AInventory *next = item->Inventory; - item->OwnerDied(); - item = next; - } - - if (flags & MF_MISSILE) - { // [RH] When missiles die, they just explode - P_ExplodeMissile (this, NULL, NULL); - return; - } - // [RH] Set the target to the thing that killed it. Strife apparently does this. - if (source != NULL) - { - target = source; - } - - flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SKULLFLY); - if (!(flags4 & MF4_DONTFALL)) flags&=~MF_NOGRAVITY; - flags |= MF_DROPOFF; - if ((flags3 & MF3_ISMONSTER) || FindState(NAME_Raise) != NULL || IsKindOf(RUNTIME_CLASS(APlayerPawn))) - { // [RH] Only monsters get to be corpses. - // Objects with a raise state should get the flag as well so they can - // be revived by an Arch-Vile. Batman Doom needs this. - // [RC] And disable this if DONTCORPSE is set, of course. - if(!(flags6 & MF6_DONTCORPSE)) flags |= MF_CORPSE; - } - flags6 |= MF6_KILLED; - - // [RH] Allow the death height to be overridden using metadata. - fixed_t metaheight = -1; - if (DamageType == NAME_Fire) - { - metaheight = GetClass()->BurnHeight; - } - if (metaheight < 0) - { - metaheight = GetClass()->DeathHeight; - } - if (metaheight < 0) - { - height >>= 2; - } - else - { - height = MAX (metaheight, 0); - } - - // [RH] If the thing has a special, execute and remove it - // Note that the thing that killed it is considered - // the activator of the script. - // New: In Hexen, the thing that died is the activator, - // so now a level flag selects who the activator gets to be. - // Everything is now moved to P_ActivateThingSpecial(). - if (special && (!(flags & MF_SPECIAL) || (flags3 & MF3_ISMONSTER)) - && !(activationtype & THINGSPEC_NoDeathSpecial)) - { - P_ActivateThingSpecial(this, source, true); - } - - if (CountsAsKill()) - level.killed_monsters++; - - if (source && source->player) - { - if (CountsAsKill()) - { // count for intermission - source->player->killcount++; - } - - // Don't count any frags at level start, because they're just telefrags - // resulting from insufficient deathmatch starts, and it wouldn't be - // fair to count them toward a player's score. - if (player && level.maptime) - { - source->player->frags[player - players]++; - if (player == source->player) // [RH] Cumulative frag count - { - char buff[256]; - - player->fragcount--; - if (deathmatch && player->spreecount >= 5 && cl_showsprees) - { - SexMessage (GStrings("SPREEKILLSELF"), buff, - player->userinfo.GetGender(), player->userinfo.GetName(), - player->userinfo.GetName()); - StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, buff, - 1.5f, 0.2f, 0, 0, CR_WHITE, 3.f, 0.5f), MAKE_ID('K','S','P','R')); - } - } - else - { - if ((dmflags2 & DF2_YES_LOSEFRAG) && deathmatch) - player->fragcount--; - - ++source->player->fragcount; - ++source->player->spreecount; - if (source->player->morphTics) - { // Make a super chicken - source->GiveInventoryType (RUNTIME_CLASS(APowerWeaponLevel2)); - } - if (deathmatch && cl_showsprees) - { - const char *spreemsg; - char buff[256]; - - switch (source->player->spreecount) - { - case 5: - spreemsg = GStrings("SPREE5"); - break; - case 10: - spreemsg = GStrings("SPREE10"); - break; - case 15: - spreemsg = GStrings("SPREE15"); - break; - case 20: - spreemsg = GStrings("SPREE20"); - break; - case 25: - spreemsg = GStrings("SPREE25"); - break; - default: - spreemsg = NULL; - break; - } - - if (spreemsg == NULL && player->spreecount >= 5) - { - if (!AnnounceSpreeLoss (this)) - { - SexMessage (GStrings("SPREEOVER"), buff, player->userinfo.GetGender(), - player->userinfo.GetName(), source->player->userinfo.GetName()); - StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, buff, - 1.5f, 0.2f, 0, 0, CR_WHITE, 3.f, 0.5f), MAKE_ID('K','S','P','R')); - } - } - else if (spreemsg != NULL) - { - if (!AnnounceSpree (source)) - { - SexMessage (spreemsg, buff, player->userinfo.GetGender(), - player->userinfo.GetName(), source->player->userinfo.GetName()); - StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, buff, - 1.5f, 0.2f, 0, 0, CR_WHITE, 3.f, 0.5f), MAKE_ID('K','S','P','R')); - } - } - } - } - - // [RH] Multikills - if (player != source->player) - { - source->player->multicount++; - if (source->player->lastkilltime > 0) - { - if (source->player->lastkilltime < level.time - 3*TICRATE) - { - source->player->multicount = 1; - } - - if (deathmatch && - source->CheckLocalView (consoleplayer) && - cl_showmultikills) - { - const char *multimsg; - - switch (source->player->multicount) - { - case 1: - multimsg = NULL; - break; - case 2: - multimsg = GStrings("MULTI2"); - break; - case 3: - multimsg = GStrings("MULTI3"); - break; - case 4: - multimsg = GStrings("MULTI4"); - break; - default: - multimsg = GStrings("MULTI5"); - break; - } - if (multimsg != NULL) - { - char buff[256]; - - if (!AnnounceMultikill (source)) - { - SexMessage (multimsg, buff, player->userinfo.GetGender(), - player->userinfo.GetName(), source->player->userinfo.GetName()); - StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, buff, - 1.5f, 0.8f, 0, 0, CR_RED, 3.f, 0.5f), MAKE_ID('M','K','I','L')); - } - } - } - } - source->player->lastkilltime = level.time; - } - - // [RH] Implement fraglimit - if (deathmatch && fraglimit && - fraglimit <= D_GetFragCount (source->player)) - { - Printf ("%s\n", GStrings("TXT_FRAGLIMIT")); - G_ExitLevel (0, false); - } - } - } - else if (!multiplayer && CountsAsKill()) - { - // count all monster deaths, - // even those caused by other monsters - players[0].killcount++; - } - - if (player) - { - // [RH] Death messages - ClientObituary (this, inflictor, source, dmgflags); - - // Death script execution, care of Skull Tag - FBehavior::StaticStartTypedScripts (SCRIPT_Death, this, true); - - // [RH] Force a delay between death and respawn - player->respawn_time = level.time + TICRATE; - - //Added by MC: Respawn bots - if (bglobal.botnum && !demoplayback) - { - if (player->Bot != NULL) - player->Bot->t_respawn = (pr_botrespawn()%15)+((bglobal.botnum-1)*2)+TICRATE+1; - - //Added by MC: Discard enemies. - for (int i = 0; i < MAXPLAYERS; i++) - { - if (players[i].Bot != NULL && this == players[i].Bot->enemy) - { - if (players[i].Bot->dest == players[i].Bot->enemy) - players[i].Bot->dest = NULL; - players[i].Bot->enemy = NULL; - } - } - - player->spreecount = 0; - player->multicount = 0; - } - - // count environment kills against you - if (!source) - { - player->frags[player - players]++; - player->fragcount--; // [RH] Cumulative frag count - } - - flags &= ~MF_SOLID; - player->playerstate = PST_DEAD; - P_DropWeapon (player); - if (this == players[consoleplayer].camera && automapactive) - { - // don't die in auto map, switch view prior to dying - AM_Stop (); - } - - // [GRB] Clear extralight. When you killed yourself with weapon that - // called A_Light1/2 before it called A_Light0, extraligh remained. - player->extralight = 0; - } - - // [RH] If this is the unmorphed version of another monster, destroy this - // actor, because the morphed version is the one that will stick around in - // the level. - if (flags & MF_UNMORPHED) - { - Destroy (); - return; - } - - - - FState *diestate = NULL; - int gibhealth = GetGibHealth(); - ActorFlags4 iflags4 = inflictor == NULL ? ActorFlags4::FromInt(0) : inflictor->flags4; - bool extremelydead = ((health < gibhealth || iflags4 & MF4_EXTREMEDEATH) && !(iflags4 & MF4_NOEXTREMEDEATH)); - - // Special check for 'extreme' damage type to ensure that it gets recorded properly as an extreme death for subsequent checks. - if (DamageType == NAME_Extreme) - { - extremelydead = true; - DamageType = NAME_None; - } - - // find the appropriate death state. The order is: - // - // 1. If damagetype is not 'none' and death is extreme, try a damage type specific extreme death state - // 2. If no such state is found or death is not extreme try a damage type specific normal death state - // 3. If damagetype is 'ice' and actor is a monster or player, try the generic freeze death (unless prohibited) - // 4. If no state has been found and death is extreme, try the extreme death state - // 5. If no such state is found or death is not extreme try the regular death state. - // 6. If still no state has been found, destroy the actor immediately. - - if (DamageType != NAME_None) - { - if (extremelydead) - { - FName labels[] = { NAME_Death, NAME_Extreme, DamageType }; - diestate = FindState(3, labels, true); - } - if (diestate == NULL) - { - diestate = FindState (NAME_Death, DamageType, true); - if (diestate != NULL) extremelydead = false; - } - if (diestate == NULL) - { - if (DamageType == NAME_Ice) - { // If an actor doesn't have an ice death, we can still give them a generic one. - - if (!deh.NoAutofreeze && !(flags4 & MF4_NOICEDEATH) && (player || (flags3 & MF3_ISMONSTER))) - { - diestate = FindState(NAME_GenericFreezeDeath); - extremelydead = false; - } - } - } - } - if (diestate == NULL) - { - - // Don't pass on a damage type this actor cannot handle. - // (most importantly, prevent barrels from passing on ice damage.) - // Massacre must be preserved though. - if (DamageType != NAME_Massacre) - { - DamageType = NAME_None; - } - - if (extremelydead) - { // Extreme death - diestate = FindState (NAME_Death, NAME_Extreme, true); - } - if (diestate == NULL) - { // Normal death - extremelydead = false; - diestate = FindState (NAME_Death); - } - } - - if (extremelydead) - { - // We'll only get here if an actual extreme death state was used. - - // For players, mark the appropriate flag. - if (player != NULL) - { - player->cheats |= CF_EXTREMELYDEAD; - } - // If a non-player, mark as extremely dead for the crash state. - else if (health >= gibhealth) - { - health = gibhealth - 1; - } - } - - if (diestate != NULL) - { - SetState (diestate); - - if (tics > 1) - { - tics -= pr_killmobj() & 3; - if (tics < 1) - tics = 1; - } - } - else - { - Destroy(); - } -} - - - - -//--------------------------------------------------------------------------- -// -// PROC P_AutoUseHealth -// -//--------------------------------------------------------------------------- -static int CountHealth(TArray &Items) -{ - int counted = 0; - for(unsigned i = 0; i < Items.Size(); i++) - { - counted += Items[i]->Amount * Items[i]->health; - } - return counted; -} - -static int UseHealthItems(TArray &Items, int &saveHealth) -{ - int saved = 0; - - while (Items.Size() > 0 && saveHealth > 0) - { - int maxhealth = 0; - int index = -1; - - // Find the largest item in the list - for(unsigned i = 0; i < Items.Size(); i++) - { - if (Items[i]->health > maxhealth) - { - index = i; - maxhealth = Items[i]->health; - } - } - - // Now apply the health items, using the same logic as Heretic and Hexen. - int count = (saveHealth + maxhealth-1) / maxhealth; - for(int i = 0; i < count; i++) - { - saved += maxhealth; - saveHealth -= maxhealth; - if (--Items[index]->Amount == 0) - { - Items[index]->DepleteOrDestroy (); - Items.Delete(index); - break; - } - } - } - return saved; -} - -void P_AutoUseHealth(player_t *player, int saveHealth) -{ - TArray NormalHealthItems; - TArray LargeHealthItems; - - for(AInventory *inv = player->mo->Inventory; inv != NULL; inv = inv->Inventory) - { - if (inv->Amount > 0 && inv->IsKindOf(RUNTIME_CLASS(AHealthPickup))) - { - int mode = static_cast(inv)->autousemode; - - if (mode == 1) NormalHealthItems.Push(inv); - else if (mode == 2) LargeHealthItems.Push(inv); - } - } - - int normalhealth = CountHealth(NormalHealthItems); - int largehealth = CountHealth(LargeHealthItems); - - bool skilluse = !!G_SkillProperty(SKILLP_AutoUseHealth); - - if (skilluse && normalhealth >= saveHealth) - { // Use quartz flasks - player->health += UseHealthItems(NormalHealthItems, saveHealth); - } - else if (largehealth >= saveHealth) - { - // Use mystic urns - player->health += UseHealthItems(LargeHealthItems, saveHealth); - } - else if (skilluse && normalhealth + largehealth >= saveHealth) - { // Use mystic urns and quartz flasks - player->health += UseHealthItems(NormalHealthItems, saveHealth); - if (saveHealth > 0) player->health += UseHealthItems(LargeHealthItems, saveHealth); - } - player->mo->health = player->health; -} - -//============================================================================ -// -// P_AutoUseStrifeHealth -// -//============================================================================ -CVAR(Bool, sv_disableautohealth, false, CVAR_ARCHIVE|CVAR_SERVERINFO) - -void P_AutoUseStrifeHealth (player_t *player) -{ - TArray Items; - - for(AInventory *inv = player->mo->Inventory; inv != NULL; inv = inv->Inventory) - { - if (inv->Amount > 0 && inv->IsKindOf(RUNTIME_CLASS(AHealthPickup))) - { - int mode = static_cast(inv)->autousemode; - - if (mode == 3) Items.Push(inv); - } - } - - if (!sv_disableautohealth) - { - while (Items.Size() > 0) - { - int maxhealth = 0; - int index = -1; - - // Find the largest item in the list - for(unsigned i = 0; i < Items.Size(); i++) - { - if (Items[i]->health > maxhealth) - { - index = i; - maxhealth = Items[i]->Amount; - } - } - - while (player->health < 50) - { - if (!player->mo->UseInventory (Items[index])) - break; - } - if (player->health >= 50) return; - // Using all of this item was not enough so delete it and restart with the next best one - Items.Delete(index); - } - } -} - -/* -================= -= -= P_DamageMobj -= -= Damages both enemies and players -= inflictor is the thing that caused the damage -= creature or missile, can be NULL (slime, etc) -= source is the thing to target after taking damage -= creature or NULL -= Source and inflictor are the same for melee attacks -= source can be null for barrel explosions and other environmental stuff -================== -*/ - -static inline bool MustForcePain(AActor *target, AActor *inflictor) -{ - return (!(target->flags5 & MF5_NOPAIN) && inflictor != NULL && - (inflictor->flags6 & MF6_FORCEPAIN) && !(inflictor->flags5 & MF5_PAINLESS)); -} - -static inline bool isFakePain(AActor *target, AActor *inflictor, int damage) -{ - return ((target->flags7 & MF7_ALLOWPAIN && damage > 0) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN))); -} - - -// Returns the amount of damage actually inflicted upon the target, or -1 if -// the damage was cancelled. -int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, FName mod, int flags) -{ - unsigned ang; - player_t *player = NULL; - fixed_t thrust; - int temp; - int painchance = 0; - FState * woundstate = NULL; - PainChanceList * pc = NULL; - bool justhit = false; - bool plrDontThrust = false; - bool invulpain = false; - bool fakedPain = false; - bool forcedPain = false; - int fakeDamage = 0; - int holdDamage = 0; - int rawdamage = damage; - - if (damage < 0) damage = 0; - - if (target == NULL || !((target->flags & MF_SHOOTABLE) || (target->flags6 & MF6_VULNERABLE))) - { // Shouldn't happen - return -1; - } - - //Rather than unnecessarily call the function over and over again, let's be a little more efficient. - fakedPain = (isFakePain(target, inflictor, damage)); - forcedPain = (MustForcePain(target, inflictor)); - - // Spectral targets only take damage from spectral projectiles. - if (target->flags4 & MF4_SPECTRAL && damage < TELEFRAG_DAMAGE) - { - if (inflictor == NULL || !(inflictor->flags4 & MF4_SPECTRAL)) - { - return -1; - } - } - if (target->health <= 0) - { - if (inflictor && mod == NAME_Ice) - { - return -1; - } - else if (target->flags & MF_ICECORPSE) // frozen - { - target->tics = 1; - target->flags6 |= MF6_SHATTERING; - target->velx = target->vely = target->velz = 0; - } - return -1; - } - // [MC] Changed it to check rawdamage here for consistency, even though that doesn't actually do anything - // different here. At any rate, invulnerable is being checked before type factoring, which is then being - // checked by player cheats/invul/buddha followed by monster buddha. This is inconsistent. Don't let the - // original telefrag damage CHECK (rawdamage) be influenced by outside factors when looking at cheats/invul. - if ((target->flags2 & MF2_INVULNERABLE) && (rawdamage < TELEFRAG_DAMAGE) && (!(flags & DMG_FORCED))) - { // actor is invulnerable - if (target->player == NULL) - { - if (inflictor == NULL || (!(inflictor->flags3 & MF3_FOILINVUL) && !(flags & DMG_FOILINVUL))) - { - if (fakedPain) - { - // big mess here: What do we use for the pain threshold? - // We cannot run the various damage filters below so for consistency it needs to be 0. - damage = 0; - invulpain = true; - goto fakepain; - } - else - return -1; - } - } - else - { - // Players are optionally excluded from getting thrust by damage. - if (static_cast(target)->PlayerFlags & PPF_NOTHRUSTWHENINVUL) - { - if (fakedPain) - plrDontThrust = 1; - else - return -1; - } - } - - } - - if (inflictor != NULL) - { - if (inflictor->flags5 & MF5_PIERCEARMOR) - flags |= DMG_NO_ARMOR; - } - - MeansOfDeath = mod; - FriendlyFire = false; - // [RH] Andy Baker's Stealth monsters - if (target->flags & MF_STEALTH) - { - target->alpha = OPAQUE; - target->visdir = -1; - } - if (target->flags & MF_SKULLFLY) - { - target->velx = target->vely = target->velz = 0; - } - - player = target->player; - if (!(flags & DMG_FORCED)) // DMG_FORCED skips all special damage checks, TELEFRAG_DAMAGE may not be reduced at all - { - if (target->flags2 & MF2_DORMANT) - { - // Invulnerable, and won't wake up - return -1; - } - - if ((rawdamage < TELEFRAG_DAMAGE) || (target->flags7 & MF7_LAXTELEFRAGDMG)) // TELEFRAG_DAMAGE may only be reduced with NOTELEFRAGPIERCE or it may not guarantee its effect. - { - if (player && damage > 1) - { - // Take half damage in trainer mode - damage = FixedMul(damage, G_SkillProperty(SKILLP_DamageFactor)); - } - // Special damage types - if (inflictor) - { - if (inflictor->flags4 & MF4_SPECTRAL) - { - if (player != NULL) - { - if (!deathmatch && inflictor->FriendPlayer > 0) - return -1; - } - else if (target->flags4 & MF4_SPECTRAL) - { - if (inflictor->FriendPlayer == 0 && !target->IsHostile(inflictor)) - return -1; - } - } - - damage = inflictor->DoSpecialDamage(target, damage, mod); - if (damage < 0) - { - return -1; - } - } - - int olddam = damage; - - if (damage > 0 && source != NULL) - { - damage = FixedMul(damage, source->DamageMultiply); - - // Handle active damage modifiers (e.g. PowerDamage) - if (damage > 0 && source->Inventory != NULL) - { - source->Inventory->ModifyDamage(damage, mod, damage, false); - } - } - // Handle passive damage modifiers (e.g. PowerProtection), provided they are not afflicted with protection penetrating powers. - if (damage > 0 && (target->Inventory != NULL) && !(flags & DMG_NO_PROTECT)) - { - target->Inventory->ModifyDamage(damage, mod, damage, true); - } - if (damage > 0 && !(flags & DMG_NO_FACTOR)) - { - damage = FixedMul(damage, target->DamageFactor); - if (damage > 0) - { - damage = DamageTypeDefinition::ApplyMobjDamageFactor(damage, mod, target->GetClass()->DamageFactors); - } - } - - if (damage >= 0) - { - damage = target->TakeSpecialDamage(inflictor, source, damage, mod); - } - - // '<0' is handled below. This only handles the case where damage gets reduced to 0. - if (damage == 0 && olddam > 0) - { - { // Still allow FORCEPAIN - if (forcedPain) - { - goto dopain; - } - else if (fakedPain) - { - goto fakepain; - } - return -1; - } - } - } - if (target->flags5 & MF5_NODAMAGE) - { - damage = 0; - } - } - if (damage < 0) - { - // any negative value means that something in the above chain has cancelled out all damage and all damage effects, including pain. - return -1; - } - // Push the target unless the source's weapon's kickback is 0. - // (i.e. Gauntlets/Chainsaw) - if (!plrDontThrust && inflictor && inflictor != target // [RH] Not if hurting own self - && !(target->flags & MF_NOCLIP) - && !(inflictor->flags2 & MF2_NODMGTHRUST) - && !(flags & DMG_THRUSTLESS) - && !(target->flags7 & MF7_DONTTHRUST) - && (source == NULL || source->player == NULL || !(source->flags2 & MF2_NODMGTHRUST))) - { - int kickback; - - if (inflictor && inflictor->projectileKickback) - kickback = inflictor->projectileKickback; - else if (!source || !source->player || !source->player->ReadyWeapon) - kickback = gameinfo.defKickback; - else - kickback = source->player->ReadyWeapon->Kickback; - - if (kickback) - { - AActor *origin = (source && (flags & DMG_INFLICTOR_IS_PUFF))? source : inflictor; - - // If the origin and target are in exactly the same spot, choose a random direction. - // (Most likely cause is from telefragging somebody during spawning because they - // haven't moved from their spawn spot at all.) - if (origin->x == target->x && origin->y == target->y) - { - ang = pr_kickbackdir.GenRand32(); - } - else - { - ang = R_PointToAngle2 (origin->x, origin->y, target->x, target->y); - } - - // Calculate this as float to avoid overflows so that the - // clamping that had to be done here can be removed. - double fltthrust; - - fltthrust = mod == NAME_MDK ? 10 : 32; - if (target->Mass > 0) - { - fltthrust = clamp((damage * 0.125 * kickback) / target->Mass, 0., fltthrust); - } - - thrust = FLOAT2FIXED(fltthrust); - - // Don't apply ultra-small damage thrust - if (thrust < FRACUNIT/100) thrust = 0; - - // make fall forwards sometimes - if ((damage < 40) && (damage > target->health) - && (target->z - origin->z > 64*FRACUNIT) - && (pr_damagemobj()&1) - // [RH] But only if not too fast and not flying - && thrust < 10*FRACUNIT - && !(target->flags & MF_NOGRAVITY) - && (inflictor == NULL || !(inflictor->flags5 & MF5_NOFORWARDFALL)) - ) - { - ang += ANG180; - thrust *= 4; - } - ang >>= ANGLETOFINESHIFT; - if (source && source->player && (flags & DMG_INFLICTOR_IS_PUFF) - && source->player->ReadyWeapon != NULL && - (source->player->ReadyWeapon->WeaponFlags & WIF_STAFF2_KICKBACK)) - { - // Staff power level 2 - target->velx += FixedMul (10*FRACUNIT, finecosine[ang]); - target->vely += FixedMul (10*FRACUNIT, finesine[ang]); - if (!(target->flags & MF_NOGRAVITY)) - { - target->velz += 5*FRACUNIT; - } - } - else - { - target->velx += FixedMul (thrust, finecosine[ang]); - target->vely += FixedMul (thrust, finesine[ang]); - } - } - } - - // [RH] Avoid friendly fire if enabled - if (!(flags & DMG_FORCED) && source != NULL && - ((player && player != source->player) || (!player && target != source)) && - target->IsTeammate (source)) - { - if (player) - FriendlyFire = true; - if (rawdamage < TELEFRAG_DAMAGE) //Use the original damage to check for telefrag amount. Don't let the now-amplified damagetypes do it. - { // Still allow telefragging :-( - damage = (int)((float)damage * level.teamdamage); - if (damage < 0) - { - return damage; - } - else if (damage == 0) - { - if (forcedPain) - { - goto dopain; - } - else if (fakedPain) - { - goto fakepain; - } - return -1; - } - } - } - - // - // player specific - // - if (player) - { - //Added by MC: Lets bots look allround for enemies if they survive an ambush. - if (player->Bot != NULL) - { - player->Bot->allround = true; - } - - // end of game hell hack - if ((target->Sector->special & 255) == dDamage_End - && damage >= target->health) - { - damage = target->health - 1; - } - - if (!(flags & DMG_FORCED)) - { - // check the real player, not a voodoo doll here for invulnerability effects - if ((rawdamage < TELEFRAG_DAMAGE && ((player->mo->flags2 & MF2_INVULNERABLE) || - (player->cheats & CF_GODMODE))) || - (player->cheats & CF_GODMODE2) || (player->mo->flags5 & MF5_NODAMAGE)) - //Absolutely no hurting if NODAMAGE is involved. Same for GODMODE2. - { // player is invulnerable, so don't hurt him - //Make sure no godmodes and NOPAIN flags are found first. - //Then, check to see if the player has NODAMAGE or ALLOWPAIN, or inflictor has CAUSEPAIN. - if ((player->cheats & CF_GODMODE) || (player->cheats & CF_GODMODE2) || (player->mo->flags5 & MF5_NOPAIN)) - return -1; - else if ((((player->mo->flags7 & MF7_ALLOWPAIN) || (player->mo->flags5 & MF5_NODAMAGE)) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN)))) - { - invulpain = true; - goto fakepain; - } - else - return -1; - } - - if (!(flags & DMG_NO_ARMOR) && player->mo->Inventory != NULL) - { - int newdam = damage; - if (damage > 0) - { - player->mo->Inventory->AbsorbDamage(damage, mod, newdam); - } - if ((rawdamage < TELEFRAG_DAMAGE) || (player->mo->flags7 & MF7_LAXTELEFRAGDMG)) //rawdamage is never modified. - { - // if we are telefragging don't let the damage value go below that magic value. Some further checks would fail otherwise. - damage = newdam; - } - - if (damage <= 0) - { - // If MF6_FORCEPAIN is set, make the player enter the pain state. - if (!(target->flags5 & MF5_NOPAIN) && inflictor != NULL && - (inflictor->flags6 & MF6_FORCEPAIN) && !(inflictor->flags5 & MF5_PAINLESS) - && (!(player->mo->flags2 & MF2_INVULNERABLE)) && (!(player->cheats & CF_GODMODE)) && (!(player->cheats & CF_GODMODE2))) - { - goto dopain; - } - return damage; - } - } - - if (damage >= player->health && rawdamage < TELEFRAG_DAMAGE - && (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch) - && !player->morphTics) - { // Try to use some inventory health - P_AutoUseHealth (player, damage - player->health + 1); - } - } - - player->health -= damage; // mirror mobj health here for Dave - // [RH] Make voodoo dolls and real players record the same health - target->health = player->mo->health -= damage; - if (player->health < 50 && !deathmatch && !(flags & DMG_FORCED)) - { - P_AutoUseStrifeHealth (player); - player->mo->health = player->health; - } - if (player->health <= 0) - { - // [SP] Buddha cheat: if the player is about to die, rescue him to 1 health. - // This does not save the player if damage >= TELEFRAG_DAMAGE, still need to - // telefrag him right? ;) (Unfortunately the damage is "absorbed" by armor, - // but telefragging should still do enough damage to kill the player) - // Ignore players that are already dead. - // [MC]Buddha2 absorbs telefrag damage, and anything else thrown their way. - if (!(flags & DMG_FORCED) && (((player->cheats & CF_BUDDHA2) || (((player->cheats & CF_BUDDHA) || (player->mo->flags7 & MF7_BUDDHA)) && (rawdamage < TELEFRAG_DAMAGE))) && (player->playerstate != PST_DEAD))) - { - // If this is a voodoo doll we need to handle the real player as well. - player->mo->health = target->health = player->health = 1; - } - else - { - player->health = 0; - } - } - player->LastDamageType = mod; - player->attacker = source; - player->damagecount += damage; // add damage after armor / invuln - if (player->damagecount > 100) - { - player->damagecount = 100; // teleport stomp does 10k points... - } - temp = damage < 100 ? damage : 100; - if (player == &players[consoleplayer]) - { - I_Tactile (40,10,40+temp*2); - } - } - else - { - // Armor for monsters. - if (!(flags & (DMG_NO_ARMOR|DMG_FORCED)) && target->Inventory != NULL && damage > 0) - { - int newdam = damage; - target->Inventory->AbsorbDamage (damage, mod, newdam); - damage = newdam; - if (damage <= 0) - { - if (fakedPain) - goto fakepain; - else - return damage; - } - } - - target->health -= damage; - } - - // - // the damage has been dealt; now deal with the consequences - // - target->DamageTypeReceived = mod; - - // If the damaging player has the power of drain, give the player 50% of the damage - // done in health. - if ( source && source->player && source->player->cheats & CF_DRAIN && !(target->flags5 & MF5_DONTDRAIN)) - { - if (!target->player || target->player != source->player) - { - if ( P_GiveBody( source, damage / 2 )) - { - S_Sound( source, CHAN_ITEM, "*drainhealth", 1, ATTN_NORM ); - } - } - } - - - if (target->health <= 0) - { - //[MC]Buddha flag for monsters. - if (!(flags & DMG_FORCED) && ((target->flags7 & MF7_BUDDHA) && (rawdamage < TELEFRAG_DAMAGE) && ((inflictor == NULL || !(inflictor->flags7 & MF7_FOILBUDDHA)) && !(flags & DMG_FOILBUDDHA)))) - { //FOILBUDDHA or Telefrag damage must kill it. - target->health = 1; - } - else - { - - // Death - target->special1 = damage; - - // use inflictor's death type if it got one. - if (inflictor && inflictor->DeathType != NAME_None) mod = inflictor->DeathType; - - // check for special fire damage or ice damage deaths - if (mod == NAME_Fire) - { - if (player && !player->morphTics) - { // Check for flame death - if (!inflictor || - ((target->health > -50) && (damage > 25)) || - !(inflictor->flags5 & MF5_SPECIALFIREDAMAGE)) - { - target->DamageType = NAME_Fire; - } - } - else - { - target->DamageType = NAME_Fire; - } - } - else - { - target->DamageType = mod; - } - if (source && source->tracer && (source->flags5 & MF5_SUMMONEDMONSTER)) - { // Minotaur's kills go to his master - // Make sure still alive and not a pointer to fighter head - if (source->tracer->player && (source->tracer->player->mo == source->tracer)) - { - source = source->tracer; - } - } - target->Die (source, inflictor, flags); - return damage; - } - } - - woundstate = target->FindState(NAME_Wound, mod); - if (woundstate != NULL) - { - int woundhealth = target->GetClass()->WoundHealth; - - if (target->health <= woundhealth) - { - target->SetState (woundstate); - return damage; - } - } - -fakepain: //Needed so we can skip the rest of the above, but still obey the original rules. - - if (!(target->flags5 & MF5_NOPAIN) && (inflictor == NULL || !(inflictor->flags5 & MF5_PAINLESS)) && - (target->player != NULL || !G_SkillProperty(SKILLP_NoPain)) && !(target->flags & MF_SKULLFLY)) - { - pc = target->GetClass()->PainChances; - painchance = target->PainChance; - if (pc != NULL) - { - int *ppc = pc->CheckKey(mod); - if (ppc != NULL) - { - painchance = *ppc; - } - } - - if (((damage >= target->PainThreshold) && (pr_damagemobj() < painchance)) - || (inflictor != NULL && (inflictor->flags6 & MF6_FORCEPAIN))) - { -dopain: - if (mod == NAME_Electric) - { - if (pr_lightning() < 96) - { - justhit = true; - FState *painstate = target->FindState(NAME_Pain, mod); - if (painstate != NULL) - target->SetState(painstate); - } - else - { // "electrocute" the target - target->renderflags |= RF_FULLBRIGHT; - if ((target->flags3 & MF3_ISMONSTER) && pr_lightning() < 128) - { - target->Howl (); - } - } - } - else - { - justhit = true; - FState *painstate = target->FindState(NAME_Pain, ((inflictor && inflictor->PainType != NAME_None) ? inflictor->PainType : mod)); - if (painstate != NULL) - target->SetState(painstate); - if (mod == NAME_PoisonCloud) - { - if ((target->flags3 & MF3_ISMONSTER) && pr_poison() < 128) - { - target->Howl (); - } - } - } - } - } - //ALLOWPAIN and CAUSEPAIN can still trigger infighting, even if no pain state is worked out. - target->reactiontime = 0; // we're awake now... - if (source) - { - if (source == target->target) - { - target->threshold = BASETHRESHOLD; - if (target->state == target->SpawnState && target->SeeState != NULL) - { - target->SetState (target->SeeState); - } - } - else if (source != target->target && target->OkayToSwitchTarget (source)) - { - // Target actor is not intent on another actor, - // so make him chase after source - - // killough 2/15/98: remember last enemy, to prevent - // sleeping early; 2/21/98: Place priority on players - - if (target->lastenemy == NULL || - (target->lastenemy->player == NULL && target->TIDtoHate == 0) || - target->lastenemy->health <= 0) - { - target->lastenemy = target->target; // remember last enemy - killough - } - target->target = source; - target->threshold = BASETHRESHOLD; - if (target->state == target->SpawnState && target->SeeState != NULL) - { - target->SetState (target->SeeState); - } - } - } - - // killough 11/98: Don't attack a friend, unless hit by that friend. - if (justhit && (target->target == source || !target->target || !target->IsFriend(target->target))) - target->flags |= MF_JUSTHIT; // fight back! - - if (invulpain) //Note that this takes into account all the cheats a player has, in terms of invulnerability. - { - return -1; //NOW we return -1! - } - return damage; -} - -void P_PoisonMobj (AActor *target, AActor *inflictor, AActor *source, int damage, int duration, int period, FName type) -{ - // Check for invulnerability. - if (!(inflictor->flags6 & MF6_POISONALWAYS)) - { - if (target->flags2 & MF2_INVULNERABLE) - { // actor is invulnerable - if (target->player == NULL) - { - if (!(inflictor->flags3 & MF3_FOILINVUL)) - { - return; - } - } - else - { - return; - } - } - } - - target->Poisoner = source; - target->PoisonDamageTypeReceived = type; - target->PoisonPeriodReceived = period; - - if (inflictor->flags6 & MF6_ADDITIVEPOISONDAMAGE) - { - target->PoisonDamageReceived += damage; - } - else - { - target->PoisonDamageReceived = damage; - } - - if (inflictor->flags6 & MF6_ADDITIVEPOISONDURATION) - { - target->PoisonDurationReceived += duration; - } - else - { - target->PoisonDurationReceived = duration; - } - -} - -bool AActor::OkayToSwitchTarget (AActor *other) -{ - if (other == this) - return false; // [RH] Don't hate self (can happen when shooting barrels) - - if (other->flags7 & MF7_NEVERTARGET) - return false; // never EVER target me! - - if (!(other->flags & MF_SHOOTABLE)) - return false; // Don't attack things that can't be hurt - - if ((flags4 & MF4_NOTARGETSWITCH) && target != NULL) - return false; // Don't switch target if not allowed - - if ((master != NULL && other->IsA(master->GetClass())) || // don't attack your master (or others of its type) - (other->master != NULL && IsA(other->master->GetClass()))) // don't attack your minion (or those of others of your type) - { - if (!IsHostile (other) && // allow target switch if other is considered hostile - (other->tid != TIDtoHate || TIDtoHate == 0) && // or has the tid we hate - other->TIDtoHate == TIDtoHate) // or has different hate information - { - return false; - } - } - - if ((other->flags3 & MF3_NOTARGET) && - (other->tid != TIDtoHate || TIDtoHate == 0) && - !IsHostile (other)) - return false; - if (threshold != 0 && !(flags4 & MF4_QUICKTORETALIATE)) - return false; - if (IsFriend (other)) - { // [RH] Friendlies don't target other friendlies - return false; - } - - int infight; - if (flags5 & MF5_NOINFIGHTING) infight=-1; - else if (level.flags2 & LEVEL2_TOTALINFIGHTING) infight=1; - else if (level.flags2 & LEVEL2_NOINFIGHTING) infight=-1; - else infight = infighting; - - if (infight < 0 && other->player == NULL && !IsHostile (other)) - { - return false; // infighting off: Non-friendlies don't target other non-friendlies - } - if (TIDtoHate != 0 && TIDtoHate == other->TIDtoHate) - return false; // [RH] Don't target "teammates" - if (other->player != NULL && (flags4 & MF4_NOHATEPLAYERS)) - return false; // [RH] Don't target players - if (target != NULL && target->health > 0 && - TIDtoHate != 0 && target->tid == TIDtoHate && pr_switcher() < 128 && - P_CheckSight (this, target)) - return false; // [RH] Don't be too quick to give up things we hate - - return true; -} - -//========================================================================== -// -// P_PoisonPlayer - Sets up all data concerning poisoning -// -// poisoner is the object directly responsible for poisoning the player, -// such as a missile. source is the actor responsible for creating the -// poisoner. -// -//========================================================================== - -bool P_PoisonPlayer (player_t *player, AActor *poisoner, AActor *source, int poison) -{ - if((player->cheats&CF_GODMODE) || (player->mo->flags2 & MF2_INVULNERABLE) || (player->cheats & CF_GODMODE2)) - { - return false; - } - if (source != NULL && source->player != player && player->mo->IsTeammate (source)) - { - poison = (int)((float)poison * level.teamdamage); - } - if (poison > 0) - { - player->poisoncount += poison; - player->poisoner = source; - if (poisoner == NULL) - { - player->poisontype = player->poisonpaintype = NAME_None; - } - else - { // We need to record these in case the poisoner disappears before poisoncount reaches 0. - player->poisontype = poisoner->DamageType; - player->poisonpaintype = poisoner->PainType != NAME_None ? poisoner->PainType : poisoner->DamageType; - } - if(player->poisoncount > 100) - { - player->poisoncount = 100; - } - } - return true; -} - -//========================================================================== -// -// P_PoisonDamage - Similar to P_DamageMobj -// -//========================================================================== - -void P_PoisonDamage (player_t *player, AActor *source, int damage, - bool playPainSound) -{ - AActor *target; - - if (player == NULL) - { - return; - } - target = player->mo; - if (target->health <= 0) - { - return; - } - if ((damage < TELEFRAG_DAMAGE && ((target->flags2 & MF2_INVULNERABLE) || - (player->cheats & CF_GODMODE))) || (player->cheats & CF_GODMODE2)) - { // target is invulnerable - return; - } - // Take half damage in trainer mode - damage = FixedMul(damage, G_SkillProperty(SKILLP_DamageFactor)); - // Handle passive damage modifiers (e.g. PowerProtection) - if (target->Inventory != NULL) - { - target->Inventory->ModifyDamage(damage, player->poisontype, damage, true); - } - // Modify with damage factors - damage = FixedMul(damage, target->DamageFactor); - if (damage > 0) - { - damage = DamageTypeDefinition::ApplyMobjDamageFactor(damage, player->poisontype, target->GetClass()->DamageFactors); - } - if (damage <= 0) - { // Damage was reduced to 0, so don't bother further. - return; - } - if (damage >= player->health - && (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch) - && !player->morphTics) - { // Try to use some inventory health - P_AutoUseHealth(player, damage - player->health+1); - } - player->health -= damage; // mirror mobj health here for Dave - if (player->health < 50 && !deathmatch) - { - P_AutoUseStrifeHealth(player); - } - if (player->health < 0) - { - player->health = 0; - } - player->attacker = source; - - // - // do the damage - // - target->health -= damage; - if (target->health <= 0) - { // Death - if ((((player->cheats & CF_BUDDHA) || (player->mo->flags7 & MF7_BUDDHA)) && damage < TELEFRAG_DAMAGE) || (player->cheats & CF_BUDDHA2)) - { // [SP] Save the player... - player->health = target->health = 1; - } - else - { - target->special1 = damage; - if (player && !player->morphTics) - { // Check for flame death - if ((player->poisontype == NAME_Fire) && (target->health > -50) && (damage > 25)) - { - target->DamageType = NAME_Fire; - } - else - { - target->DamageType = player->poisontype; - } - } - target->Die(source, source); - return; - } - } - if (!(level.time&63) && playPainSound) - { - FState *painstate = target->FindState(NAME_Pain, player->poisonpaintype); - if (painstate != NULL) - { - target->SetState(painstate); - } - } -/* - if((P_Random() < target->info->painchance) - && !(target->flags&MF_SKULLFLY)) - { - target->flags |= MF_JUSTHIT; // fight back! - P_SetMobjState(target, target->info->painstate); - } -*/ -} - - -CCMD (kill) -{ - if (argv.argc() > 1) - { - if (CheckCheatmode ()) - return; - - if (!stricmp (argv[1], "monsters")) - { - // Kill all the monsters - if (CheckCheatmode ()) - return; - - Net_WriteByte (DEM_GENERICCHEAT); - Net_WriteByte (CHT_MASSACRE); - } - else - { - Net_WriteByte (DEM_KILLCLASSCHEAT); - Net_WriteString (argv[1]); - } - } - else - { - // If suiciding is disabled, then don't do it. - if (dmflags2 & DF2_NOSUICIDE) - return; - - // Kill the player - Net_WriteByte (DEM_SUICIDE); - } - C_HideConsole (); -} +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// $Log:$ +// +// DESCRIPTION: +// Handling interactions (i.e., collisions). +// +//----------------------------------------------------------------------------- + + + + +// Data. +#include "doomdef.h" +#include "gstrings.h" + +#include "doomstat.h" + +#include "m_random.h" +#include "i_system.h" +#include "announcer.h" + +#include "am_map.h" + +#include "c_console.h" +#include "c_dispatch.h" + +#include "p_local.h" + +#include "p_lnspec.h" +#include "p_effect.h" +#include "p_acs.h" + +#include "b_bot.h" //Added by MC: + +#include "ravenshared.h" +#include "a_hexenglobal.h" +#include "a_sharedglobal.h" +#include "a_pickups.h" +#include "gi.h" +#include "templates.h" +#include "sbar.h" +#include "s_sound.h" +#include "g_level.h" +#include "d_net.h" +#include "d_netinf.h" + +static FRandom pr_obituary ("Obituary"); +static FRandom pr_botrespawn ("BotRespawn"); +static FRandom pr_killmobj ("ActorDie"); +FRandom pr_damagemobj ("ActorTakeDamage"); +static FRandom pr_lightning ("LightningDamage"); +static FRandom pr_poison ("PoisonDamage"); +static FRandom pr_switcher ("SwitchTarget"); +static FRandom pr_kickbackdir ("KickbackDir"); + +CVAR (Bool, cl_showsprees, true, CVAR_ARCHIVE) +CVAR (Bool, cl_showmultikills, true, CVAR_ARCHIVE) +EXTERN_CVAR (Bool, show_obituaries) + + +FName MeansOfDeath; + +// +// GET STUFF +// + +// +// P_TouchSpecialThing +// +void P_TouchSpecialThing (AActor *special, AActor *toucher) +{ + fixed_t delta = special->z - toucher->z; + + // The pickup is at or above the toucher's feet OR + // The pickup is below the toucher. + if (delta > toucher->height || delta < MIN(-32*FRACUNIT, -special->height)) + { // out of reach + return; + } + + // Dead thing touching. + // Can happen with a sliding player corpse. + if (toucher->health <= 0) + return; + + //Added by MC: Finished with this destination. + if (toucher->player != NULL && toucher->player->Bot != NULL && special == toucher->player->Bot->dest) + { + toucher->player->Bot->prev = toucher->player->Bot->dest; + toucher->player->Bot->dest = NULL; + } + + special->Touch (toucher); +} + + +// [RH] +// SexMessage: Replace parts of strings with gender-specific pronouns +// +// The following expansions are performed: +// %g -> he/she/it +// %h -> him/her/it +// %p -> his/her/its +// %o -> other (victim) +// %k -> killer +// +void SexMessage (const char *from, char *to, int gender, const char *victim, const char *killer) +{ + static const char *genderstuff[3][3] = + { + { "he", "him", "his" }, + { "she", "her", "her" }, + { "it", "it", "its" } + }; + static const int gendershift[3][3] = + { + { 2, 3, 3 }, + { 3, 3, 3 }, + { 2, 2, 3 } + }; + const char *subst = NULL; + + do + { + if (*from != '%') + { + *to++ = *from; + } + else + { + int gendermsg = -1; + + switch (from[1]) + { + case 'g': gendermsg = 0; break; + case 'h': gendermsg = 1; break; + case 'p': gendermsg = 2; break; + case 'o': subst = victim; break; + case 'k': subst = killer; break; + } + if (subst != NULL) + { + size_t len = strlen (subst); + memcpy (to, subst, len); + to += len; + from++; + subst = NULL; + } + else if (gendermsg < 0) + { + *to++ = '%'; + } + else + { + strcpy (to, genderstuff[gender][gendermsg]); + to += gendershift[gender][gendermsg]; + from++; + } + } + } while (*from++); +} + +// [RH] +// ClientObituary: Show a message when a player dies +// +void ClientObituary (AActor *self, AActor *inflictor, AActor *attacker, int dmgflags) +{ + FName mod; + const char *message; + const char *messagename; + char gendermessage[1024]; + int gender; + + // No obituaries for non-players, voodoo dolls or when not wanted + if (self->player == NULL || self->player->mo != self || !show_obituaries) + return; + + gender = self->player->userinfo.GetGender(); + + // Treat voodoo dolls as unknown deaths + if (inflictor && inflictor->player && inflictor->player->mo != inflictor) + MeansOfDeath = NAME_None; + + mod = MeansOfDeath; + message = NULL; + messagename = NULL; + + if (attacker == NULL || attacker->player != NULL) + { + if (mod == NAME_Telefrag) + { + if (AnnounceTelefrag (attacker, self)) + return; + } + else + { + if (AnnounceKill (attacker, self)) + return; + } + } + + 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. + if (inflictor && inflictor->player && inflictor->player->mo != inflictor) + { + messagename = "OB_VOODOO"; + } + + if (messagename != NULL) + message = GStrings(messagename); + + if (attacker != NULL && message == NULL) + { + if (attacker == self) + { + message = GStrings("OB_KILLEDSELF"); + } + else if (attacker->player == NULL) + { + if (mod == NAME_Telefrag) + { + message = GStrings("OB_MONTELEFRAG"); + } + else if (mod == NAME_Melee && attacker->GetClass()->HitObituary.IsNotEmpty()) + { + message = attacker->GetClass()->HitObituary; + } + else if (attacker->GetClass()->Obituary.IsNotEmpty()) + { + message = attacker->GetClass()->Obituary; + } + } + } + + if (message == NULL && attacker != NULL && attacker->player != NULL) + { + if (self->player != attacker->player && self->IsTeammate(attacker)) + { + self = attacker; + gender = self->player->userinfo.GetGender(); + mysnprintf (gendermessage, countof(gendermessage), "OB_FRIENDLY%c", '1' + (pr_obituary() & 3)); + message = GStrings(gendermessage); + } + else + { + if (mod == NAME_Telefrag) message = GStrings("OB_MPTELEFRAG"); + if (message == NULL) + { + if (inflictor != NULL && inflictor->GetClass()->Obituary.IsNotEmpty()) + { + message = inflictor->GetClass()->Obituary; + } + if (message == NULL && (dmgflags & DMG_PLAYERATTACK) && attacker->player->ReadyWeapon != NULL) + { + message = attacker->player->ReadyWeapon->GetClass()->Obituary; + } + if (message == NULL) + { + switch (mod) + { + case NAME_BFGSplash: messagename = "OB_MPBFG_SPLASH"; break; + case NAME_Railgun: messagename = "OB_RAILGUN"; break; + } + if (messagename != NULL) + message = GStrings(messagename); + } + if (message == NULL) + { + message = attacker->GetClass()->Obituary; + } + } + } + } + else attacker = self; // for the message creation + + if (message != NULL && message[0] == '$') + { + message = GStrings[message+1]; + } + + if (message == NULL) + { + message = GStrings("OB_DEFAULT"); + } + + // [CK] Don't display empty strings + if (message == NULL || strlen(message) <= 0) + return; + + SexMessage (message, gendermessage, gender, + self->player->userinfo.GetName(), attacker->player->userinfo.GetName()); + Printf (PRINT_MEDIUM, "%s\n", gendermessage); +} + + +// +// KillMobj +// +EXTERN_CVAR (Int, fraglimit) + +void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) +{ + // Handle possible unmorph on death + bool wasgibbed = (health < GetGibHealth()); + AActor *realthis = NULL; + int realstyle = 0; + int realhealth = 0; + if (P_MorphedDeath(this, &realthis, &realstyle, &realhealth)) + { + if (!(realstyle & MORPH_UNDOBYDEATHSAVES)) + { + if (wasgibbed) + { + int realgibhealth = realthis->GetGibHealth(); + if (realthis->health >= realgibhealth) + { + realthis->health = realgibhealth -1; // if morphed was gibbed, so must original be (where allowed)l + } + } + realthis->Die(source, inflictor, dmgflags); + } + return; + } + + // [SO] 9/2/02 -- It's rather funny to see an exploded player body with the invuln sparkle active :) + effects &= ~FX_RESPAWNINVUL; + //flags &= ~MF_INVINCIBLE; + + if (debugfile && this->player) + { + static int dieticks[MAXPLAYERS]; // [ZzZombo] not used? Except if for peeking in debugger... + int pnum = int(this->player-players); + dieticks[pnum] = gametic; + fprintf (debugfile, "died (%d) on tic %d (%s)\n", pnum, gametic, + this->player->cheats&CF_PREDICTING?"predicting":"real"); + } + + // [RH] Notify this actor's items. + for (AInventory *item = Inventory; item != NULL; ) + { + AInventory *next = item->Inventory; + item->OwnerDied(); + item = next; + } + + if (flags & MF_MISSILE) + { // [RH] When missiles die, they just explode + P_ExplodeMissile (this, NULL, NULL); + return; + } + // [RH] Set the target to the thing that killed it. Strife apparently does this. + if (source != NULL) + { + target = source; + } + + flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SKULLFLY); + if (!(flags4 & MF4_DONTFALL)) flags&=~MF_NOGRAVITY; + flags |= MF_DROPOFF; + if ((flags3 & MF3_ISMONSTER) || FindState(NAME_Raise) != NULL || IsKindOf(RUNTIME_CLASS(APlayerPawn))) + { // [RH] Only monsters get to be corpses. + // Objects with a raise state should get the flag as well so they can + // be revived by an Arch-Vile. Batman Doom needs this. + // [RC] And disable this if DONTCORPSE is set, of course. + if(!(flags6 & MF6_DONTCORPSE)) flags |= MF_CORPSE; + } + flags6 |= MF6_KILLED; + + // [RH] Allow the death height to be overridden using metadata. + fixed_t metaheight = -1; + if (DamageType == NAME_Fire) + { + metaheight = GetClass()->BurnHeight; + } + if (metaheight < 0) + { + metaheight = GetClass()->DeathHeight; + } + if (metaheight < 0) + { + height >>= 2; + } + else + { + height = MAX (metaheight, 0); + } + + // [RH] If the thing has a special, execute and remove it + // Note that the thing that killed it is considered + // the activator of the script. + // New: In Hexen, the thing that died is the activator, + // so now a level flag selects who the activator gets to be. + // Everything is now moved to P_ActivateThingSpecial(). + if (special && (!(flags & MF_SPECIAL) || (flags3 & MF3_ISMONSTER)) + && !(activationtype & THINGSPEC_NoDeathSpecial)) + { + P_ActivateThingSpecial(this, source, true); + } + + if (CountsAsKill()) + level.killed_monsters++; + + if (source && source->player) + { + if (CountsAsKill()) + { // count for intermission + source->player->killcount++; + } + + // Don't count any frags at level start, because they're just telefrags + // resulting from insufficient deathmatch starts, and it wouldn't be + // fair to count them toward a player's score. + if (player && level.maptime) + { + source->player->frags[player - players]++; + if (player == source->player) // [RH] Cumulative frag count + { + char buff[256]; + + player->fragcount--; + if (deathmatch && player->spreecount >= 5 && cl_showsprees) + { + SexMessage (GStrings("SPREEKILLSELF"), buff, + player->userinfo.GetGender(), player->userinfo.GetName(), + player->userinfo.GetName()); + StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, buff, + 1.5f, 0.2f, 0, 0, CR_WHITE, 3.f, 0.5f), MAKE_ID('K','S','P','R')); + } + } + else + { + if ((dmflags2 & DF2_YES_LOSEFRAG) && deathmatch) + player->fragcount--; + + if (this->IsTeammate(source)) + { + source->player->fragcount--; + } + else + { + ++source->player->fragcount; + ++source->player->spreecount; + } + + if (source->player->morphTics) + { // Make a super chicken + source->GiveInventoryType (RUNTIME_CLASS(APowerWeaponLevel2)); + } + + if (deathmatch && cl_showsprees) + { + const char *spreemsg; + char buff[256]; + + switch (source->player->spreecount) + { + case 5: + spreemsg = GStrings("SPREE5"); + break; + case 10: + spreemsg = GStrings("SPREE10"); + break; + case 15: + spreemsg = GStrings("SPREE15"); + break; + case 20: + spreemsg = GStrings("SPREE20"); + break; + case 25: + spreemsg = GStrings("SPREE25"); + break; + default: + spreemsg = NULL; + break; + } + + if (spreemsg == NULL && player->spreecount >= 5) + { + if (!AnnounceSpreeLoss (this)) + { + SexMessage (GStrings("SPREEOVER"), buff, player->userinfo.GetGender(), + player->userinfo.GetName(), source->player->userinfo.GetName()); + StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, buff, + 1.5f, 0.2f, 0, 0, CR_WHITE, 3.f, 0.5f), MAKE_ID('K','S','P','R')); + } + } + else if (spreemsg != NULL) + { + if (!AnnounceSpree (source)) + { + SexMessage (spreemsg, buff, player->userinfo.GetGender(), + player->userinfo.GetName(), source->player->userinfo.GetName()); + StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, buff, + 1.5f, 0.2f, 0, 0, CR_WHITE, 3.f, 0.5f), MAKE_ID('K','S','P','R')); + } + } + } + } + + // [RH] Multikills + if (player != source->player) + { + source->player->multicount++; + if (source->player->lastkilltime > 0) + { + if (source->player->lastkilltime < level.time - 3*TICRATE) + { + source->player->multicount = 1; + } + + if (deathmatch && + source->CheckLocalView (consoleplayer) && + cl_showmultikills) + { + const char *multimsg; + + switch (source->player->multicount) + { + case 1: + multimsg = NULL; + break; + case 2: + multimsg = GStrings("MULTI2"); + break; + case 3: + multimsg = GStrings("MULTI3"); + break; + case 4: + multimsg = GStrings("MULTI4"); + break; + default: + multimsg = GStrings("MULTI5"); + break; + } + if (multimsg != NULL) + { + char buff[256]; + + if (!AnnounceMultikill (source)) + { + SexMessage (multimsg, buff, player->userinfo.GetGender(), + player->userinfo.GetName(), source->player->userinfo.GetName()); + StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, buff, + 1.5f, 0.8f, 0, 0, CR_RED, 3.f, 0.5f), MAKE_ID('M','K','I','L')); + } + } + } + } + source->player->lastkilltime = level.time; + } + + // [RH] Implement fraglimit + if (deathmatch && fraglimit && + fraglimit <= D_GetFragCount (source->player)) + { + Printf ("%s\n", GStrings("TXT_FRAGLIMIT")); + G_ExitLevel (0, false); + } + } + } + else if (!multiplayer && CountsAsKill()) + { + // count all monster deaths, + // even those caused by other monsters + players[0].killcount++; + } + + if (player) + { + // [RH] Death messages + ClientObituary (this, inflictor, source, dmgflags); + + // Death script execution, care of Skull Tag + FBehavior::StaticStartTypedScripts (SCRIPT_Death, this, true); + + // [RH] Force a delay between death and respawn + player->respawn_time = level.time + TICRATE; + + //Added by MC: Respawn bots + if (bglobal.botnum && !demoplayback) + { + if (player->Bot != NULL) + player->Bot->t_respawn = (pr_botrespawn()%15)+((bglobal.botnum-1)*2)+TICRATE+1; + + //Added by MC: Discard enemies. + for (int i = 0; i < MAXPLAYERS; i++) + { + if (players[i].Bot != NULL && this == players[i].Bot->enemy) + { + if (players[i].Bot->dest == players[i].Bot->enemy) + players[i].Bot->dest = NULL; + players[i].Bot->enemy = NULL; + } + } + + player->spreecount = 0; + player->multicount = 0; + } + + // count environment kills against you + if (!source) + { + player->frags[player - players]++; + player->fragcount--; // [RH] Cumulative frag count + } + + flags &= ~MF_SOLID; + player->playerstate = PST_DEAD; + P_DropWeapon (player); + if (this == players[consoleplayer].camera && automapactive) + { + // don't die in auto map, switch view prior to dying + AM_Stop (); + } + + // [GRB] Clear extralight. When you killed yourself with weapon that + // called A_Light1/2 before it called A_Light0, extraligh remained. + player->extralight = 0; + } + + // [RH] If this is the unmorphed version of another monster, destroy this + // actor, because the morphed version is the one that will stick around in + // the level. + if (flags & MF_UNMORPHED) + { + Destroy (); + return; + } + + + + FState *diestate = NULL; + int gibhealth = GetGibHealth(); + ActorFlags4 iflags4 = inflictor == NULL ? ActorFlags4::FromInt(0) : inflictor->flags4; + bool extremelydead = ((health < gibhealth || iflags4 & MF4_EXTREMEDEATH) && !(iflags4 & MF4_NOEXTREMEDEATH)); + + // Special check for 'extreme' damage type to ensure that it gets recorded properly as an extreme death for subsequent checks. + if (DamageType == NAME_Extreme) + { + extremelydead = true; + DamageType = NAME_None; + } + + // find the appropriate death state. The order is: + // + // 1. If damagetype is not 'none' and death is extreme, try a damage type specific extreme death state + // 2. If no such state is found or death is not extreme try a damage type specific normal death state + // 3. If damagetype is 'ice' and actor is a monster or player, try the generic freeze death (unless prohibited) + // 4. If no state has been found and death is extreme, try the extreme death state + // 5. If no such state is found or death is not extreme try the regular death state. + // 6. If still no state has been found, destroy the actor immediately. + + if (DamageType != NAME_None) + { + if (extremelydead) + { + FName labels[] = { NAME_Death, NAME_Extreme, DamageType }; + diestate = FindState(3, labels, true); + } + if (diestate == NULL) + { + diestate = FindState (NAME_Death, DamageType, true); + if (diestate != NULL) extremelydead = false; + } + if (diestate == NULL) + { + if (DamageType == NAME_Ice) + { // If an actor doesn't have an ice death, we can still give them a generic one. + + if (!deh.NoAutofreeze && !(flags4 & MF4_NOICEDEATH) && (player || (flags3 & MF3_ISMONSTER))) + { + diestate = FindState(NAME_GenericFreezeDeath); + extremelydead = false; + } + } + } + } + if (diestate == NULL) + { + + // Don't pass on a damage type this actor cannot handle. + // (most importantly, prevent barrels from passing on ice damage.) + // Massacre must be preserved though. + if (DamageType != NAME_Massacre) + { + DamageType = NAME_None; + } + + if (extremelydead) + { // Extreme death + diestate = FindState (NAME_Death, NAME_Extreme, true); + } + if (diestate == NULL) + { // Normal death + extremelydead = false; + diestate = FindState (NAME_Death); + } + } + + if (extremelydead) + { + // We'll only get here if an actual extreme death state was used. + + // For players, mark the appropriate flag. + if (player != NULL) + { + player->cheats |= CF_EXTREMELYDEAD; + } + // If a non-player, mark as extremely dead for the crash state. + else if (health >= gibhealth) + { + health = gibhealth - 1; + } + } + + if (diestate != NULL) + { + SetState (diestate); + + if (tics > 1) + { + tics -= pr_killmobj() & 3; + if (tics < 1) + tics = 1; + } + } + else + { + Destroy(); + } +} + + + + +//--------------------------------------------------------------------------- +// +// PROC P_AutoUseHealth +// +//--------------------------------------------------------------------------- +static int CountHealth(TArray &Items) +{ + int counted = 0; + for(unsigned i = 0; i < Items.Size(); i++) + { + counted += Items[i]->Amount * Items[i]->health; + } + return counted; +} + +static int UseHealthItems(TArray &Items, int &saveHealth) +{ + int saved = 0; + + while (Items.Size() > 0 && saveHealth > 0) + { + int maxhealth = 0; + int index = -1; + + // Find the largest item in the list + for(unsigned i = 0; i < Items.Size(); i++) + { + if (Items[i]->health > maxhealth) + { + index = i; + maxhealth = Items[i]->health; + } + } + + // Now apply the health items, using the same logic as Heretic and Hexen. + int count = (saveHealth + maxhealth-1) / maxhealth; + for(int i = 0; i < count; i++) + { + saved += maxhealth; + saveHealth -= maxhealth; + if (--Items[index]->Amount == 0) + { + Items[index]->DepleteOrDestroy (); + Items.Delete(index); + break; + } + } + } + return saved; +} + +void P_AutoUseHealth(player_t *player, int saveHealth) +{ + TArray NormalHealthItems; + TArray LargeHealthItems; + + for(AInventory *inv = player->mo->Inventory; inv != NULL; inv = inv->Inventory) + { + if (inv->Amount > 0 && inv->IsKindOf(RUNTIME_CLASS(AHealthPickup))) + { + int mode = static_cast(inv)->autousemode; + + if (mode == 1) NormalHealthItems.Push(inv); + else if (mode == 2) LargeHealthItems.Push(inv); + } + } + + int normalhealth = CountHealth(NormalHealthItems); + int largehealth = CountHealth(LargeHealthItems); + + bool skilluse = !!G_SkillProperty(SKILLP_AutoUseHealth); + + if (skilluse && normalhealth >= saveHealth) + { // Use quartz flasks + player->health += UseHealthItems(NormalHealthItems, saveHealth); + } + else if (largehealth >= saveHealth) + { + // Use mystic urns + player->health += UseHealthItems(LargeHealthItems, saveHealth); + } + else if (skilluse && normalhealth + largehealth >= saveHealth) + { // Use mystic urns and quartz flasks + player->health += UseHealthItems(NormalHealthItems, saveHealth); + if (saveHealth > 0) player->health += UseHealthItems(LargeHealthItems, saveHealth); + } + player->mo->health = player->health; +} + +//============================================================================ +// +// P_AutoUseStrifeHealth +// +//============================================================================ +CVAR(Bool, sv_disableautohealth, false, CVAR_ARCHIVE|CVAR_SERVERINFO) + +void P_AutoUseStrifeHealth (player_t *player) +{ + TArray Items; + + for(AInventory *inv = player->mo->Inventory; inv != NULL; inv = inv->Inventory) + { + if (inv->Amount > 0 && inv->IsKindOf(RUNTIME_CLASS(AHealthPickup))) + { + int mode = static_cast(inv)->autousemode; + + if (mode == 3) Items.Push(inv); + } + } + + if (!sv_disableautohealth) + { + while (Items.Size() > 0) + { + int maxhealth = 0; + int index = -1; + + // Find the largest item in the list + for(unsigned i = 0; i < Items.Size(); i++) + { + if (Items[i]->health > maxhealth) + { + index = i; + maxhealth = Items[i]->Amount; + } + } + + while (player->health < 50) + { + if (!player->mo->UseInventory (Items[index])) + break; + } + if (player->health >= 50) return; + // Using all of this item was not enough so delete it and restart with the next best one + Items.Delete(index); + } + } +} + +/* +================= += += P_DamageMobj += += Damages both enemies and players += inflictor is the thing that caused the damage += creature or missile, can be NULL (slime, etc) += source is the thing to target after taking damage += creature or NULL += Source and inflictor are the same for melee attacks += source can be null for barrel explosions and other environmental stuff +================== +*/ + +static inline bool MustForcePain(AActor *target, AActor *inflictor) +{ + return (!(target->flags5 & MF5_NOPAIN) && inflictor != NULL && + (inflictor->flags6 & MF6_FORCEPAIN) && !(inflictor->flags5 & MF5_PAINLESS)); +} + +static inline bool isFakePain(AActor *target, AActor *inflictor, int damage) +{ + return ((target->flags7 & MF7_ALLOWPAIN && damage > 0) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN))); +} + + +// Returns the amount of damage actually inflicted upon the target, or -1 if +// the damage was cancelled. +int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, FName mod, int flags) +{ + unsigned ang; + player_t *player = NULL; + fixed_t thrust; + int temp; + int painchance = 0; + FState * woundstate = NULL; + PainChanceList * pc = NULL; + bool justhit = false; + bool plrDontThrust = false; + bool invulpain = false; + bool fakedPain = false; + bool forcedPain = false; + int fakeDamage = 0; + int holdDamage = 0; + int rawdamage = damage; + + if (damage < 0) damage = 0; + + if (target == NULL || !((target->flags & MF_SHOOTABLE) || (target->flags6 & MF6_VULNERABLE))) + { // Shouldn't happen + return -1; + } + + //Rather than unnecessarily call the function over and over again, let's be a little more efficient. + fakedPain = (isFakePain(target, inflictor, damage)); + forcedPain = (MustForcePain(target, inflictor)); + + // Spectral targets only take damage from spectral projectiles. + if (target->flags4 & MF4_SPECTRAL && damage < TELEFRAG_DAMAGE) + { + if (inflictor == NULL || !(inflictor->flags4 & MF4_SPECTRAL)) + { + return -1; + } + } + if (target->health <= 0) + { + if (inflictor && mod == NAME_Ice) + { + return -1; + } + else if (target->flags & MF_ICECORPSE) // frozen + { + target->tics = 1; + target->flags6 |= MF6_SHATTERING; + target->velx = target->vely = target->velz = 0; + } + return -1; + } + // [MC] Changed it to check rawdamage here for consistency, even though that doesn't actually do anything + // different here. At any rate, invulnerable is being checked before type factoring, which is then being + // checked by player cheats/invul/buddha followed by monster buddha. This is inconsistent. Don't let the + // original telefrag damage CHECK (rawdamage) be influenced by outside factors when looking at cheats/invul. + if ((target->flags2 & MF2_INVULNERABLE) && (rawdamage < TELEFRAG_DAMAGE) && (!(flags & DMG_FORCED))) + { // actor is invulnerable + if (target->player == NULL) + { + if (inflictor == NULL || (!(inflictor->flags3 & MF3_FOILINVUL) && !(flags & DMG_FOILINVUL))) + { + if (fakedPain) + { + // big mess here: What do we use for the pain threshold? + // We cannot run the various damage filters below so for consistency it needs to be 0. + damage = 0; + invulpain = true; + goto fakepain; + } + else + return -1; + } + } + else + { + // Players are optionally excluded from getting thrust by damage. + if (static_cast(target)->PlayerFlags & PPF_NOTHRUSTWHENINVUL) + { + if (fakedPain) + plrDontThrust = 1; + else + return -1; + } + } + + } + + if (inflictor != NULL) + { + if (inflictor->flags5 & MF5_PIERCEARMOR) + flags |= DMG_NO_ARMOR; + } + + MeansOfDeath = mod; + // [RH] Andy Baker's Stealth monsters + if (target->flags & MF_STEALTH) + { + target->alpha = OPAQUE; + target->visdir = -1; + } + if (target->flags & MF_SKULLFLY) + { + target->velx = target->vely = target->velz = 0; + } + + player = target->player; + if (!(flags & DMG_FORCED)) // DMG_FORCED skips all special damage checks, TELEFRAG_DAMAGE may not be reduced at all + { + if (target->flags2 & MF2_DORMANT) + { + // Invulnerable, and won't wake up + return -1; + } + + if ((rawdamage < TELEFRAG_DAMAGE) || (target->flags7 & MF7_LAXTELEFRAGDMG)) // TELEFRAG_DAMAGE may only be reduced with NOTELEFRAGPIERCE or it may not guarantee its effect. + { + if (player && damage > 1) + { + // Take half damage in trainer mode + damage = FixedMul(damage, G_SkillProperty(SKILLP_DamageFactor)); + } + // Special damage types + if (inflictor) + { + if (inflictor->flags4 & MF4_SPECTRAL) + { + if (player != NULL) + { + if (!deathmatch && inflictor->FriendPlayer > 0) + return -1; + } + else if (target->flags4 & MF4_SPECTRAL) + { + if (inflictor->FriendPlayer == 0 && !target->IsHostile(inflictor)) + return -1; + } + } + + damage = inflictor->DoSpecialDamage(target, damage, mod); + if (damage < 0) + { + return -1; + } + } + + int olddam = damage; + + if (damage > 0 && source != NULL) + { + damage = FixedMul(damage, source->DamageMultiply); + + // Handle active damage modifiers (e.g. PowerDamage) + if (damage > 0 && source->Inventory != NULL) + { + source->Inventory->ModifyDamage(damage, mod, damage, false); + } + } + // Handle passive damage modifiers (e.g. PowerProtection), provided they are not afflicted with protection penetrating powers. + if (damage > 0 && (target->Inventory != NULL) && !(flags & DMG_NO_PROTECT)) + { + target->Inventory->ModifyDamage(damage, mod, damage, true); + } + if (damage > 0 && !(flags & DMG_NO_FACTOR)) + { + damage = FixedMul(damage, target->DamageFactor); + if (damage > 0) + { + damage = DamageTypeDefinition::ApplyMobjDamageFactor(damage, mod, target->GetClass()->DamageFactors); + } + } + + if (damage >= 0) + { + damage = target->TakeSpecialDamage(inflictor, source, damage, mod); + } + + // '<0' is handled below. This only handles the case where damage gets reduced to 0. + if (damage == 0 && olddam > 0) + { + { // Still allow FORCEPAIN + if (forcedPain) + { + goto dopain; + } + else if (fakedPain) + { + goto fakepain; + } + return -1; + } + } + } + if (target->flags5 & MF5_NODAMAGE) + { + damage = 0; + } + } + if (damage < 0) + { + // any negative value means that something in the above chain has cancelled out all damage and all damage effects, including pain. + return -1; + } + // Push the target unless the source's weapon's kickback is 0. + // (i.e. Gauntlets/Chainsaw) + if (!plrDontThrust && inflictor && inflictor != target // [RH] Not if hurting own self + && !(target->flags & MF_NOCLIP) + && !(inflictor->flags2 & MF2_NODMGTHRUST) + && !(flags & DMG_THRUSTLESS) + && !(target->flags7 & MF7_DONTTHRUST) + && (source == NULL || source->player == NULL || !(source->flags2 & MF2_NODMGTHRUST))) + { + int kickback; + + if (inflictor && inflictor->projectileKickback) + kickback = inflictor->projectileKickback; + else if (!source || !source->player || !source->player->ReadyWeapon) + kickback = gameinfo.defKickback; + else + kickback = source->player->ReadyWeapon->Kickback; + + if (kickback) + { + AActor *origin = (source && (flags & DMG_INFLICTOR_IS_PUFF))? source : inflictor; + + // If the origin and target are in exactly the same spot, choose a random direction. + // (Most likely cause is from telefragging somebody during spawning because they + // haven't moved from their spawn spot at all.) + if (origin->x == target->x && origin->y == target->y) + { + ang = pr_kickbackdir.GenRand32(); + } + else + { + ang = R_PointToAngle2 (origin->x, origin->y, target->x, target->y); + } + + // Calculate this as float to avoid overflows so that the + // clamping that had to be done here can be removed. + double fltthrust; + + fltthrust = mod == NAME_MDK ? 10 : 32; + if (target->Mass > 0) + { + fltthrust = clamp((damage * 0.125 * kickback) / target->Mass, 0., fltthrust); + } + + thrust = FLOAT2FIXED(fltthrust); + + // Don't apply ultra-small damage thrust + if (thrust < FRACUNIT/100) thrust = 0; + + // make fall forwards sometimes + if ((damage < 40) && (damage > target->health) + && (target->z - origin->z > 64*FRACUNIT) + && (pr_damagemobj()&1) + // [RH] But only if not too fast and not flying + && thrust < 10*FRACUNIT + && !(target->flags & MF_NOGRAVITY) + && (inflictor == NULL || !(inflictor->flags5 & MF5_NOFORWARDFALL)) + ) + { + ang += ANG180; + thrust *= 4; + } + ang >>= ANGLETOFINESHIFT; + if (source && source->player && (flags & DMG_INFLICTOR_IS_PUFF) + && source->player->ReadyWeapon != NULL && + (source->player->ReadyWeapon->WeaponFlags & WIF_STAFF2_KICKBACK)) + { + // Staff power level 2 + target->velx += FixedMul (10*FRACUNIT, finecosine[ang]); + target->vely += FixedMul (10*FRACUNIT, finesine[ang]); + if (!(target->flags & MF_NOGRAVITY)) + { + target->velz += 5*FRACUNIT; + } + } + else + { + target->velx += FixedMul (thrust, finecosine[ang]); + target->vely += FixedMul (thrust, finesine[ang]); + } + } + } + + // [RH] Avoid friendly fire if enabled + if (!(flags & DMG_FORCED) && source != NULL && + ((player && player != source->player) || (!player && target != source)) && + target->IsTeammate (source)) + { + if (rawdamage < TELEFRAG_DAMAGE) //Use the original damage to check for telefrag amount. Don't let the now-amplified damagetypes do it. + { // Still allow telefragging :-( + damage = (int)((float)damage * level.teamdamage); + if (damage < 0) + { + return damage; + } + else if (damage == 0) + { + if (forcedPain) + { + goto dopain; + } + else if (fakedPain) + { + goto fakepain; + } + return -1; + } + } + } + + // + // player specific + // + if (player) + { + //Added by MC: Lets bots look allround for enemies if they survive an ambush. + if (player->Bot != NULL) + { + player->Bot->allround = true; + } + + // end of game hell hack + if ((target->Sector->special & 255) == dDamage_End + && damage >= target->health) + { + damage = target->health - 1; + } + + if (!(flags & DMG_FORCED)) + { + // check the real player, not a voodoo doll here for invulnerability effects + if ((rawdamage < TELEFRAG_DAMAGE && ((player->mo->flags2 & MF2_INVULNERABLE) || + (player->cheats & CF_GODMODE))) || + (player->cheats & CF_GODMODE2) || (player->mo->flags5 & MF5_NODAMAGE)) + //Absolutely no hurting if NODAMAGE is involved. Same for GODMODE2. + { // player is invulnerable, so don't hurt him + //Make sure no godmodes and NOPAIN flags are found first. + //Then, check to see if the player has NODAMAGE or ALLOWPAIN, or inflictor has CAUSEPAIN. + if ((player->cheats & CF_GODMODE) || (player->cheats & CF_GODMODE2) || (player->mo->flags5 & MF5_NOPAIN)) + return -1; + else if ((((player->mo->flags7 & MF7_ALLOWPAIN) || (player->mo->flags5 & MF5_NODAMAGE)) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN)))) + { + invulpain = true; + goto fakepain; + } + else + return -1; + } + + if (!(flags & DMG_NO_ARMOR) && player->mo->Inventory != NULL) + { + int newdam = damage; + if (damage > 0) + { + player->mo->Inventory->AbsorbDamage(damage, mod, newdam); + } + if ((rawdamage < TELEFRAG_DAMAGE) || (player->mo->flags7 & MF7_LAXTELEFRAGDMG)) //rawdamage is never modified. + { + // if we are telefragging don't let the damage value go below that magic value. Some further checks would fail otherwise. + damage = newdam; + } + + if (damage <= 0) + { + // If MF6_FORCEPAIN is set, make the player enter the pain state. + if (!(target->flags5 & MF5_NOPAIN) && inflictor != NULL && + (inflictor->flags6 & MF6_FORCEPAIN) && !(inflictor->flags5 & MF5_PAINLESS) + && (!(player->mo->flags2 & MF2_INVULNERABLE)) && (!(player->cheats & CF_GODMODE)) && (!(player->cheats & CF_GODMODE2))) + { + goto dopain; + } + return damage; + } + } + + if (damage >= player->health && rawdamage < TELEFRAG_DAMAGE + && (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch) + && !player->morphTics) + { // Try to use some inventory health + P_AutoUseHealth (player, damage - player->health + 1); + } + } + + player->health -= damage; // mirror mobj health here for Dave + // [RH] Make voodoo dolls and real players record the same health + target->health = player->mo->health -= damage; + if (player->health < 50 && !deathmatch && !(flags & DMG_FORCED)) + { + P_AutoUseStrifeHealth (player); + player->mo->health = player->health; + } + if (player->health <= 0) + { + // [SP] Buddha cheat: if the player is about to die, rescue him to 1 health. + // This does not save the player if damage >= TELEFRAG_DAMAGE, still need to + // telefrag him right? ;) (Unfortunately the damage is "absorbed" by armor, + // but telefragging should still do enough damage to kill the player) + // Ignore players that are already dead. + // [MC]Buddha2 absorbs telefrag damage, and anything else thrown their way. + if (!(flags & DMG_FORCED) && (((player->cheats & CF_BUDDHA2) || (((player->cheats & CF_BUDDHA) || (player->mo->flags7 & MF7_BUDDHA)) && (rawdamage < TELEFRAG_DAMAGE))) && (player->playerstate != PST_DEAD))) + { + // If this is a voodoo doll we need to handle the real player as well. + player->mo->health = target->health = player->health = 1; + } + else + { + player->health = 0; + } + } + player->LastDamageType = mod; + player->attacker = source; + player->damagecount += damage; // add damage after armor / invuln + if (player->damagecount > 100) + { + player->damagecount = 100; // teleport stomp does 10k points... + } + temp = damage < 100 ? damage : 100; + if (player == &players[consoleplayer]) + { + I_Tactile (40,10,40+temp*2); + } + } + else + { + // Armor for monsters. + if (!(flags & (DMG_NO_ARMOR|DMG_FORCED)) && target->Inventory != NULL && damage > 0) + { + int newdam = damage; + target->Inventory->AbsorbDamage (damage, mod, newdam); + damage = newdam; + if (damage <= 0) + { + if (fakedPain) + goto fakepain; + else + return damage; + } + } + + target->health -= damage; + } + + // + // the damage has been dealt; now deal with the consequences + // + target->DamageTypeReceived = mod; + + // If the damaging player has the power of drain, give the player 50% of the damage + // done in health. + if ( source && source->player && source->player->cheats & CF_DRAIN && !(target->flags5 & MF5_DONTDRAIN)) + { + if (!target->player || target->player != source->player) + { + if ( P_GiveBody( source, damage / 2 )) + { + S_Sound( source, CHAN_ITEM, "*drainhealth", 1, ATTN_NORM ); + } + } + } + + + if (target->health <= 0) + { + //[MC]Buddha flag for monsters. + if (!(flags & DMG_FORCED) && ((target->flags7 & MF7_BUDDHA) && (rawdamage < TELEFRAG_DAMAGE) && ((inflictor == NULL || !(inflictor->flags7 & MF7_FOILBUDDHA)) && !(flags & DMG_FOILBUDDHA)))) + { //FOILBUDDHA or Telefrag damage must kill it. + target->health = 1; + } + else + { + + // Death + target->special1 = damage; + + // use inflictor's death type if it got one. + if (inflictor && inflictor->DeathType != NAME_None) mod = inflictor->DeathType; + + // check for special fire damage or ice damage deaths + if (mod == NAME_Fire) + { + if (player && !player->morphTics) + { // Check for flame death + if (!inflictor || + ((target->health > -50) && (damage > 25)) || + !(inflictor->flags5 & MF5_SPECIALFIREDAMAGE)) + { + target->DamageType = NAME_Fire; + } + } + else + { + target->DamageType = NAME_Fire; + } + } + else + { + target->DamageType = mod; + } + if (source && source->tracer && (source->flags5 & MF5_SUMMONEDMONSTER)) + { // Minotaur's kills go to his master + // Make sure still alive and not a pointer to fighter head + if (source->tracer->player && (source->tracer->player->mo == source->tracer)) + { + source = source->tracer; + } + } + target->Die (source, inflictor, flags); + return damage; + } + } + + woundstate = target->FindState(NAME_Wound, mod); + if (woundstate != NULL) + { + int woundhealth = target->GetClass()->WoundHealth; + + if (target->health <= woundhealth) + { + target->SetState (woundstate); + return damage; + } + } + +fakepain: //Needed so we can skip the rest of the above, but still obey the original rules. + + if (!(target->flags5 & MF5_NOPAIN) && (inflictor == NULL || !(inflictor->flags5 & MF5_PAINLESS)) && + (target->player != NULL || !G_SkillProperty(SKILLP_NoPain)) && !(target->flags & MF_SKULLFLY)) + { + pc = target->GetClass()->PainChances; + painchance = target->PainChance; + if (pc != NULL) + { + int *ppc = pc->CheckKey(mod); + if (ppc != NULL) + { + painchance = *ppc; + } + } + + if (((damage >= target->PainThreshold) && (pr_damagemobj() < painchance)) + || (inflictor != NULL && (inflictor->flags6 & MF6_FORCEPAIN))) + { +dopain: + if (mod == NAME_Electric) + { + if (pr_lightning() < 96) + { + justhit = true; + FState *painstate = target->FindState(NAME_Pain, mod); + if (painstate != NULL) + target->SetState(painstate); + } + else + { // "electrocute" the target + target->renderflags |= RF_FULLBRIGHT; + if ((target->flags3 & MF3_ISMONSTER) && pr_lightning() < 128) + { + target->Howl (); + } + } + } + else + { + justhit = true; + FState *painstate = target->FindState(NAME_Pain, ((inflictor && inflictor->PainType != NAME_None) ? inflictor->PainType : mod)); + if (painstate != NULL) + target->SetState(painstate); + if (mod == NAME_PoisonCloud) + { + if ((target->flags3 & MF3_ISMONSTER) && pr_poison() < 128) + { + target->Howl (); + } + } + } + } + } + //ALLOWPAIN and CAUSEPAIN can still trigger infighting, even if no pain state is worked out. + target->reactiontime = 0; // we're awake now... + if (source) + { + if (source == target->target) + { + target->threshold = BASETHRESHOLD; + if (target->state == target->SpawnState && target->SeeState != NULL) + { + target->SetState (target->SeeState); + } + } + else if (source != target->target && target->OkayToSwitchTarget (source)) + { + // Target actor is not intent on another actor, + // so make him chase after source + + // killough 2/15/98: remember last enemy, to prevent + // sleeping early; 2/21/98: Place priority on players + + if (target->lastenemy == NULL || + (target->lastenemy->player == NULL && target->TIDtoHate == 0) || + target->lastenemy->health <= 0) + { + target->lastenemy = target->target; // remember last enemy - killough + } + target->target = source; + target->threshold = BASETHRESHOLD; + if (target->state == target->SpawnState && target->SeeState != NULL) + { + target->SetState (target->SeeState); + } + } + } + + // killough 11/98: Don't attack a friend, unless hit by that friend. + if (justhit && (target->target == source || !target->target || !target->IsFriend(target->target))) + target->flags |= MF_JUSTHIT; // fight back! + + if (invulpain) //Note that this takes into account all the cheats a player has, in terms of invulnerability. + { + return -1; //NOW we return -1! + } + return damage; +} + +void P_PoisonMobj (AActor *target, AActor *inflictor, AActor *source, int damage, int duration, int period, FName type) +{ + // Check for invulnerability. + if (!(inflictor->flags6 & MF6_POISONALWAYS)) + { + if (target->flags2 & MF2_INVULNERABLE) + { // actor is invulnerable + if (target->player == NULL) + { + if (!(inflictor->flags3 & MF3_FOILINVUL)) + { + return; + } + } + else + { + return; + } + } + } + + target->Poisoner = source; + target->PoisonDamageTypeReceived = type; + target->PoisonPeriodReceived = period; + + if (inflictor->flags6 & MF6_ADDITIVEPOISONDAMAGE) + { + target->PoisonDamageReceived += damage; + } + else + { + target->PoisonDamageReceived = damage; + } + + if (inflictor->flags6 & MF6_ADDITIVEPOISONDURATION) + { + target->PoisonDurationReceived += duration; + } + else + { + target->PoisonDurationReceived = duration; + } + +} + +bool AActor::OkayToSwitchTarget (AActor *other) +{ + if (other == this) + return false; // [RH] Don't hate self (can happen when shooting barrels) + + if (other->flags7 & MF7_NEVERTARGET) + return false; // never EVER target me! + + if (!(other->flags & MF_SHOOTABLE)) + return false; // Don't attack things that can't be hurt + + if ((flags4 & MF4_NOTARGETSWITCH) && target != NULL) + return false; // Don't switch target if not allowed + + if ((master != NULL && other->IsA(master->GetClass())) || // don't attack your master (or others of its type) + (other->master != NULL && IsA(other->master->GetClass()))) // don't attack your minion (or those of others of your type) + { + if (!IsHostile (other) && // allow target switch if other is considered hostile + (other->tid != TIDtoHate || TIDtoHate == 0) && // or has the tid we hate + other->TIDtoHate == TIDtoHate) // or has different hate information + { + return false; + } + } + + if ((other->flags3 & MF3_NOTARGET) && + (other->tid != TIDtoHate || TIDtoHate == 0) && + !IsHostile (other)) + return false; + if (threshold != 0 && !(flags4 & MF4_QUICKTORETALIATE)) + return false; + if (IsFriend (other)) + { // [RH] Friendlies don't target other friendlies + return false; + } + + int infight; + if (flags5 & MF5_NOINFIGHTING) infight=-1; + else if (level.flags2 & LEVEL2_TOTALINFIGHTING) infight=1; + else if (level.flags2 & LEVEL2_NOINFIGHTING) infight=-1; + else infight = infighting; + + if (infight < 0 && other->player == NULL && !IsHostile (other)) + { + return false; // infighting off: Non-friendlies don't target other non-friendlies + } + if (TIDtoHate != 0 && TIDtoHate == other->TIDtoHate) + return false; // [RH] Don't target "teammates" + if (other->player != NULL && (flags4 & MF4_NOHATEPLAYERS)) + return false; // [RH] Don't target players + if (target != NULL && target->health > 0 && + TIDtoHate != 0 && target->tid == TIDtoHate && pr_switcher() < 128 && + P_CheckSight (this, target)) + return false; // [RH] Don't be too quick to give up things we hate + + return true; +} + +//========================================================================== +// +// P_PoisonPlayer - Sets up all data concerning poisoning +// +// poisoner is the object directly responsible for poisoning the player, +// such as a missile. source is the actor responsible for creating the +// poisoner. +// +//========================================================================== + +bool P_PoisonPlayer (player_t *player, AActor *poisoner, AActor *source, int poison) +{ + if((player->cheats&CF_GODMODE) || (player->mo->flags2 & MF2_INVULNERABLE) || (player->cheats & CF_GODMODE2)) + { + return false; + } + if (source != NULL && source->player != player && player->mo->IsTeammate (source)) + { + poison = (int)((float)poison * level.teamdamage); + } + if (poison > 0) + { + player->poisoncount += poison; + player->poisoner = source; + if (poisoner == NULL) + { + player->poisontype = player->poisonpaintype = NAME_None; + } + else + { // We need to record these in case the poisoner disappears before poisoncount reaches 0. + player->poisontype = poisoner->DamageType; + player->poisonpaintype = poisoner->PainType != NAME_None ? poisoner->PainType : poisoner->DamageType; + } + if(player->poisoncount > 100) + { + player->poisoncount = 100; + } + } + return true; +} + +//========================================================================== +// +// P_PoisonDamage - Similar to P_DamageMobj +// +//========================================================================== + +void P_PoisonDamage (player_t *player, AActor *source, int damage, + bool playPainSound) +{ + AActor *target; + + if (player == NULL) + { + return; + } + target = player->mo; + if (target->health <= 0) + { + return; + } + if ((damage < TELEFRAG_DAMAGE && ((target->flags2 & MF2_INVULNERABLE) || + (player->cheats & CF_GODMODE))) || (player->cheats & CF_GODMODE2)) + { // target is invulnerable + return; + } + // Take half damage in trainer mode + damage = FixedMul(damage, G_SkillProperty(SKILLP_DamageFactor)); + // Handle passive damage modifiers (e.g. PowerProtection) + if (target->Inventory != NULL) + { + target->Inventory->ModifyDamage(damage, player->poisontype, damage, true); + } + // Modify with damage factors + damage = FixedMul(damage, target->DamageFactor); + if (damage > 0) + { + damage = DamageTypeDefinition::ApplyMobjDamageFactor(damage, player->poisontype, target->GetClass()->DamageFactors); + } + if (damage <= 0) + { // Damage was reduced to 0, so don't bother further. + return; + } + if (damage >= player->health + && (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch) + && !player->morphTics) + { // Try to use some inventory health + P_AutoUseHealth(player, damage - player->health+1); + } + player->health -= damage; // mirror mobj health here for Dave + if (player->health < 50 && !deathmatch) + { + P_AutoUseStrifeHealth(player); + } + if (player->health < 0) + { + player->health = 0; + } + player->attacker = source; + + // + // do the damage + // + target->health -= damage; + if (target->health <= 0) + { // Death + if ((((player->cheats & CF_BUDDHA) || (player->mo->flags7 & MF7_BUDDHA)) && damage < TELEFRAG_DAMAGE) || (player->cheats & CF_BUDDHA2)) + { // [SP] Save the player... + player->health = target->health = 1; + } + else + { + target->special1 = damage; + if (player && !player->morphTics) + { // Check for flame death + if ((player->poisontype == NAME_Fire) && (target->health > -50) && (damage > 25)) + { + target->DamageType = NAME_Fire; + } + else + { + target->DamageType = player->poisontype; + } + } + target->Die(source, source); + return; + } + } + if (!(level.time&63) && playPainSound) + { + FState *painstate = target->FindState(NAME_Pain, player->poisonpaintype); + if (painstate != NULL) + { + target->SetState(painstate); + } + } +/* + if((P_Random() < target->info->painchance) + && !(target->flags&MF_SKULLFLY)) + { + target->flags |= MF_JUSTHIT; // fight back! + P_SetMobjState(target, target->info->painstate); + } +*/ +} + + +CCMD (kill) +{ + if (argv.argc() > 1) + { + if (CheckCheatmode ()) + return; + + if (!stricmp (argv[1], "monsters")) + { + // Kill all the monsters + if (CheckCheatmode ()) + return; + + Net_WriteByte (DEM_GENERICCHEAT); + Net_WriteByte (CHT_MASSACRE); + } + else + { + Net_WriteByte (DEM_KILLCLASSCHEAT); + Net_WriteString (argv[1]); + } + } + else + { + // If suiciding is disabled, then don't do it. + if (dmflags2 & DF2_NOSUICIDE) + return; + + // Kill the player + Net_WriteByte (DEM_SUICIDE); + } + C_HideConsole (); +} + +CCMD(remove) +{ + if (argv.argc() == 2) + { + if (CheckCheatmode()) + return; + + Net_WriteByte(DEM_REMOVE); + Net_WriteString(argv[1]); + C_HideConsole(); + } + else + { + Printf("Usage: remove \n"); + return; + } + +} diff --git a/src/p_local.h b/src/p_local.h index 39ba6ea26..c1c43c5f8 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -154,7 +154,7 @@ AActor *P_SpawnMissileZAimed (AActor *source, fixed_t z, AActor *dest, PClassAct AActor *P_SpawnPlayerMissile (AActor* source, PClassActor *type); AActor *P_SpawnPlayerMissile (AActor *source, PClassActor *type, angle_t angle); AActor *P_SpawnPlayerMissile (AActor *source, fixed_t x, fixed_t y, fixed_t z, PClassActor *type, angle_t angle, - AActor **pLineTarget = NULL, AActor **MissileActor = NULL, bool nofreeaim = false); + AActor **pLineTarget = NULL, AActor **MissileActor = NULL, bool nofreeaim = false, bool noautoaim = false); void P_CheckFakeFloorTriggers (AActor *mo, fixed_t oldz, bool oldz_has_viewheight=false); @@ -180,7 +180,7 @@ bool P_Thing_Raise(AActor *thing, AActor *raiser); bool P_Thing_CanRaise(AActor *thing); PClassActor *P_GetSpawnableType(int spawnnum); void InitSpawnablesFromMapinfo(); -int P_Thing_Warp(AActor *caller, AActor *reference, fixed_t xofs, fixed_t yofs, fixed_t zofs, angle_t angle, int flags); +int P_Thing_Warp(AActor *caller, AActor *reference, fixed_t xofs, fixed_t yofs, fixed_t zofs, angle_t angle, int flags, fixed_t heightoffset); enum WARPF { @@ -244,7 +244,11 @@ fixed_t P_AproxDistance (fixed_t dx, fixed_t dy); inline int P_PointOnLineSide (fixed_t x, fixed_t y, const line_t *line) { - return DMulScale32 (y-line->v1->y, line->dx, line->v1->x-x, line->dy) > 0; + extern int P_VanillaPointOnLineSide(fixed_t x, fixed_t y, const line_t* line); + + return i_compatflags2 & COMPATF2_POINTONLINE + ? P_VanillaPointOnLineSide(x, y, line) + : DMulScale32 (y-line->v1->y, line->dx, line->v1->x-x, line->dy) > 0; } //========================================================================== @@ -258,7 +262,11 @@ inline int P_PointOnLineSide (fixed_t x, fixed_t y, const line_t *line) inline int P_PointOnDivlineSide (fixed_t x, fixed_t y, const divline_t *line) { - return DMulScale32 (y-line->y, line->dx, line->x-x, line->dy) > 0; + extern int P_VanillaPointOnDivlineSide(fixed_t x, fixed_t y, const divline_t* line); + + return (i_compatflags2 & COMPATF2_POINTONLINE) + ? P_VanillaPointOnDivlineSide(x, y, line) + : (DMulScale32 (y-line->y, line->dx, line->x-x, line->dy) > 0); } //========================================================================== diff --git a/src/p_map.cpp b/src/p_map.cpp index 9a0ca3a12..21f6f3833 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -1013,6 +1013,10 @@ bool PIT_CheckThing(AActor *thing, FCheckPosition &tm) bool solid; int damage; + // don't clip against self + if (thing == tm.thing) + return true; + if (!((thing->flags & (MF_SOLID | MF_SPECIAL | MF_SHOOTABLE)) || thing->flags6 & MF6_TOUCHY)) return true; // can't hit thing @@ -1020,10 +1024,6 @@ bool PIT_CheckThing(AActor *thing, FCheckPosition &tm) if (abs(thing->x - tm.x) >= blockdist || abs(thing->y - tm.y) >= blockdist) return true; - // don't clip against self - if (thing == tm.thing) - return true; - if ((thing->flags2 | tm.thing->flags2) & MF2_THRUACTORS) return true; diff --git a/src/p_maputl.cpp b/src/p_maputl.cpp index 3a4a4c6a0..0a39460e7 100644 --- a/src/p_maputl.cpp +++ b/src/p_maputl.cpp @@ -1523,3 +1523,92 @@ static AActor *RoughBlockCheck (AActor *mo, int index, void *param) } return NULL; } + +//=========================================================================== +// +// P_VanillaPointOnLineSide +// P_PointOnLineSide() from the initial Doom source code release +// +//=========================================================================== + +int P_VanillaPointOnLineSide(fixed_t x, fixed_t y, const line_t* line) +{ + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!line->dx) + { + if (x <= line->v1->x) + return line->dy > 0; + + return line->dy < 0; + } + if (!line->dy) + { + if (y <= line->v1->y) + return line->dx < 0; + + return line->dx > 0; + } + + dx = (x - line->v1->x); + dy = (y - line->v1->y); + + left = FixedMul ( line->dy>>FRACBITS , dx ); + right = FixedMul ( dy , line->dx>>FRACBITS ); + + if (right < left) + return 0; // front side + return 1; // back side +} + +//=========================================================================== +// +// P_VanillaPointOnDivlineSide +// P_PointOnDivlineSide() from the initial Doom source code release +// +//=========================================================================== + +int P_VanillaPointOnDivlineSide(fixed_t x, fixed_t y, const divline_t* line) +{ + fixed_t dx; + fixed_t dy; + fixed_t left; + fixed_t right; + + if (!line->dx) + { + if (x <= line->x) + return line->dy > 0; + + return line->dy < 0; + } + if (!line->dy) + { + if (y <= line->y) + return line->dx < 0; + + return line->dx > 0; + } + + dx = (x - line->x); + dy = (y - line->y); + + // try to quickly decide by looking at sign bits + if ( (line->dy ^ line->dx ^ dx ^ dy)&0x80000000 ) + { + if ( (line->dy ^ dx) & 0x80000000 ) + return 1; // (left is negative) + return 0; + } + + left = FixedMul ( line->dy>>8, dx>>8 ); + right = FixedMul ( dy>>8 , line->dx>>8 ); + + if (right < left) + return 0; // front side + return 1; // back side +} + diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 2a72314cb..e716f4e75 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -6034,7 +6034,7 @@ AActor *P_SpawnPlayerMissile (AActor *source, PClassActor *type, angle_t angle) AActor *P_SpawnPlayerMissile (AActor *source, fixed_t x, fixed_t y, fixed_t z, PClassActor *type, angle_t angle, AActor **pLineTarget, AActor **pMissileActor, - bool nofreeaim) + bool nofreeaim, bool noautoaim) { static const int angdiff[3] = { -1<<26, 1<<26, 0 }; angle_t an = angle; @@ -6047,7 +6047,7 @@ AActor *P_SpawnPlayerMissile (AActor *source, fixed_t x, fixed_t y, fixed_t z, { return NULL; } - if (source->player && source->player->ReadyWeapon && (source->player->ReadyWeapon->WeaponFlags & WIF_NOAUTOAIM)) + if (source->player && source->player->ReadyWeapon && ((source->player->ReadyWeapon->WeaponFlags & WIF_NOAUTOAIM) || noautoaim)) { // Keep exactly the same angle and pitch as the player's own aim an = angle; diff --git a/src/p_teleport.cpp b/src/p_teleport.cpp index 7fb49fe24..13c0cdbd0 100644 --- a/src/p_teleport.cpp +++ b/src/p_teleport.cpp @@ -210,7 +210,7 @@ bool P_Teleport (AActor *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, if (thing->player && (useFog || !keepOrientation) && bHaltVelocity) { // Freeze player for about .5 sec - if (thing->Inventory == NULL || thing->Inventory->GetSpeedFactor() <= FRACUNIT) + if (thing->Inventory == NULL || !thing->Inventory->GetNoTeleportFreeze()) thing->reactiontime = 18; } if (thing->flags & MF_MISSILE) diff --git a/src/p_things.cpp b/src/p_things.cpp index 092937d72..7ac50a395 100644 --- a/src/p_things.cpp +++ b/src/p_things.cpp @@ -680,7 +680,7 @@ void InitSpawnablesFromMapinfo() } -int P_Thing_Warp(AActor *caller, AActor *reference, fixed_t xofs, fixed_t yofs, fixed_t zofs, angle_t angle, int flags) +int P_Thing_Warp(AActor *caller, AActor *reference, fixed_t xofs, fixed_t yofs, fixed_t zofs, angle_t angle, int flags, fixed_t heightoffset) { if (flags & WARPF_MOVEPTR) { @@ -693,6 +693,9 @@ int P_Thing_Warp(AActor *caller, AActor *reference, fixed_t xofs, fixed_t yofs, fixed_t oldy = caller->y; fixed_t oldz = caller->z; + zofs += FixedMul(reference->height, heightoffset); + + if (!(flags & WARPF_ABSOLUTEANGLE)) { angle += (flags & WARPF_USECALLERANGLE) ? caller->angle : reference->angle; @@ -715,30 +718,13 @@ int P_Thing_Warp(AActor *caller, AActor *reference, fixed_t xofs, fixed_t yofs, if (flags & WARPF_TOFLOOR) { // set correct xy - + // now the caller's floorz should be appropriate for the assigned xy-position + // assigning position again with. + // extra unlink, link and environment calculation caller->SetOrigin( reference->x + xofs, reference->y + yofs, - reference->z); - - // now the caller's floorz should be appropriate for the assigned xy-position - // assigning position again with - - if (zofs) - { - // extra unlink, link and environment calculation - caller->SetOrigin( - caller->x, - caller->y, - caller->floorz + zofs); - } - else - { - // if there is no offset, there should be no ill effect from moving down to the already defined floor - - // A_Teleport does the same thing anyway - caller->z = caller->floorz; - } + reference->floorz + zofs); } else { diff --git a/src/p_user.cpp b/src/p_user.cpp index a113fc6bf..9cea8d7c7 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -1645,12 +1645,15 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SkullPop) if (player != NULL) { player->mo = mo; - if (player->camera == self) - { - player->camera = mo; - } player->damagecount = 32; } + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (playeringame[i] && players[i].camera == self) + { + players[i].camera = mo; + } + } return 0; } diff --git a/src/posix/i_system.cpp b/src/posix/i_system.cpp index fd6bb4c0c..84b953717 100644 --- a/src/posix/i_system.cpp +++ b/src/posix/i_system.cpp @@ -758,3 +758,9 @@ unsigned int I_MakeRNGSeed() } return seed; } + +TArray I_GetGogPaths() +{ + // GOG's Doom games are Windows only at the moment + return TArray(); +} diff --git a/src/posix/i_system.h b/src/posix/i_system.h index abda490c4..391503602 100644 --- a/src/posix/i_system.h +++ b/src/posix/i_system.h @@ -124,6 +124,8 @@ int I_PickIWad (WadStuff *wads, int numwads, bool queryiwad, int defaultiwad); // directories for IWADs if the user purchased any through Steam. TArray I_GetSteamPath(); +TArray I_GetGogPaths(); + // The ini could not be saved at exit bool I_WriteIniFailed (); diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index 8433b301b..33ca0495d 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -771,7 +771,7 @@ bool FMODSoundRenderer::Init() } #endif -#ifndef _WIN32 +#if !defined _WIN32 && !defined __APPLE__ // Try to load SDL output plugin result = Sys->setPluginPath(progdir); // Should we really look for it in the program directory? result = Sys->loadPlugin("liboutput_sdl.so", &OutputPlugin); diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 1f5d3a162..d200e083e 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -1481,6 +1481,7 @@ enum FP_Flags { FPF_AIMATANGLE = 1, FPF_TRANSFERTRANSLATION = 2, + FPF_NOAUTOAIM = 4, }; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireCustomMissile) { @@ -1520,8 +1521,9 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireCustomMissile) // Temporarily adjusts the pitch fixed_t saved_player_pitch = self->pitch; self->pitch -= pitch; - AActor *misl = P_SpawnPlayerMissile (self, x, y, z, ti, shootangle, &linetarget); + AActor * misl=P_SpawnPlayerMissile (self, x, y, z, ti, shootangle, &linetarget, NULL, false, (flags & FPF_NOAUTOAIM) != 0); self->pitch = saved_player_pitch; + // automatic handling of seeker missiles if (misl) { @@ -2096,6 +2098,9 @@ enum SIX_Flags SIXF_ORIGINATOR = 0x00800000, SIXF_TRANSFERSPRITEFRAME = 0x01000000, SIXF_TRANSFERROLL = 0x02000000, + SIXF_ISTARGET = 0x04000000, + SIXF_ISMASTER = 0x08000000, + SIXF_ISTRACER = 0x10000000, }; static bool InitSpawnedItem(AActor *self, AActor *mo, int flags) @@ -2253,6 +2258,18 @@ static bool InitSpawnedItem(AActor *self, AActor *mo, int flags) mo->roll = self->roll; } + if (flags & SIXF_ISTARGET) + { + self->target = mo; + } + if (flags & SIXF_ISMASTER) + { + self->master = mo; + } + if (flags & SIXF_ISTRACER) + { + self->tracer = mo; + } return true; } @@ -3297,6 +3314,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Respawn) self->flags |= MF_SOLID; self->height = self->GetDefault()->height; + self->radius = self->GetDefault()->radius; CALL_ACTION(A_RestoreSpecialPosition, self); if (flags & RSF_TELEFRAG) @@ -5021,8 +5039,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Warp) PARAM_ANGLE_OPT(angle) { angle = 0; } PARAM_INT_OPT(flags) { flags = 0; } PARAM_STATE_OPT(success_state) { success_state = NULL; } - - + PARAM_FIXED_OPT(heightoffset) { heightoffset = 0; } + AActor *reference; if ((flags & WARPF_USETID)) @@ -5041,7 +5059,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Warp) return numret; } - if (P_Thing_Warp(self, reference, xofs, yofs, zofs, angle, flags)) + if (P_Thing_Warp(self, reference, xofs, yofs, zofs, angle, flags, heightoffset)) { if (success_state) { @@ -5409,7 +5427,6 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropItem) // A_SetSpeed // //========================================================================== - DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpeed) { PARAM_ACTION_PROLOGUE; @@ -5425,6 +5442,29 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpeed) return 0; } +//========================================================================== +// +// A_SetFloatSpeed +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatSpeed) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT(speed); + PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; } + + AActor *ref = COPY_AAPTR(self, ptr); + + if (!ref) + { + ACTION_SET_RESULT(false); + return 0; + } + + ref->FloatSpeed = speed; + return 0; +} + //=========================================================================== // // Common A_Damage handler @@ -6110,6 +6150,27 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHigherOrLower) return numret; } +//=========================================================================== +// A_SetSpecies(str species, ptr) +// +// Sets the species of the calling actor('s pointer). +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecies) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME(species); + PARAM_INT_OPT(ptr) { ptr = AAPTR_DEFAULT; } + + AActor *mobj = COPY_AAPTR(self, ptr); + if (!mobj) + { + ACTION_SET_RESULT(false); + return 0; + } + + mobj->Species = species; + return 0; +} //=========================================================================== // @@ -6152,3 +6213,4 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMax) self->RipLevelMax = max; return 0; } + diff --git a/src/thingdef/thingdef_data.cpp b/src/thingdef/thingdef_data.cpp index 90ffe6aa3..183105591 100644 --- a/src/thingdef/thingdef_data.cpp +++ b/src/thingdef/thingdef_data.cpp @@ -330,9 +330,11 @@ static FFlagDef InventoryFlagDefs[] = DEFINE_FLAG(IF, TOSSED, AInventory, ItemFlags), DEFINE_FLAG(IF, ALWAYSRESPAWN, AInventory, ItemFlags), DEFINE_FLAG(IF, TRANSFER, AInventory, ItemFlags), + DEFINE_FLAG(IF, NOTELEPORTFREEZE, AInventory, ItemFlags), DEFINE_DEPRECATED_FLAG(PICKUPFLASH), - DEFINE_DEPRECATED_FLAG(INTERHUBSTRIP),}; + DEFINE_DEPRECATED_FLAG(INTERHUBSTRIP), +}; static FFlagDef WeaponFlagDefs[] = { diff --git a/src/version.h b/src/version.h index 3d28dd2ac..913c9bd18 100644 --- a/src/version.h +++ b/src/version.h @@ -61,11 +61,11 @@ const char *GetVersionString(); // Protocol version used in demos. // Bump it if you change existing DEM_ commands or add new ones. // Otherwise, it should be safe to leave it alone. -#define DEMOGAMEVERSION 0x21B +#define DEMOGAMEVERSION 0x21C // Minimum demo version we can play. // Bump it whenever you change or remove existing DEM_ commands. -#define MINDEMOVERSION 0x21B +#define MINDEMOVERSION 0x21C // SAVEVER is the version of the information stored in level snapshots. // Note that SAVEVER is not directly comparable to VERSION. @@ -76,7 +76,7 @@ const char *GetVersionString(); // Use 4500 as the base git save version, since it's higher than the // SVN revision ever got. -#define SAVEVER 4523 +#define SAVEVER 4524 #define SAVEVERSTRINGIFY2(x) #x #define SAVEVERSTRINGIFY(x) SAVEVERSTRINGIFY2(x) diff --git a/src/wi_stuff.cpp b/src/wi_stuff.cpp index 02492f272..8119d3802 100644 --- a/src/wi_stuff.cpp +++ b/src/wi_stuff.cpp @@ -1959,9 +1959,9 @@ void WI_drawStats (void) } else { - screen->DrawText (BigFont, CR_UNTRANSLATED, 50, 65, "KILLS", DTA_Clean, true, DTA_Shadow, true, TAG_DONE); - screen->DrawText (BigFont, CR_UNTRANSLATED, 50, 90, "ITEMS", DTA_Clean, true, DTA_Shadow, true, TAG_DONE); - screen->DrawText (BigFont, CR_UNTRANSLATED, 50, 115, "SECRETS", DTA_Clean, true, DTA_Shadow, true, TAG_DONE); + screen->DrawText (BigFont, CR_UNTRANSLATED, 50, 65, GStrings("TXT_IMKILLS"), DTA_Clean, true, DTA_Shadow, true, TAG_DONE); + screen->DrawText (BigFont, CR_UNTRANSLATED, 50, 90, GStrings("TXT_IMITEMS"), DTA_Clean, true, DTA_Shadow, true, TAG_DONE); + screen->DrawText (BigFont, CR_UNTRANSLATED, 50, 115, GStrings("TXT_IMSECRETS"), DTA_Clean, true, DTA_Shadow, true, TAG_DONE); int countpos = gameinfo.gametype==GAME_Strife? 285:270; if (sp_state >= 2) @@ -1978,7 +1978,7 @@ void WI_drawStats (void) } if (sp_state >= 8) { - screen->DrawText (BigFont, CR_UNTRANSLATED, 85, 160, "TIME", + screen->DrawText (BigFont, CR_UNTRANSLATED, 85, 160, GStrings("TXT_IMTIME"), DTA_Clean, true, DTA_Shadow, true, TAG_DONE); WI_drawTime (249, 160, cnt_time); if (wi_showtotaltime) diff --git a/src/win32/i_dijoy.cpp b/src/win32/i_dijoy.cpp index ccbc7ecdc..439e0fda7 100644 --- a/src/win32/i_dijoy.cpp +++ b/src/win32/i_dijoy.cpp @@ -309,7 +309,7 @@ FDInputJoystick::~FDInputJoystick() { Joy_GenerateButtonEvents(Axes[0].ButtonValue, 0, 2, KEY_JOYAXIS1PLUS); } - else + else if (Axes.Size() > 1) { Joy_GenerateButtonEvents(Axes[1].ButtonValue, 0, 4, KEY_JOYAXIS1PLUS); for (i = 2; i < Axes.Size(); ++i) diff --git a/src/win32/i_system.cpp b/src/win32/i_system.cpp index 6d8fb8595..ef56c7050 100644 --- a/src/win32/i_system.cpp +++ b/src/win32/i_system.cpp @@ -137,6 +137,7 @@ extern bool ConWindowHidden; // PUBLIC DATA DEFINITIONS ------------------------------------------------- CVAR (String, queryiwad_key, "shift", CVAR_GLOBALCONFIG|CVAR_ARCHIVE); +CVAR (Bool, con_debugoutput, false, 0); double PerfToSec, PerfToMillisec; UINT TimerPeriod; @@ -1053,6 +1054,31 @@ static TArray bufferedConsoleStuff; void I_PrintStr(const char *cp) { + if (con_debugoutput) + { + // Strip out any color escape sequences before writing to debug output + char * copy = new char[strlen(cp)+1]; + const char * srcp = cp; + char * dstp = copy; + + while (*srcp != 0) + { + if (*srcp!=0x1c && *srcp!=0x1d && *srcp!=0x1e && *srcp!=0x1f) + { + *dstp++=*srcp++; + } + else + { + if (srcp[1]!=0) srcp+=2; + else break; + } + } + *dstp=0; + + OutputDebugStringA(copy); + delete [] copy; + } + if (ConWindowHidden) { bufferedConsoleStuff.Push(cp); @@ -1504,30 +1530,83 @@ int I_FindClose(void *handle) static bool QueryPathKey(HKEY key, const char *keypath, const char *valname, FString &value) { - HKEY steamkey; + HKEY pathkey; DWORD pathtype; DWORD pathlen; LONG res; - if(ERROR_SUCCESS == RegOpenKeyEx(key, keypath, 0, KEY_QUERY_VALUE, &steamkey)) + if(ERROR_SUCCESS == RegOpenKeyEx(key, keypath, 0, KEY_QUERY_VALUE, &pathkey)) { - if (ERROR_SUCCESS == RegQueryValueEx(steamkey, valname, 0, &pathtype, NULL, &pathlen) && + if (ERROR_SUCCESS == RegQueryValueEx(pathkey, valname, 0, &pathtype, NULL, &pathlen) && pathtype == REG_SZ && pathlen != 0) { // Don't include terminating null in count char *chars = value.LockNewBuffer(pathlen - 1); - res = RegQueryValueEx(steamkey, valname, 0, NULL, (LPBYTE)chars, &pathlen); + res = RegQueryValueEx(pathkey, valname, 0, NULL, (LPBYTE)chars, &pathlen); value.UnlockBuffer(); if (res != ERROR_SUCCESS) { value = ""; } } - RegCloseKey(steamkey); + RegCloseKey(pathkey); } return value.IsNotEmpty(); } +//========================================================================== +// +// I_GetGogPaths +// +// Check the registry for GOG installation paths, so we can search for IWADs +// that were bought from GOG.com. This is a bit different from the Steam +// version because each game has its own independent installation path, no +// such thing as /SteamApps/common/. +// +//========================================================================== + +TArray I_GetGogPaths() +{ + TArray result; + FString path; + FString gamepath; + +#ifdef _WIN64 + FString gogregistrypath = "Software\\Wow6432Node\\GOG.com\\Games"; +#else + // If a 32-bit ZDoom runs on a 64-bit Windows, this will be transparently and + // automatically redirected to the Wow6432Node address instead, so this address + // should be safe to use in all cases. + FString gogregistrypath = "Software\\GOG.com\\Games"; +#endif + + // Look for Ultimate Doom + gamepath = gogregistrypath + "\\1435827232"; + if (QueryPathKey(HKEY_LOCAL_MACHINE, gamepath.GetChars(), "Path", path)) + { + result.Push(path); // directly in install folder + } + + // Look for Doom II + gamepath = gogregistrypath + "\\1435848814"; + if (QueryPathKey(HKEY_LOCAL_MACHINE, gamepath.GetChars(), "Path", path)) + { + result.Push(path + "/doom2"); // in a subdirectory + // If direct support for the Master Levels is ever added, they are in path + /master/wads + } + + // Look for Final Doom + gamepath = gogregistrypath + "\\1435848742"; + if (QueryPathKey(HKEY_LOCAL_MACHINE, gamepath.GetChars(), "Path", path)) + { + // in subdirectories + result.Push(path + "/TNT"); + result.Push(path + "/Plutonia"); + } + + return result; +} + //========================================================================== // // I_GetSteamPath diff --git a/src/win32/i_system.h b/src/win32/i_system.h index 566ca1977..03f4f3c0f 100644 --- a/src/win32/i_system.h +++ b/src/win32/i_system.h @@ -168,6 +168,9 @@ void I_SetWndProc(); // directories for IWADs if the user purchased any through Steam. TArray I_GetSteamPath(); +// [GZ] Same deal for GOG paths +TArray I_GetGogPaths(); + // Damn Microsoft for doing Get/SetWindowLongPtr half-assed. Instead of // giving them proper prototypes under Win32, they are just macros for // Get/SetWindowLong, meaning they take LONGs and not LONG_PTRs. diff --git a/src/zstring.cpp b/src/zstring.cpp index 510ff19d7..1aa388da9 100644 --- a/src/zstring.cpp +++ b/src/zstring.cpp @@ -583,6 +583,7 @@ void FString::SwapCase () void FString::StripLeft () { size_t max = Len(), i, j; + if (max == 0) return; for (i = 0; i < max; ++i) { if (!isspace(Chars[i])) @@ -613,6 +614,7 @@ void FString::StripLeft (const FString &charset) void FString::StripLeft (const char *charset) { size_t max = Len(), i, j; + if (max == 0) return; for (i = 0; i < max; ++i) { if (!strchr (charset, Chars[i])) @@ -665,6 +667,7 @@ void FString::StripRight (const FString &charset) void FString::StripRight (const char *charset) { size_t max = Len(), i; + if (max == 0) return; for (i = max; i-- > 0; ) { if (!strchr (charset, Chars[i])) @@ -687,6 +690,7 @@ void FString::StripRight (const char *charset) void FString::StripLeftRight () { size_t max = Len(), i, j, k; + if (max == 0) return; for (i = 0; i < max; ++i) { if (!isspace(Chars[i])) @@ -723,6 +727,7 @@ void FString::StripLeftRight (const FString &charset) void FString::StripLeftRight (const char *charset) { size_t max = Len(), i, j, k; + if (max == 0) return; for (i = 0; i < max; ++i) { if (!strchr (charset, Chars[i])) diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index 0347941ce..8bf4e5d3a 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -217,7 +217,7 @@ ACTOR Actor native //: Thinker action native A_PlayerSkinCheck(state label); action native A_BasicAttack(int meleedamage, sound meleesound, class missiletype, float missileheight); action native A_Teleport(state teleportstate = "", class targettype = "BossSpot", class fogtype = "TeleportFog", int flags = 0, float mindist = 0, float maxdist = 0, int ptr = AAPTR_DEFAULT); - action native A_Warp(int ptr_destination, float xofs = 0, float yofs = 0, float zofs = 0, float angle = 0, int flags = 0, state success_state = ""); + action native A_Warp(int ptr_destination, float xofs = 0, float yofs = 0, float zofs = 0, float angle = 0, int flags = 0, state success_state = "", float heightoffset = 0); action native A_ThrowGrenade(class itemtype, float zheight = 0, float xyvel = 0, float zvel = 0, bool useammo = true); action native A_Weave(int xspeed, int yspeed, float xdist, float ydist); @@ -272,6 +272,7 @@ ACTOR Actor native //: Thinker action native A_SetDamageType(name damagetype); action native A_DropItem(class item, int dropamount = -1, int chance = 256); action native A_SetSpeed(float speed, int ptr = AAPTR_DEFAULT); + action native A_SetFloatSpeed(float speed, int ptr = AAPTR_DEFAULT); action native A_DamageSelf(int amount, name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); action native A_DamageTarget(int amount, name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); action native A_DamageMaster(int amount, name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); @@ -299,6 +300,7 @@ ACTOR Actor native //: Thinker action native A_SetHealth(int health, int ptr = AAPTR_DEFAULT); action native A_ResetHealth(int ptr = AAPTR_DEFAULT); action native 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); diff --git a/wadsrc/static/actors/constants.txt b/wadsrc/static/actors/constants.txt index 304e6145a..0724c1109 100644 --- a/wadsrc/static/actors/constants.txt +++ b/wadsrc/static/actors/constants.txt @@ -74,6 +74,9 @@ const int SXF_NOPOINTERS = 1 << 22; const int SXF_ORIGINATOR = 1 << 23; const int SXF_TRANSFERSPRITEFRAME = 1 << 24; const int SXF_TRANSFERROLL = 1 << 25; +const int SXF_ISTARGET = 1 << 26; +const int SXF_ISMASTER = 1 << 27; +const int SXF_ISTRACER = 1 << 28; // Flags for A_Chase const int CHF_FASTCHASE = 1; @@ -185,6 +188,7 @@ const int CPF_STEALARMOR = 32; // Flags for A_CustomMissile const int FPF_AIMATANGLE = 1; const int FPF_TRANSFERTRANSLATION = 2; +const int FPF_NOAUTOAIM = 4; // Flags for A_Teleport enum diff --git a/wadsrc/static/actors/doom/fatso.txt b/wadsrc/static/actors/doom/fatso.txt index 1ad40fd93..0b3039325 100644 --- a/wadsrc/static/actors/doom/fatso.txt +++ b/wadsrc/static/actors/doom/fatso.txt @@ -30,9 +30,9 @@ ACTOR Fatso Missile: FATT G 20 A_FatRaise FATT H 10 BRIGHT A_FatAttack1 - FATT IG 5 + FATT IG 5 A_FaceTarget FATT H 10 BRIGHT A_FatAttack2 - FATT IG 5 + FATT IG 5 A_FaceTarget FATT H 10 BRIGHT A_FatAttack3 FATT IG 5 Goto See diff --git a/wadsrc/static/actors/doom/keen.txt b/wadsrc/static/actors/doom/keen.txt index 2c07e5b32..93d43764f 100644 --- a/wadsrc/static/actors/doom/keen.txt +++ b/wadsrc/static/actors/doom/keen.txt @@ -28,7 +28,7 @@ ACTOR CommanderKeen KEEN AB 6 KEEN C 6 A_Scream KEEN DEFGH 6 - KEEN I 6 A_NoBlocking + KEEN I 6 KEEN J 6 KEEN K 6 A_KeenDie KEEN L -1 diff --git a/wadsrc/static/actors/doom/spidermaster.txt b/wadsrc/static/actors/doom/spidermaster.txt index 97ebbf143..c2a742f1e 100644 --- a/wadsrc/static/actors/doom/spidermaster.txt +++ b/wadsrc/static/actors/doom/spidermaster.txt @@ -6,7 +6,7 @@ ACTOR SpiderMastermind { Health 3000 - Radius 100 + Radius 128 Height 100 Mass 1000 Speed 12 diff --git a/wadsrc/static/actors/heretic/hereticweaps.txt b/wadsrc/static/actors/heretic/hereticweaps.txt index 711c3fd29..f0db7caf2 100644 --- a/wadsrc/static/actors/heretic/hereticweaps.txt +++ b/wadsrc/static/actors/heretic/hereticweaps.txt @@ -802,6 +802,7 @@ ACTOR BlasterPuff +NOGRAVITY +PUFFONACTORS RenderStyle Add + SeeSound "weapons/blasterhit" States { Crash: diff --git a/wadsrc/static/actors/heretic/mummy.txt b/wadsrc/static/actors/heretic/mummy.txt index 46e9deaba..c06f519f6 100644 --- a/wadsrc/static/actors/heretic/mummy.txt +++ b/wadsrc/static/actors/heretic/mummy.txt @@ -121,12 +121,11 @@ ACTOR MummyFX1 States { Spawn: - FX15 A 1 Bright FX15 A 5 Bright A_PlaySound("mummy/head") FX15 B 5 Bright A_SeekerMissile(10,20) FX15 C 5 Bright FX15 B 5 Bright A_SeekerMissile(10,20) - Goto Spawn+1 + Loop Death: FX15 DEFG 5 Bright Stop diff --git a/wadsrc/static/actors/shared/inventory.txt b/wadsrc/static/actors/shared/inventory.txt index 594848781..e0901d712 100644 --- a/wadsrc/static/actors/shared/inventory.txt +++ b/wadsrc/static/actors/shared/inventory.txt @@ -10,7 +10,7 @@ ACTOR Inventory native action native A_JumpIfNoAmmo(state label); action native A_CustomPunch(int damage, bool norandom = false, int flags = CPF_USEAMMO, class pufftype = "BulletPuff", float range = 0, float lifesteal = 0, int lifestealmax = 0, class armorbonustype = "ArmorBonus"); action native A_FireBullets(float/*angle*/ spread_xy, float/*angle*/ spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", int flags = 1, float range = 0); - action native A_FireCustomMissile(class missiletype, float angle = 0, bool useammo = true, int spawnofs_xy = 0, float spawnheight = 0, bool aimatangle = false, float pitch = 0); + action native A_FireCustomMissile(class missiletype, float angle = 0, bool useammo = true, int spawnofs_xy = 0, float spawnheight = 0, int flags = 0, float pitch = 0); action native A_RailAttack(int damage, int spawnofs_xy = 0, bool useammo = true, color color1 = "", color color2 = "", int flags = 0, float maxdiff = 0, class pufftype = "BulletPuff", float/*angle*/ spread_xy = 0, float/*angle*/ spread_z = 0, float range = 0, int duration = 0, float sparsity = 1.0, float driftspeed = 1.0, class spawnclass = "none", float spawnofs_z = 0, int spiraloffset = 270); action native A_Light(int extralight); action native A_Light0(); @@ -225,6 +225,7 @@ ACTOR PowerWeaponLevel2 : Powerup native { Powerup.Duration -40 Inventory.Icon "SPINBK0" + +INVENTORY.NOTELEPORTFREEZE } ACTOR PowerSpeed : Powerup native @@ -232,6 +233,7 @@ ACTOR PowerSpeed : Powerup native Powerup.Duration -45 Speed 1.5 Inventory.Icon "SPBOOT0" + +INVENTORY.NOTELEPORTFREEZE } // Player Speed Trail (used by the Speed Powerup) ---------------------------- diff --git a/wadsrc/static/compatibility.txt b/wadsrc/static/compatibility.txt index 26f43bb25..1bd031ed8 100644 --- a/wadsrc/static/compatibility.txt +++ b/wadsrc/static/compatibility.txt @@ -55,6 +55,8 @@ A80E7EE40E0D0C76A6FBD242BE29FE27 // map15 { stairs maskedmidtex + corpsegibs + vileghosts } 10E1E2B36302D31AC4AE68C84B5DC457 // Eternal Doom MAP28 @@ -125,7 +127,6 @@ BA530202AF0BA0C6CBAE6A0C7076FB72 // Requiem map04 CBDFEFAC579A62DE8F1B48CA4A09D381 // gather2.wad map05 and darkside.wad map01 C7A2FAFB0AFB2632C50AD625CDB50E51 // Reverie map18 9E5724BC6135AA6F86EE54FD4D91F1E2 // Project X map14 -6DA6FCBA8089161BDEC6A1D3F6C8D60F // Eternal Doom map25 01899825FFEAE016D39C02A7DA4B218F // Archie map01 1D9F3AFDC2517C2E450491ED13896712 // Seej map01 0AE745A3AB86D15FB2FB74489962C421 // 6pack2 map02 @@ -320,8 +321,7 @@ F481922F4881F74760F3C0437FD5EDD0 // map03 7C1913DEE396BA26CFF22A0E9369B7D2 // Nuke Mine, e1m2 { - clearlinespecial 1107 - clearlinespecial 1108 + pointonline } 5B862477519B21B30059A466F2FF6460 // Khorus, map08 @@ -396,3 +396,13 @@ A53AE580A4AF2B5D0B0893F86914781E // TNT: Evilution map31 { setthingflags 470 2016 } + +D0139194F7817BF06F3988DFC47DB38D // Whispers of Satan map29 +{ + nopassover +} + +D7F6E9F08C39A17026349A04F8C0B0BE // Return to Hadron, e1m9 +{ + pointonline +} diff --git a/wadsrc/static/dehsupp.txt b/wadsrc/static/dehsupp.txt index 51db22e91..ae31f6830 100644 --- a/wadsrc/static/dehsupp.txt +++ b/wadsrc/static/dehsupp.txt @@ -534,7 +534,13 @@ SoundMap "skeleton/active", "skeleton/sight", "skeleton/attack", - "misc/chat" + "misc/chat", + "dog/sight", + "dog/attack", + "dog/active", + "dog/death", + "dog/pain", + }; // Names of different actor types, in original Doom 2 order diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index a01a03559..bdefa1b86 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -1281,6 +1281,14 @@ TXT_LEADBOOTSOFF = "LEAD BOOTS OFF"; TXT_LIGHTER = "You feel lighter"; TXT_GRAVITY = "Gravity weighs you down"; +// Raven intermission + +TXT_IMKILLS = "KILLS"; +TXT_IMITEMS = "ITEMS"; +TXT_IMSECRETS = "SECRETS"; +TXT_IMTIME = "TIME"; + + RAVENQUITMSG = "ARE YOU SURE YOU WANT TO QUIT?"; // Hexen strings diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index f15f91dff..b1ca44de7 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -1303,6 +1303,7 @@ OptionMenu "CompatibilityOptions" Option "Find shortest textures like Doom", "compat_SHORTTEX", "YesNo" Option "Use buggier stair building", "compat_stairs", "YesNo" Option "Use Doom's floor motion behavior", "compat_floormove", "YesNo" + Option "Use Doom's point-on-line algorithm", "compat_pointonline", "YesNo" StaticText " " StaticText "Physics Behavior",1