mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-27 22:42:57 +00:00
Single commit - destructible geometry feature
This commit is contained in:
parent
a33cc13054
commit
b911bbc424
18 changed files with 897 additions and 56 deletions
|
@ -141,6 +141,11 @@ Note: All <bool> fields default to false unless mentioned otherwise.
|
|||
// 13: Portal line.
|
||||
revealed = <bool>; // true = line is initially visible on automap.
|
||||
|
||||
health = <int>; // Amount of hitpoints for this line.
|
||||
healthgroup = <int>; // ID of destructible object to synchronize hitpoints (optional, default is 0)
|
||||
damagespecial = <bool>; // This line will call special if having health>0 and receiving damage
|
||||
deathspecial = <bool>; // This line will call special if health was reduced to 0
|
||||
|
||||
* Note about arg0str
|
||||
|
||||
For lines with ACS specials (80-86 and 226), if arg0str is present and non-null, it
|
||||
|
@ -248,17 +253,21 @@ Note: All <bool> fields default to false unless mentioned otherwise.
|
|||
color_sprites = <int>; // Material color of sprites in sector. Default is white (0xffffff)
|
||||
|
||||
|
||||
portal_ceil_blocksound = <bool> // ceiling portal blocks sound.
|
||||
portal_ceil_disabled = <bool> // ceiling portal disabled.
|
||||
portal_ceil_nopass = <bool> // ceiling portal blocks movement if true.
|
||||
portal_ceil_norender = <bool> // ceiling portal not rendered.
|
||||
portal_ceil_overlaytype = <string> // defines translucency style, can either be "translucent" or "additive". Default is "translucent".
|
||||
portal_floor_blocksound = <bool> // floor portal blocks sound.
|
||||
portal_floor_disabled = <bool> // floor portal disabled.
|
||||
portal_floor_nopass = <bool> // ceiling portal blocks movement if true.
|
||||
portal_floor_norender = <bool> // ceiling portal not rendered.
|
||||
portal_floor_overlaytype = <string> // defines translucency style, can either be "translucent" or "additive". Default is "translucent".
|
||||
portal_ceil_blocksound = <bool>; // ceiling portal blocks sound.
|
||||
portal_ceil_disabled = <bool>; // ceiling portal disabled.
|
||||
portal_ceil_nopass = <bool>; // ceiling portal blocks movement if true.
|
||||
portal_ceil_norender = <bool>; // ceiling portal not rendered.
|
||||
portal_ceil_overlaytype = <string>; // defines translucency style, can either be "translucent" or "additive". Default is "translucent".
|
||||
portal_floor_blocksound = <bool>; // floor portal blocks sound.
|
||||
portal_floor_disabled = <bool>; // floor portal disabled.
|
||||
portal_floor_nopass = <bool>; // ceiling portal blocks movement if true.
|
||||
portal_floor_norender = <bool>; // ceiling portal not rendered.
|
||||
portal_floor_overlaytype = <string>; // defines translucency style, can either be "translucent" or "additive". Default is "translucent".
|
||||
|
||||
healthfloor = <int>; // Amount of hitpoints for this sector (includes floor and bottom-outside linedef sides)
|
||||
healthfloorgroup = <int>; // ID of destructible object to synchronize hitpoints (optional, default is 0)
|
||||
healthceiling = <int>; // Amount of hitpoints for this sector (includes ceiling and top-outside linedef sides)
|
||||
healthceilinggroup = <int>; // ID of destructible object to synchronize hitpoints (optional, default is 0)
|
||||
|
||||
* Note about dropactors
|
||||
|
||||
|
|
|
@ -948,6 +948,7 @@ set (PCH_SOURCES
|
|||
p_actionfunctions.cpp
|
||||
p_ceiling.cpp
|
||||
p_conversation.cpp
|
||||
p_destructible.cpp
|
||||
p_doors.cpp
|
||||
p_effect.cpp
|
||||
p_enemy.cpp
|
||||
|
|
|
@ -152,6 +152,8 @@ DEFINE_SPECIAL(Sector_Set3DFloor, 160, -1, -1, 5)
|
|||
DEFINE_SPECIAL(Sector_SetContents, 161, -1, -1, 3)
|
||||
|
||||
// [RH] Begin new specials for ZDoom
|
||||
DEFINE_SPECIAL(Line_SetHealth, 150, 2, 2, 2)
|
||||
DEFINE_SPECIAL(Sector_SetHealth, 151, 3, 3, 3)
|
||||
DEFINE_SPECIAL(Ceiling_CrushAndRaiseDist, 168, 3, 5, 5)
|
||||
DEFINE_SPECIAL(Generic_Crusher2, 169, 5, 5, 5)
|
||||
DEFINE_SPECIAL(Sector_SetCeilingScale2, 170, 3, 3, 3)
|
||||
|
|
|
@ -191,6 +191,8 @@ enum SPAC
|
|||
SPAC_MUse = 1<<8, // monsters can use
|
||||
SPAC_MPush = 1<<9, // monsters can push
|
||||
SPAC_UseBack = 1<<10, // Can be used from the backside
|
||||
SPAC_Damage = 1<<11, // [ZZ] when linedef receives damage
|
||||
SPAC_Death = 1<<12, // [ZZ] when linedef receives damage and has 0 health
|
||||
|
||||
SPAC_PlayerActivate = (SPAC_Cross|SPAC_Use|SPAC_Impact|SPAC_Push|SPAC_AnyCross|SPAC_UseThrough|SPAC_UseBack),
|
||||
};
|
||||
|
|
|
@ -40,6 +40,8 @@
|
|||
#include "r_defs.h"
|
||||
#include "portal.h"
|
||||
#include "p_blockmap.h"
|
||||
#include "p_local.h"
|
||||
#include "p_destructible.h"
|
||||
|
||||
struct FLevelLocals
|
||||
{
|
||||
|
@ -96,6 +98,9 @@ struct FLevelLocals
|
|||
|
||||
TArray<zone_t> Zones;
|
||||
|
||||
// [ZZ] Destructible geometry information
|
||||
TMap<int, FHealthGroup> healthGroups;
|
||||
|
||||
FBlockmap blockmap;
|
||||
|
||||
// These are copies of the loaded map data that get used by the savegame code to skip unaltered fields
|
||||
|
|
|
@ -668,6 +668,14 @@ xx(Prev)
|
|||
xx(Children)
|
||||
xx(Owner)
|
||||
|
||||
xx(HealthFloor)
|
||||
xx(HealthCeiling)
|
||||
xx(DamageSpecial)
|
||||
xx(DeathSpecial)
|
||||
xx(HealthFloorGroup)
|
||||
xx(HealthCeilingGroup)
|
||||
xx(HealthGroup)
|
||||
|
||||
// USDF keywords
|
||||
xx(Amount)
|
||||
xx(Text)
|
||||
|
|
|
@ -4975,6 +4975,8 @@ enum EACSFunctions
|
|||
ACSF_Ceil,
|
||||
ACSF_ScriptCall,
|
||||
ACSF_StartSlideshow,
|
||||
ACSF_GetSectorHealth,
|
||||
ACSF_GetLineHealth,
|
||||
|
||||
// Eternity's
|
||||
ACSF_GetLineX = 300,
|
||||
|
@ -6841,6 +6843,44 @@ doplaysound: if (funcIndex == ACSF_PlayActorSound)
|
|||
G_StartSlideshow(FName(FBehavior::StaticLookupString(args[0])));
|
||||
break;
|
||||
|
||||
case ACSF_GetSectorHealth:
|
||||
{
|
||||
int part = args[1];
|
||||
FSectorTagIterator it(args[0]);
|
||||
int s = it.Next();
|
||||
if (s < 0)
|
||||
return 0;
|
||||
sector_t* ss = &level.sectors[s];
|
||||
FHealthGroup* grp;
|
||||
if (part == SECPART_Ceiling)
|
||||
{
|
||||
return (ss->healthceilinggroup && (grp = P_GetHealthGroup(ss->healthceilinggroup)))
|
||||
? grp->health : ss->healthceiling;
|
||||
}
|
||||
else if (part == SECPART_Floor)
|
||||
{
|
||||
return (ss->healthfloorgroup && (grp = P_GetHealthGroup(ss->healthfloorgroup)))
|
||||
? grp->health : ss->healthfloor;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case ACSF_GetLineHealth:
|
||||
{
|
||||
FLineIdIterator it(args[0]);
|
||||
int l = it.Next();
|
||||
if (l < 0)
|
||||
return 0;
|
||||
line_t* ll = &level.lines[l];
|
||||
if (ll->healthgroup > 0)
|
||||
{
|
||||
FHealthGroup* grp = P_GetHealthGroup(ll->healthgroup);
|
||||
if (grp) return grp->health;
|
||||
}
|
||||
|
||||
return ll->health;
|
||||
}
|
||||
|
||||
case ACSF_GetLineX:
|
||||
case ACSF_GetLineY:
|
||||
{
|
||||
|
|
532
src/p_destructible.cpp
Executable file
532
src/p_destructible.cpp
Executable file
|
@ -0,0 +1,532 @@
|
|||
#include "p_spec.h"
|
||||
#include "g_levellocals.h"
|
||||
#include "p_destructible.h"
|
||||
#include "v_text.h"
|
||||
#include "actor.h"
|
||||
#include "p_trace.h"
|
||||
#include "p_lnspec.h"
|
||||
#include "r_sky.h"
|
||||
#include "p_local.h"
|
||||
#include "p_maputl.h"
|
||||
#include "c_cvars.h"
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// [ZZ] Geometry damage logic callbacks
|
||||
//
|
||||
//==========================================================================
|
||||
void P_SetHealthGroupHealth(int group, int health)
|
||||
{
|
||||
FHealthGroup* grp = P_GetHealthGroup(group);
|
||||
if (!grp) return;
|
||||
|
||||
grp->health = health;
|
||||
|
||||
// now set health of all linked objects to the same as this one
|
||||
for (unsigned i = 0; i < grp->lines.Size(); i++)
|
||||
{
|
||||
line_t* lline = grp->lines[i];
|
||||
lline->health = health;
|
||||
}
|
||||
//
|
||||
for (unsigned i = 0; i < grp->sectors.Size(); i++)
|
||||
{
|
||||
sector_t* lsector = grp->sectors[i];
|
||||
if (lsector->healthceilinggroup == group)
|
||||
lsector->healthceiling = health;
|
||||
if (lsector->healthfloorgroup == group)
|
||||
lsector->healthfloor = health;
|
||||
}
|
||||
}
|
||||
|
||||
void P_DamageHealthGroup(FHealthGroup* grp, void* object, AActor* source, int damage, FName damagetype, int side, int part, DVector3 position)
|
||||
{
|
||||
if (!grp) return;
|
||||
int group = grp->id;
|
||||
|
||||
// now set health of all linked objects to the same as this one
|
||||
for (unsigned i = 0; i < grp->lines.Size(); i++)
|
||||
{
|
||||
line_t* lline = grp->lines[i];
|
||||
if (lline == object)
|
||||
continue;
|
||||
lline->health = grp->health + damage;
|
||||
P_DamageLinedef(lline, source, damage, damagetype, side, position, false);
|
||||
}
|
||||
//
|
||||
for (unsigned i = 0; i < grp->sectors.Size(); i++)
|
||||
{
|
||||
sector_t* lsector = grp->sectors[i];
|
||||
|
||||
if (lsector->healthceilinggroup == group && (lsector != object || part != SECPART_Ceiling))
|
||||
{
|
||||
lsector->healthceiling = grp->health + damage;
|
||||
P_DamageSector(lsector, source, damage, damagetype, SECPART_Ceiling, position, false);
|
||||
}
|
||||
|
||||
if (lsector->healthfloorgroup == group && (lsector != object || part != SECPART_Floor))
|
||||
{
|
||||
lsector->healthfloor = grp->health + damage;
|
||||
P_DamageSector(lsector, source, damage, damagetype, SECPART_Floor, position, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void P_DamageLinedef(line_t* line, AActor* source, int damage, FName damagetype, int side, DVector3 position, bool dogroups)
|
||||
{
|
||||
line->health -= damage;
|
||||
if (line->health < 0) line->health = 0;
|
||||
|
||||
// callbacks here
|
||||
// first off, call special if needed
|
||||
if ((line->activation & SPAC_Damage) || ((line->activation & SPAC_Death) && !line->health))
|
||||
{
|
||||
int activateon = SPAC_Damage;
|
||||
if (!line->health) activateon |= SPAC_Death;
|
||||
P_ActivateLine(line, source, side, activateon&line->activation, &position);
|
||||
}
|
||||
|
||||
if (dogroups && line->healthgroup)
|
||||
{
|
||||
FHealthGroup* grp = P_GetHealthGroup(line->healthgroup);
|
||||
if (grp)
|
||||
grp->health = line->health;
|
||||
P_DamageHealthGroup(grp, line, source, damage, damagetype, side, -1, position);
|
||||
}
|
||||
|
||||
//Printf("P_DamageLinedef: %d damage (type=%s, source=%p), new health = %d\n", damage, damagetype.GetChars(), source, line->health);
|
||||
}
|
||||
|
||||
void P_DamageSector(sector_t* sector, AActor* source, int damage, FName damagetype, int part, DVector3 position, bool dogroups)
|
||||
{
|
||||
int sectorhealth = (part == SECPART_Ceiling) ? sector->healthceiling : sector->healthfloor;
|
||||
int newhealth = sectorhealth - damage;
|
||||
if (newhealth < 0) newhealth = 0;
|
||||
if (part == SECPART_Ceiling)
|
||||
sector->healthceiling = newhealth;
|
||||
else sector->healthfloor = newhealth;
|
||||
|
||||
// callbacks here
|
||||
int dmg = (part == SECPART_Ceiling) ? SECSPAC_DamageCeiling : SECSPAC_DamageFloor;
|
||||
int dth = (part == SECPART_Ceiling) ? SECSPAC_DeathCeiling : SECSPAC_DeathFloor;
|
||||
if (sector->SecActTarget)
|
||||
{
|
||||
sector->TriggerSectorActions(source, dmg);
|
||||
if (newhealth == 0)
|
||||
sector->TriggerSectorActions(source, dth);
|
||||
}
|
||||
|
||||
int group = (part == SECPART_Ceiling) ? sector->healthceilinggroup : sector->healthfloorgroup;
|
||||
if (dogroups && group)
|
||||
{
|
||||
FHealthGroup* grp = P_GetHealthGroup(group);
|
||||
if (grp)
|
||||
grp->health = newhealth;
|
||||
P_DamageHealthGroup(grp, sector, source, damage, damagetype, 0, part, position);
|
||||
}
|
||||
|
||||
//Printf("P_DamageSector: %d damage (type=%s, position=%s, source=%p), new health = %d\n", damage, damagetype.GetChars(), (part == SECPART_Ceiling) ? "ceiling" : "floor", source, newhealth);
|
||||
}
|
||||
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
FHealthGroup* P_GetHealthGroup(int id)
|
||||
{
|
||||
FHealthGroup* grp = level.healthGroups.CheckKey(id);
|
||||
return grp;
|
||||
}
|
||||
|
||||
FHealthGroup* P_GetHealthGroupOrNew(int id, int health)
|
||||
{
|
||||
FHealthGroup* grp = level.healthGroups.CheckKey(id);
|
||||
if (!grp)
|
||||
{
|
||||
FHealthGroup newgrp;
|
||||
newgrp.id = id;
|
||||
newgrp.health = health;
|
||||
grp = &(level.healthGroups.Insert(id, newgrp));
|
||||
}
|
||||
return grp;
|
||||
}
|
||||
|
||||
void P_InitHealthGroups()
|
||||
{
|
||||
level.healthGroups.Clear();
|
||||
TArray<int> groupsInError;
|
||||
for (unsigned i = 0; i < level.lines.Size(); i++)
|
||||
{
|
||||
line_t* lline = &level.lines[i];
|
||||
if (lline->healthgroup > 0)
|
||||
{
|
||||
FHealthGroup* grp = P_GetHealthGroupOrNew(lline->healthgroup, lline->health);
|
||||
if (grp->health != lline->health)
|
||||
{
|
||||
Printf(TEXTCOLOR_RED "Warning: line %d in health group %d has different starting health (line health = %d, group health = %d)\n", i, lline->healthgroup, lline->health, grp->health);
|
||||
groupsInError.Push(lline->healthgroup);
|
||||
if (lline->health > grp->health)
|
||||
grp->health = lline->health;
|
||||
}
|
||||
grp->lines.Push(lline);
|
||||
}
|
||||
}
|
||||
for (unsigned i = 0; i < level.sectors.Size(); i++)
|
||||
{
|
||||
sector_t* lsector = &level.sectors[i];
|
||||
if (lsector->healthceilinggroup > 0)
|
||||
{
|
||||
FHealthGroup* grp = P_GetHealthGroupOrNew(lsector->healthceilinggroup, lsector->healthceiling);
|
||||
if (grp->health != lsector->healthceiling)
|
||||
{
|
||||
Printf(TEXTCOLOR_RED "Warning: sector ceiling %d in health group %d has different starting health (ceiling health = %d, group health = %d)\n", i, lsector->healthceilinggroup, lsector->healthceiling, grp->health);
|
||||
groupsInError.Push(lsector->healthceilinggroup);
|
||||
if (lsector->healthceiling > grp->health)
|
||||
grp->health = lsector->healthceiling;
|
||||
}
|
||||
grp->sectors.Push(lsector);
|
||||
}
|
||||
if (lsector->healthfloorgroup > 0)
|
||||
{
|
||||
FHealthGroup* grp = P_GetHealthGroupOrNew(lsector->healthfloorgroup, lsector->healthfloor);
|
||||
if (grp->health != lsector->healthfloor)
|
||||
{
|
||||
Printf(TEXTCOLOR_RED "Warning: sector floor %d in health group %d has different starting health (floor health = %d, group health = %d)\n", i, lsector->healthfloorgroup, lsector->healthfloor, grp->health);
|
||||
groupsInError.Push(lsector->healthfloorgroup);
|
||||
if (lsector->healthfloor > grp->health)
|
||||
grp->health = lsector->healthfloor;
|
||||
}
|
||||
if (lsector->healthceilinggroup != lsector->healthfloorgroup)
|
||||
grp->sectors.Push(lsector);
|
||||
}
|
||||
}
|
||||
for (unsigned i = 0; i < groupsInError.Size(); i++)
|
||||
{
|
||||
FHealthGroup* grp = P_GetHealthGroup(groupsInError[i]);
|
||||
Printf(TEXTCOLOR_GOLD "Health group %d is using the highest found health value of %d", groupsInError[i], grp->health);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// P_GeometryLineAttack
|
||||
//
|
||||
// Applies hitscan damage to geometry (lines and sectors with health>0)
|
||||
//==========================================================================
|
||||
|
||||
void P_GeometryLineAttack(FTraceResults& trace, AActor* thing, int damage, FName damageType)
|
||||
{
|
||||
// [ZZ] hitscan geometry damage logic
|
||||
//
|
||||
if (trace.HitType == TRACE_HitWall && P_CheckLinedefVulnerable(trace.Line, trace.Side))
|
||||
{
|
||||
if (trace.Tier == TIER_Lower || trace.Tier == TIER_Upper) // process back sector health if any
|
||||
{
|
||||
sector_t* backsector = (trace.Line->sidedef[!trace.Side] ? trace.Line->sidedef[!trace.Side]->sector : nullptr);
|
||||
int sectorhealth = 0;
|
||||
if (backsector && trace.Tier == TIER_Lower && backsector->healthfloor > 0 && P_CheckLinedefVulnerable(trace.Line, trace.Side, SECPART_Floor))
|
||||
sectorhealth = backsector->healthfloor;
|
||||
if (backsector && trace.Tier == TIER_Upper && backsector->healthceiling > 0 && P_CheckLinedefVulnerable(trace.Line, trace.Side, SECPART_Ceiling))
|
||||
sectorhealth = backsector->healthceiling;
|
||||
if (sectorhealth > 0)
|
||||
{
|
||||
P_DamageSector(backsector, thing, damage, damageType, (trace.Tier == TIER_Upper) ? SECPART_Ceiling : SECPART_Floor, trace.HitPos);
|
||||
}
|
||||
}
|
||||
// always process linedef health if any
|
||||
if (trace.Line->health > 0)
|
||||
{
|
||||
P_DamageLinedef(trace.Line, thing, damage, damageType, trace.Side, trace.HitPos);
|
||||
}
|
||||
// fake floors are not handled
|
||||
}
|
||||
else if (trace.HitType == TRACE_HitFloor || trace.HitType == TRACE_HitCeiling)
|
||||
{
|
||||
int sectorhealth = 0;
|
||||
if (trace.HitType == TRACE_HitFloor && trace.Sector->healthfloor > 0 && P_CheckSectorVulnerable(trace.Sector, SECPART_Floor))
|
||||
sectorhealth = trace.Sector->healthfloor;
|
||||
if (trace.HitType == TRACE_HitCeiling && trace.Sector->healthceiling > 0 && P_CheckSectorVulnerable(trace.Sector, SECPART_Ceiling))
|
||||
sectorhealth = trace.Sector->healthceiling;
|
||||
if (sectorhealth > 0)
|
||||
{
|
||||
P_DamageSector(trace.Sector, thing, damage, damageType, (trace.HitType == TRACE_HitCeiling) ? SECPART_Ceiling : SECPART_Floor, trace.HitPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// P_GeometryRadiusAttack
|
||||
//
|
||||
// Applies radius damage to destructible geometry (lines and sectors with health>0)
|
||||
//==========================================================================
|
||||
|
||||
struct pgra_data_t
|
||||
{
|
||||
DVector3 pos;
|
||||
line_t* line;
|
||||
sector_t* sector;
|
||||
int secpart;
|
||||
};
|
||||
|
||||
// we use this horizontally for 2D wall distance, and in a bit fancier way for 3D wall distance
|
||||
static DVector2 PGRA_ClosestPointOnLine2D(DVector2 x, DVector2 p1, DVector2 p2)
|
||||
{
|
||||
DVector2 p2p1 = (p2 - p1);
|
||||
DVector2 xp1 = (x - p1);
|
||||
double r = p2p1 | xp1;
|
||||
double len = p2p1.Length();
|
||||
r /= len;
|
||||
|
||||
if (r < 0)
|
||||
return p1;
|
||||
if (r > len)
|
||||
return p2;
|
||||
|
||||
return p1 + p2p1.Unit() * r;
|
||||
}
|
||||
|
||||
static void PGRA_InsertIfCloser(TMap<int, pgra_data_t>& damageGroupPos, int group, DVector3 pt, DVector3 check, sector_t* checksector, sector_t* sector, line_t* line, int secpart)
|
||||
{
|
||||
// simple solid geometry sight check between "check" and "pt"
|
||||
// expected - Trace hits nothing
|
||||
DVector3 ptVec = (pt - check);
|
||||
double ptDst = ptVec.Length() - 0.5;
|
||||
ptVec.MakeUnit();
|
||||
FTraceResults res;
|
||||
bool isblocked = Trace(check, checksector, ptVec, ptDst, 0, 0xFFFFFFFF, nullptr, res);
|
||||
if (isblocked) return;
|
||||
|
||||
pgra_data_t* existing = damageGroupPos.CheckKey(group);
|
||||
// not present or distance is closer
|
||||
if (!existing || (((*existing).pos - check).Length() > (pt - check).Length()))
|
||||
{
|
||||
if (!existing) existing = &damageGroupPos.Insert(group, pgra_data_t());
|
||||
existing->pos = pt;
|
||||
existing->line = line;
|
||||
existing->sector = sector;
|
||||
existing->secpart = secpart;
|
||||
}
|
||||
}
|
||||
|
||||
EXTERN_CVAR(Float, splashfactor);
|
||||
|
||||
void P_GeometryRadiusAttack(AActor* bombspot, AActor* bombsource, int bombdamage, int bombdistance, FName damagetype, int fulldamagedistance)
|
||||
{
|
||||
TMap<int, pgra_data_t> damageGroupPos;
|
||||
|
||||
double bombdistancefloat = 1. / (double)(bombdistance - fulldamagedistance);
|
||||
|
||||
// now, this is not entirely correct... but sector actions still _do_ require a valid source actor to trigger anything
|
||||
if (!bombspot)
|
||||
return;
|
||||
if (!bombsource)
|
||||
bombsource = bombspot;
|
||||
|
||||
// check current sector floor and ceiling. this is the only *sector* checked, otherwise only use lines
|
||||
// a bit imprecise, but should work
|
||||
sector_t* srcsector = bombspot->Sector;
|
||||
|
||||
if (srcsector->healthceiling > 0)
|
||||
{
|
||||
double dstceiling = srcsector->ceilingplane.Normal() | (bombspot->Pos() + srcsector->ceilingplane.Normal()*srcsector->ceilingplane.D);
|
||||
DVector3 spotTo = bombspot->Pos() - srcsector->ceilingplane.Normal() * dstceiling;
|
||||
int grp = srcsector->healthceilinggroup;
|
||||
if (grp <= 0)
|
||||
grp = 0x80000000 | (srcsector->sectornum & 0x7FFFFFFF);
|
||||
PGRA_InsertIfCloser(damageGroupPos, grp, spotTo, bombspot->Pos(), srcsector, srcsector, nullptr, SECPART_Ceiling);
|
||||
}
|
||||
|
||||
if (srcsector->healthfloor > 0)
|
||||
{
|
||||
double dstfloor = srcsector->floorplane.Normal() | (bombspot->Pos() + srcsector->floorplane.Normal()*srcsector->floorplane.D);
|
||||
DVector3 spotTo = bombspot->Pos() - srcsector->floorplane.Normal() * dstfloor;
|
||||
int grp = srcsector->healthfloorgroup;
|
||||
if (grp <= 0)
|
||||
grp = 0x40000000 | (srcsector->sectornum & 0x7FFFFFFF);
|
||||
PGRA_InsertIfCloser(damageGroupPos, grp, spotTo, bombspot->Pos(), srcsector, srcsector, nullptr, SECPART_Floor);
|
||||
}
|
||||
|
||||
// enumerate all lines around
|
||||
FBoundingBox bombbox(bombspot->X(), bombspot->Y(), bombdistance * 16);
|
||||
FBlockLinesIterator it(bombbox);
|
||||
line_t* ln;
|
||||
int vc = validcount;
|
||||
TArray<line_t*> lines;
|
||||
while ((ln = it.Next())) // iterator and Trace both use validcount and interfere with each other
|
||||
lines.Push(ln);
|
||||
for (int i = 0; i < lines.Size(); i++)
|
||||
{
|
||||
ln = lines[i];
|
||||
DVector2 pos2d = bombspot->Pos().XY();
|
||||
int sd = P_PointOnLineSide(pos2d, ln);
|
||||
|
||||
side_t* side = ln->sidedef[sd];
|
||||
if (!side) continue;
|
||||
sector_t* sector = side->sector;
|
||||
side_t* otherside = ln->sidedef[!sd];
|
||||
sector_t* othersector = otherside ? otherside->sector : nullptr;
|
||||
if (!ln->health && (!othersector || (!othersector->healthfloor && !othersector->healthceiling)))
|
||||
continue; // non-interactive geometry
|
||||
|
||||
DVector2 to2d = PGRA_ClosestPointOnLine2D(bombspot->Pos().XY(), side->V1()->p, side->V2()->p);
|
||||
// this gives us x/y
|
||||
double distto2d = (to2d - pos2d).Length();
|
||||
double z_top1, z_top2, z_bottom1, z_bottom2; // here, z_top1 is closest to the ceiling, and z_bottom1 is closest to the floor.
|
||||
z_top1 = sector->ceilingplane.ZatPoint(to2d);
|
||||
z_bottom1 = sector->floorplane.ZatPoint(to2d);
|
||||
DVector3 to3d_fullheight(to2d.X, to2d.Y, clamp(bombspot->Z(), z_bottom1, z_top1));
|
||||
|
||||
if (ln->health && P_CheckLinedefVulnerable(ln, sd))
|
||||
{
|
||||
bool cantdamage = false;
|
||||
bool linefullheight = othersector && !!(ln->flags & (ML_BLOCKEVERYTHING));
|
||||
// decide specific position to affect on a line.
|
||||
if (!linefullheight)
|
||||
{
|
||||
z_top2 = othersector->ceilingplane.ZatPoint(to2d);
|
||||
z_bottom2 = othersector->floorplane.ZatPoint(to2d);
|
||||
double bombz = bombspot->Z();
|
||||
if (z_top2 > z_top1)
|
||||
z_top2 = z_top1;
|
||||
if (z_bottom2 < z_bottom1)
|
||||
z_bottom2 = z_bottom1;
|
||||
if (bombz <= z_top2 && bombz >= z_bottom2) // between top and bottom opening
|
||||
to3d_fullheight.Z = (z_top2 - bombz < bombz - z_bottom2) ? z_top2 : z_bottom2;
|
||||
else if (bombz < z_bottom2 && bombz >= z_bottom1)
|
||||
to3d_fullheight.Z = clamp(bombz, z_bottom1, z_bottom2);
|
||||
else if (bombz > z_top2 && bombz <= z_top1)
|
||||
to3d_fullheight.Z = clamp(bombz, z_top2, z_top1);
|
||||
else cantdamage = true;
|
||||
}
|
||||
|
||||
if (!cantdamage)
|
||||
{
|
||||
if (ln->healthgroup)
|
||||
{
|
||||
PGRA_InsertIfCloser(damageGroupPos, ln->healthgroup, to3d_fullheight, bombspot->Pos(), srcsector, nullptr, ln, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise just damage line
|
||||
double dst = (to3d_fullheight - bombspot->Pos()).Length();
|
||||
int damage = 0;
|
||||
if (dst < bombdistance)
|
||||
{
|
||||
dst = clamp<double>(dst - (double)fulldamagedistance, 0, dst);
|
||||
damage = (int)((double)bombdamage * (1. - dst * bombdistancefloat));
|
||||
if (bombsource == bombspot)
|
||||
damage = (int)(damage * splashfactor);
|
||||
}
|
||||
P_DamageLinedef(ln, bombsource, damage, damagetype, sd, to3d_fullheight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (othersector && othersector->healthceiling && P_CheckLinedefVulnerable(ln, sd, SECPART_Ceiling))
|
||||
{
|
||||
z_top2 = othersector->ceilingplane.ZatPoint(to2d);
|
||||
if (z_top2 < z_top1) // we have front side to hit against
|
||||
{
|
||||
DVector3 to3d_upper(to2d.X, to2d.Y, clamp(bombspot->Z(), z_top2, z_top1));
|
||||
int grp = othersector->healthceilinggroup;
|
||||
if (grp <= 0)
|
||||
grp = 0x80000000 | (othersector->sectornum & 0x7FFFFFFF);
|
||||
PGRA_InsertIfCloser(damageGroupPos, grp, to3d_upper, bombspot->Pos(), srcsector, othersector, nullptr, SECPART_Ceiling);
|
||||
}
|
||||
}
|
||||
|
||||
if (othersector && othersector->healthfloor && P_CheckLinedefVulnerable(ln, sd, SECPART_Floor))
|
||||
{
|
||||
z_bottom2 = othersector->floorplane.ZatPoint(to2d);
|
||||
if (z_bottom2 > z_bottom1) // we have front side to hit against
|
||||
{
|
||||
DVector3 to3d_lower(to2d.X, to2d.Y, clamp(bombspot->Z(), z_bottom1, z_bottom2));
|
||||
int grp = othersector->healthfloorgroup;
|
||||
if (grp <= 0)
|
||||
grp = 0x40000000 | (othersector->sectornum & 0x7FFFFFFF);
|
||||
PGRA_InsertIfCloser(damageGroupPos, grp, to3d_lower, bombspot->Pos(), srcsector, othersector, nullptr, SECPART_Floor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// damage health groups and sectors.
|
||||
// if group & 0x80000000, this is sector ceiling without health group
|
||||
// if grpup & 0x40000000, this is sector floor without health group
|
||||
// otherwise this is some object in health group
|
||||
TMap<int, pgra_data_t>::ConstIterator damageGroupIt(damageGroupPos);
|
||||
TMap<int, pgra_data_t>::ConstPair* damageGroupPair;
|
||||
while (damageGroupIt.NextPair(damageGroupPair))
|
||||
{
|
||||
DVector3 pos = damageGroupPair->Value.pos;
|
||||
double dst = (pos - bombspot->Pos()).Length();
|
||||
int damage = 0;
|
||||
if (dst < bombdistance)
|
||||
{
|
||||
dst = clamp<double>(dst - (double)fulldamagedistance, 0, dst);
|
||||
damage = (int)((double)bombdamage * (1. - dst * bombdistancefloat));
|
||||
if (bombsource == bombspot)
|
||||
damage = (int)(damage * splashfactor);
|
||||
}
|
||||
|
||||
// for debug
|
||||
//P_SpawnParticle(damageGroupPair->Value.pos, DVector3(), DVector3(), PalEntry(0xFF, 0x00, 0x00), 1.0, 35, 5.0, 0.0, 0.0, 0);
|
||||
|
||||
int grp = damageGroupPair->Key;
|
||||
if (grp & 0x80000000) // sector ceiling
|
||||
{
|
||||
assert(damageGroupPair->Value.sector != nullptr);
|
||||
P_DamageSector(damageGroupPair->Value.sector, bombsource, damage, damagetype, SECPART_Ceiling, pos);
|
||||
}
|
||||
else if (grp & 0x40000000) // sector floor
|
||||
{
|
||||
assert(damageGroupPair->Value.sector != nullptr);
|
||||
P_DamageSector(damageGroupPair->Value.sector, bombsource, damage, damagetype, SECPART_Floor, pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert((damageGroupPair->Value.sector != nullptr) != (damageGroupPair->Value.line != nullptr));
|
||||
if (damageGroupPair->Value.line != nullptr)
|
||||
P_DamageLinedef(damageGroupPair->Value.line, bombsource, damage, damagetype, P_PointOnLineSide(pos.XY(), damageGroupPair->Value.line), pos);
|
||||
else P_DamageSector(damageGroupPair->Value.sector, bombsource, damage, damagetype, damageGroupPair->Value.secpart, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// P_CheckLinedefVulnerable
|
||||
//
|
||||
// If sectorpart is <0: returns if linedef is damageable directly
|
||||
// If sectorpart is valid (SECPART_ enum): returns if sector on sidedef is
|
||||
// damageable through this line at specified sectorpart
|
||||
//==========================================================================
|
||||
|
||||
bool P_CheckLinedefVulnerable(line_t* line, int side, int sectorpart)
|
||||
{
|
||||
if (line->special == Line_Horizon)
|
||||
return false;
|
||||
side_t* sidedef = line->sidedef[side];
|
||||
if (!sidedef)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// P_CheckSectorVulnerable
|
||||
//
|
||||
// Returns true if sector's floor or ceiling is directly damageable
|
||||
//==========================================================================
|
||||
|
||||
bool P_CheckSectorVulnerable(sector_t* sector, int part)
|
||||
{
|
||||
FTextureID texture = sector->GetTexture((part == SECPART_Ceiling) ? sector_t::ceiling : sector_t::floor);
|
||||
secplane_t* plane = (part == SECPART_Ceiling) ? §or->ceilingplane : §or->floorplane;
|
||||
if (texture == skyflatnum)
|
||||
return false;
|
||||
return true;
|
||||
}
|
37
src/p_destructible.h
Executable file
37
src/p_destructible.h
Executable file
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "tarray.h"
|
||||
#include "r_defs.h"
|
||||
#include "p_trace.h"
|
||||
|
||||
// [ZZ] Destructible geometry related
|
||||
struct FHealthGroup
|
||||
{
|
||||
TArray<sector_t*> sectors;
|
||||
TArray<line_t*> lines;
|
||||
int health;
|
||||
int id;
|
||||
};
|
||||
|
||||
// for P_DamageSector
|
||||
enum
|
||||
{
|
||||
SECPART_Floor = 0,
|
||||
SECPART_Ceiling = 1
|
||||
};
|
||||
|
||||
void P_InitHealthGroups();
|
||||
|
||||
void P_SetHealthGroupHealth(int group, int health);
|
||||
|
||||
FHealthGroup* P_GetHealthGroup(int id);
|
||||
FHealthGroup* P_GetHealthGroupOrNew(int id, int startinghealth);
|
||||
|
||||
void P_DamageSector(sector_t* sector, AActor* source, int damage, FName damagetype, int part, DVector3 position, bool dogroups = true);
|
||||
void P_DamageLinedef(line_t* line, AActor* source, int damage, FName damagetype, int side, DVector3 position, bool dogroups = true);
|
||||
|
||||
void P_GeometryLineAttack(FTraceResults& trace, AActor* thing, int damage, FName damageType);
|
||||
void P_GeometryRadiusAttack(AActor* bombspot, AActor* bombsource, int bombdamage, int bombdistance, FName damagetype, int fulldamagedistance);
|
||||
|
||||
bool P_CheckLinedefVulnerable(line_t* line, int side, int part = -1);
|
||||
bool P_CheckSectorVulnerable(sector_t* sector, int part);
|
|
@ -56,6 +56,7 @@
|
|||
#include "p_spec.h"
|
||||
#include "g_levellocals.h"
|
||||
#include "vm.h"
|
||||
#include "p_destructible.h"
|
||||
|
||||
// Remaps EE sector change types to Generic_Floor values. According to the Eternity Wiki:
|
||||
/*
|
||||
|
@ -3478,6 +3479,53 @@ FUNC(LS_Sector_SetCeilingGlow)
|
|||
return true;
|
||||
}
|
||||
|
||||
FUNC(LS_Line_SetHealth)
|
||||
// Line_SetHealth(id, health)
|
||||
{
|
||||
FLineIdIterator itr(arg0);
|
||||
int l;
|
||||
|
||||
if (arg1 < 0)
|
||||
arg1 = 0;
|
||||
|
||||
while ((l = itr.Next()) >= 0)
|
||||
{
|
||||
line_t* line = &level.lines[l];
|
||||
line->health = arg1;
|
||||
if (line->healthgroup)
|
||||
P_SetHealthGroupHealth(line->healthgroup, arg1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
FUNC(LS_Sector_SetHealth)
|
||||
// Sector_SetHealth(id, part, health)
|
||||
{
|
||||
FSectorTagIterator itr(arg0);
|
||||
int s;
|
||||
|
||||
if (arg2 < 0)
|
||||
arg2 = 0;
|
||||
|
||||
while ((s = itr.Next()) >= 0)
|
||||
{
|
||||
sector_t* sector = &level.sectors[s];
|
||||
if (arg1 == SECPART_Ceiling)
|
||||
{
|
||||
sector->healthceiling = arg2;
|
||||
if (sector->healthceilinggroup)
|
||||
P_SetHealthGroupHealth(sector->healthceilinggroup, arg2);
|
||||
}
|
||||
else if (arg1 == SECPART_Floor)
|
||||
{
|
||||
sector->healthfloor = arg2;
|
||||
if (sector->healthfloorgroup)
|
||||
P_SetHealthGroupHealth(sector->healthfloorgroup, arg2);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static lnSpecFunc LineSpecials[] =
|
||||
{
|
||||
/* 0 */ LS_NOP,
|
||||
|
@ -3630,8 +3678,8 @@ static lnSpecFunc LineSpecials[] =
|
|||
/* 147 */ LS_NOP,
|
||||
/* 148 */ LS_NOP,
|
||||
/* 149 */ LS_NOP,
|
||||
/* 150 */ LS_NOP,
|
||||
/* 151 */ LS_NOP,
|
||||
/* 150 */ LS_Line_SetHealth,
|
||||
/* 151 */ LS_Sector_SetHealth,
|
||||
/* 152 */ LS_NOP, // 152 Team_Score
|
||||
/* 153 */ LS_NOP, // 153 Team_GivePoints
|
||||
/* 154 */ LS_Teleport_NoStop,
|
||||
|
|
|
@ -248,7 +248,6 @@ AActor *P_RoughMonsterSearch (AActor *mo, int distance, bool onlyseekable=false,
|
|||
// P_MAP
|
||||
//
|
||||
|
||||
|
||||
struct spechit_t
|
||||
{
|
||||
line_t *line;
|
||||
|
|
|
@ -4684,6 +4684,8 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance,
|
|||
{
|
||||
if (trace.HitType != TRACE_HitActor)
|
||||
{
|
||||
P_GeometryLineAttack(trace, t1, damage, damageType);
|
||||
|
||||
// position a bit closer for puffs
|
||||
if (nointeract || trace.HitType != TRACE_HitWall || ((trace.Line->special != Line_Horizon) || spawnSky))
|
||||
{
|
||||
|
@ -5517,6 +5519,8 @@ void P_RailAttack(FRailParams *p)
|
|||
}
|
||||
}
|
||||
|
||||
P_GeometryLineAttack(trace, p->source, p->damage, damagetype);
|
||||
|
||||
// Spawn a decal or puff at the point where the trace ended.
|
||||
if (trace.HitType == TRACE_HitWall)
|
||||
{
|
||||
|
@ -6127,6 +6131,8 @@ int P_RadiusAttack(AActor *bombspot, AActor *bombsource, int bombdamage, int bom
|
|||
bombsource = bombspot;
|
||||
}
|
||||
|
||||
P_GeometryRadiusAttack(bombspot, bombsource, bombdamage, bombdistance, bombmod, fulldamagedistance);
|
||||
|
||||
int count = 0;
|
||||
while ((it.Next(&cres)))
|
||||
{
|
||||
|
|
|
@ -1938,6 +1938,31 @@ bool AActor::Massacre ()
|
|||
|
||||
void P_ExplodeMissile (AActor *mo, line_t *line, AActor *target, bool onsky)
|
||||
{
|
||||
// [ZZ] line damage callback
|
||||
if (line)
|
||||
{
|
||||
int wside = P_PointOnLineSide(mo->Pos(), line);
|
||||
int oside = !wside;
|
||||
side_t* otherside = line->sidedef[oside];
|
||||
// check if hit upper or lower part
|
||||
if (otherside)
|
||||
{
|
||||
sector_t* othersector = otherside->sector;
|
||||
double otherfloorz = othersector->floorplane.ZatPoint(mo->Pos());
|
||||
double otherceilingz = othersector->ceilingplane.ZatPoint(mo->Pos());
|
||||
double actualz = mo->Pos().Z;
|
||||
if (actualz < otherfloorz && othersector->healthfloor > 0 && P_CheckLinedefVulnerable(line, wside, SECPART_Floor))
|
||||
P_DamageSector(othersector, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, SECPART_Floor, mo->Pos());
|
||||
if (actualz > otherceilingz && othersector->healthceiling > 0 && P_CheckLinedefVulnerable(line, wside, SECPART_Ceiling))
|
||||
P_DamageSector(othersector, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, SECPART_Ceiling, mo->Pos());
|
||||
}
|
||||
|
||||
if (line->health > 0 && P_CheckLinedefVulnerable(line, wside))
|
||||
{
|
||||
P_DamageLinedef(line, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, wside, mo->Pos());
|
||||
}
|
||||
}
|
||||
|
||||
if (mo->flags3 & MF3_EXPLOCOUNT)
|
||||
{
|
||||
if (++mo->threshold < mo->DefThreshold)
|
||||
|
@ -2689,30 +2714,50 @@ double P_XYMovement (AActor *mo, DVector2 scroll)
|
|||
explode:
|
||||
// explode a missile
|
||||
bool onsky = false;
|
||||
if (tm.ceilingline &&
|
||||
tm.ceilingline->backsector &&
|
||||
tm.ceilingline->backsector->GetTexture(sector_t::ceiling) == skyflatnum &&
|
||||
mo->Z() >= tm.ceilingline->backsector->ceilingplane.ZatPoint(mo->PosRelative(tm.ceilingline)))
|
||||
if (tm.ceilingline &&
|
||||
tm.ceilingline->backsector &&
|
||||
tm.ceilingline->backsector->GetTexture(sector_t::ceiling) == skyflatnum &&
|
||||
mo->Z() >= tm.ceilingline->backsector->ceilingplane.ZatPoint(mo->PosRelative(tm.ceilingline)))
|
||||
{
|
||||
if (!(mo->flags3 & MF3_SKYEXPLODE))
|
||||
{
|
||||
if (!(mo->flags3 & MF3_SKYEXPLODE))
|
||||
{
|
||||
// Hack to prevent missiles exploding against the sky.
|
||||
// Does not handle sky floors.
|
||||
mo->Destroy();
|
||||
return Oldfloorz;
|
||||
}
|
||||
else onsky = true;
|
||||
// Hack to prevent missiles exploding against the sky.
|
||||
// Does not handle sky floors.
|
||||
mo->Destroy();
|
||||
return Oldfloorz;
|
||||
}
|
||||
// [RH] Don't explode on horizon lines.
|
||||
if (mo->BlockingLine != NULL && mo->BlockingLine->special == Line_Horizon)
|
||||
else onsky = true;
|
||||
}
|
||||
// [RH] Don't explode on horizon lines.
|
||||
if (mo->BlockingLine != NULL && mo->BlockingLine->special == Line_Horizon)
|
||||
{
|
||||
if (!(mo->flags3 & MF3_SKYEXPLODE))
|
||||
{
|
||||
if (!(mo->flags3 & MF3_SKYEXPLODE))
|
||||
{
|
||||
mo->Destroy();
|
||||
return Oldfloorz;
|
||||
}
|
||||
else onsky = true;
|
||||
mo->Destroy();
|
||||
return Oldfloorz;
|
||||
}
|
||||
else onsky = true;
|
||||
}
|
||||
if (!mo->BlockingLine && !BlockingMobj) // hit floor or ceiling while XY movement
|
||||
{
|
||||
int hitpart = -1;
|
||||
sector_t* hitsector = nullptr;
|
||||
// check against floor
|
||||
if (tm.floorsector && mo->Z() < tm.floorsector->floorplane.ZatPoint(tm.pos.XY()))
|
||||
{
|
||||
hitpart = SECPART_Floor;
|
||||
hitsector = tm.floorsector;
|
||||
if (hitsector->healthfloor > 0 && P_CheckSectorVulnerable(hitsector, hitpart))
|
||||
P_DamageSector(hitsector, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, hitpart, mo->Pos());
|
||||
}
|
||||
if (tm.ceilingsector && mo->Z() + mo->Height > tm.ceilingsector->ceilingplane.ZatPoint(tm.pos.XY()))
|
||||
{
|
||||
hitpart = SECPART_Ceiling;
|
||||
hitsector = tm.ceilingsector;
|
||||
if (hitsector->healthceiling > 0 && P_CheckSectorVulnerable(hitsector, hitpart))
|
||||
P_DamageSector(hitsector, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, hitpart, mo->Pos());
|
||||
}
|
||||
}
|
||||
P_ExplodeMissile (mo, mo->BlockingLine, BlockingMobj, onsky);
|
||||
return Oldfloorz;
|
||||
}
|
||||
|
@ -3105,6 +3150,9 @@ void P_ZMovement (AActor *mo, double oldfloorz)
|
|||
else onsky = true;
|
||||
}
|
||||
P_HitFloor (mo);
|
||||
// hit floor: direct damage callback
|
||||
if (mo->Sector->healthfloor > 0 && P_CheckSectorVulnerable(mo->Sector, SECPART_Floor))
|
||||
P_DamageSector(mo->Sector, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, SECPART_Floor, mo->Pos());
|
||||
P_ExplodeMissile (mo, NULL, NULL, onsky);
|
||||
return;
|
||||
}
|
||||
|
@ -3208,6 +3256,9 @@ void P_ZMovement (AActor *mo, double oldfloorz)
|
|||
}
|
||||
else onsky = true;
|
||||
}
|
||||
// hit ceiling: direct damage callback
|
||||
if (mo->Sector->healthceiling > 0 && P_CheckSectorVulnerable(mo->Sector, SECPART_Ceiling))
|
||||
P_DamageSector(mo->Sector, mo, mo->GetMissileDamage((mo->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1), mo->DamageType, SECPART_Ceiling, mo->Pos());
|
||||
P_ExplodeMissile (mo, NULL, NULL, onsky);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
#include "edata.h"
|
||||
#endif
|
||||
#include "events.h"
|
||||
#include "p_destructible.h"
|
||||
#include "types.h"
|
||||
#include "i_time.h"
|
||||
#include "scripting/vm/vm.h"
|
||||
|
@ -4124,6 +4125,7 @@ void P_SetupLevel (const char *lumpname, int position, bool newGame)
|
|||
screen->mVertexData->CreateVBO();
|
||||
SWRenderer->SetColormap(); //The SW renderer needs to do some special setup for the level's default colormap.
|
||||
InitPortalGroups();
|
||||
P_InitHealthGroups();
|
||||
|
||||
times[16].Clock();
|
||||
if (reloop) P_LoopSidedefs (false);
|
||||
|
|
|
@ -1120,6 +1120,21 @@ public:
|
|||
tagstring = CheckString(key);
|
||||
break;
|
||||
|
||||
case NAME_Health:
|
||||
ld->health = CheckInt(key);
|
||||
break;
|
||||
|
||||
case NAME_DamageSpecial:
|
||||
Flag(ld->activation, SPAC_Damage, key);
|
||||
break;
|
||||
|
||||
case NAME_DeathSpecial:
|
||||
Flag(ld->activation, SPAC_Death, key);
|
||||
break;
|
||||
|
||||
case NAME_HealthGroup:
|
||||
ld->healthgroup = CheckInt(key);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
@ -1362,7 +1377,6 @@ public:
|
|||
double scroll_floor_y = 0;
|
||||
FName scroll_floor_type = NAME_None;
|
||||
|
||||
|
||||
memset(sec, 0, sizeof(*sec));
|
||||
sec->lightlevel = 160;
|
||||
sec->SetXScale(sector_t::floor, 1.); // [RH] floor and ceiling scaling
|
||||
|
@ -1769,6 +1783,22 @@ public:
|
|||
// These two are used by Eternity for something I do not understand.
|
||||
//case NAME_portal_ceil_useglobaltex:
|
||||
//case NAME_portal_floor_useglobaltex:
|
||||
|
||||
case NAME_HealthFloor:
|
||||
sec->healthfloor = CheckInt(key);
|
||||
break;
|
||||
|
||||
case NAME_HealthCeiling:
|
||||
sec->healthceiling = CheckInt(key);
|
||||
break;
|
||||
|
||||
case NAME_HealthFloorGroup:
|
||||
sec->healthfloorgroup = CheckInt(key);
|
||||
break;
|
||||
|
||||
case NAME_HealthCeilingGroup:
|
||||
sec->healthceilinggroup = CheckInt(key);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
|
35
src/r_defs.h
35
src/r_defs.h
|
@ -266,17 +266,21 @@ struct FRemapTable;
|
|||
|
||||
enum
|
||||
{
|
||||
SECSPAC_Enter = 1, // Trigger when player enters
|
||||
SECSPAC_Exit = 2, // Trigger when player exits
|
||||
SECSPAC_HitFloor = 4, // Trigger when player hits floor
|
||||
SECSPAC_HitCeiling = 8, // Trigger when player hits ceiling
|
||||
SECSPAC_Use = 16, // Trigger when player uses
|
||||
SECSPAC_UseWall = 32, // Trigger when player uses a wall
|
||||
SECSPAC_EyesDive = 64, // Trigger when player eyes go below fake floor
|
||||
SECSPAC_EyesSurface = 128, // Trigger when player eyes go above fake floor
|
||||
SECSPAC_EyesBelowC = 256, // Trigger when player eyes go below fake ceiling
|
||||
SECSPAC_EyesAboveC = 512, // Trigger when player eyes go above fake ceiling
|
||||
SECSPAC_HitFakeFloor= 1024, // Trigger when player hits fake floor
|
||||
SECSPAC_Enter = 1<< 0, // Trigger when player enters
|
||||
SECSPAC_Exit = 1<< 1, // Trigger when player exits
|
||||
SECSPAC_HitFloor = 1<< 2, // Trigger when player hits floor
|
||||
SECSPAC_HitCeiling = 1<< 3, // Trigger when player hits ceiling
|
||||
SECSPAC_Use = 1<< 4, // Trigger when player uses
|
||||
SECSPAC_UseWall = 1<< 5, // Trigger when player uses a wall
|
||||
SECSPAC_EyesDive = 1<< 6, // Trigger when player eyes go below fake floor
|
||||
SECSPAC_EyesSurface = 1<< 7, // Trigger when player eyes go above fake floor
|
||||
SECSPAC_EyesBelowC = 1<< 8, // Trigger when player eyes go below fake ceiling
|
||||
SECSPAC_EyesAboveC = 1<< 9, // Trigger when player eyes go above fake ceiling
|
||||
SECSPAC_HitFakeFloor= 1<<10, // Trigger when player hits fake floor
|
||||
SECSPAC_DamageFloor = 1<<11, // Trigger when floor is damaged
|
||||
SECSPAC_DamageCeiling=1<<12, // Trigger when ceiling is damaged
|
||||
SECSPAC_DeathFloor = 1<<13, // Trigger when floor has 0 hp
|
||||
SECSPAC_DeathCeiling= 1<<14, // Trigger when ceiling has 0 hp
|
||||
};
|
||||
|
||||
struct secplane_t
|
||||
|
@ -1090,6 +1094,13 @@ public:
|
|||
INVALIDATE_OTHER = 2
|
||||
};
|
||||
|
||||
// [ZZ] these are for destructible sectors.
|
||||
// default is 0, which means no special behavior
|
||||
int healthfloor;
|
||||
int healthceiling;
|
||||
int healthfloorgroup;
|
||||
int healthceilinggroup;
|
||||
|
||||
};
|
||||
|
||||
struct ReverbContainer;
|
||||
|
@ -1307,6 +1318,8 @@ struct line_t
|
|||
unsigned portalindex;
|
||||
unsigned portaltransferred;
|
||||
AutomapLineStyle automapstyle;
|
||||
int health; // [ZZ] for destructible geometry (0 = no special behavior)
|
||||
int healthgroup; // [ZZ] this is the "destructible object" id
|
||||
|
||||
DVector2 Delta() const
|
||||
{
|
||||
|
|
|
@ -88,6 +88,10 @@ DoomEdNums
|
|||
9503 = "$SetCeilingSlope"
|
||||
9510 = "$CopyFloorPlane"
|
||||
9511 = "$CopyCeilingPlane"
|
||||
9600 = SecActDamageFloor
|
||||
9601 = SecActDamageCeiling
|
||||
9602 = SecActDeathFloor
|
||||
9603 = SecActDeathCeiling
|
||||
9800 = PointLight
|
||||
9801 = PointLightPulse
|
||||
9802 = PointLightFlicker
|
||||
|
|
|
@ -4,17 +4,21 @@ class SectorAction : Actor
|
|||
// self class uses health to define the activation type.
|
||||
enum EActivation
|
||||
{
|
||||
SECSPAC_Enter = 1,
|
||||
SECSPAC_Exit = 2,
|
||||
SECSPAC_HitFloor = 4,
|
||||
SECSPAC_HitCeiling = 8,
|
||||
SECSPAC_Use = 16,
|
||||
SECSPAC_UseWall = 32,
|
||||
SECSPAC_EyesDive = 64,
|
||||
SECSPAC_EyesSurface = 128,
|
||||
SECSPAC_EyesBelowC = 256,
|
||||
SECSPAC_EyesAboveC = 512,
|
||||
SECSPAC_HitFakeFloor= 1024,
|
||||
SECSPAC_Enter = 1<< 0,
|
||||
SECSPAC_Exit = 1<< 1,
|
||||
SECSPAC_HitFloor = 1<< 2,
|
||||
SECSPAC_HitCeiling = 1<< 3,
|
||||
SECSPAC_Use = 1<< 4,
|
||||
SECSPAC_UseWall = 1<< 5,
|
||||
SECSPAC_EyesDive = 1<< 6,
|
||||
SECSPAC_EyesSurface = 1<< 7,
|
||||
SECSPAC_EyesBelowC = 1<< 8,
|
||||
SECSPAC_EyesAboveC = 1<< 9,
|
||||
SECSPAC_HitFakeFloor = 1<<10,
|
||||
SECSPAC_DamageFloor = 1<<11,
|
||||
SECSPAC_DamageCeiling = 1<<12,
|
||||
SECSPAC_DeathFloor = 1<<13,
|
||||
SECSPAC_DeathCeiling = 1<<14
|
||||
};
|
||||
|
||||
default
|
||||
|
@ -200,6 +204,54 @@ class SecActHitFakeFloor : SectorAction
|
|||
}
|
||||
}
|
||||
|
||||
// Triggered when sector's floor is damaged ----------------------------------
|
||||
class SecActDamageFloor : SectorAction
|
||||
{
|
||||
Default
|
||||
{
|
||||
Health SECSPAC_DamageFloor;
|
||||
}
|
||||
|
||||
// [ZZ] damage is unconditional, so this as well
|
||||
override bool CanTrigger (Actor triggerer) { return !!special; }
|
||||
}
|
||||
|
||||
// Triggered when sector's ceiling is damaged ----------------------------------
|
||||
class SecActDamageCeiling : SectorAction
|
||||
{
|
||||
Default
|
||||
{
|
||||
Health SECSPAC_DamageCeiling;
|
||||
}
|
||||
|
||||
// [ZZ] damage is unconditional, so this as well
|
||||
override bool CanTrigger (Actor triggerer) { return !!special; }
|
||||
}
|
||||
|
||||
// Triggered when sector's floor is reduced to 0 hp ----------------------------------
|
||||
class SecActDeathFloor : SectorAction
|
||||
{
|
||||
Default
|
||||
{
|
||||
Health SECSPAC_DeathFloor;
|
||||
}
|
||||
|
||||
// [ZZ] damage is unconditional, so this as well
|
||||
override bool CanTrigger (Actor triggerer) { return !!special; }
|
||||
}
|
||||
|
||||
// Triggered when sector's ceiling is reduced to 0 hp ----------------------------------
|
||||
class SecActDeathCeiling : SectorAction
|
||||
{
|
||||
Default
|
||||
{
|
||||
Health SECSPAC_DeathCeiling;
|
||||
}
|
||||
|
||||
// [ZZ] damage is unconditional, so this as well
|
||||
override bool CanTrigger (Actor triggerer) { return !!special; }
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Music changer. Uses the sector action class to do its job
|
||||
|
|
Loading…
Reference in a new issue