From 3af08ecbac7d27ea069f516f067244ef8466d111 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 23 Dec 2022 17:58:02 +0100 Subject: [PATCH] - scriptified and consolidated the hitscan attacks. --- source/games/duke/src/funct.h | 1 - source/games/duke/src/player.cpp | 34 -- source/games/duke/src/player_d.cpp | 234 ------------ source/games/duke/src/player_r.cpp | 235 ------------ wadsrc/static/zscript.txt | 1 + .../games/duke/actors/_placeholders.zs | 36 -- .../games/duke/actors/dukeweapons/hitscan.zs | 355 ++++++++++++++++++ 7 files changed, 356 insertions(+), 540 deletions(-) create mode 100644 wadsrc/static/zscript/games/duke/actors/dukeweapons/hitscan.zs diff --git a/source/games/duke/src/funct.h b/source/games/duke/src/funct.h index 7f83f3904..128294004 100644 --- a/source/games/duke/src/funct.h +++ b/source/games/duke/src/funct.h @@ -106,7 +106,6 @@ void playerLookUp(int snum, ESyncBits actions); void playerLookDown(int snum, ESyncBits actions); void playerAimUp(int snum, ESyncBits actions); void playerAimDown(int snum, ESyncBits actions); -void tracers(const DVector3& start, const DVector3& dest, int n); DDukeActor* aim(DDukeActor* s, int aang, bool force = true); DDukeActor* aim_(DDukeActor* actor, DDukeActor* weapon); void checkweapons(player_struct* const p); diff --git a/source/games/duke/src/player.cpp b/source/games/duke/src/player.cpp index 1c6aee0e1..de2201fde 100644 --- a/source/games/duke/src/player.cpp +++ b/source/games/duke/src/player.cpp @@ -129,40 +129,6 @@ void forceplayerangle(player_struct* p) // //--------------------------------------------------------------------------- -void tracers(const DVector3& start, const DVector3& dest, int n) -{ - sectortype* sect = nullptr; - - auto direction = dest - start; - - if (direction.XY().Sum() < 192.75) - return; - - auto pos = start; - auto add = direction / (n + 1); - for (int i = n; i > 0; i--) - { - pos += add; - updatesector(pos, §); - if (sect) - { - if (sect->lotag == 2) - { - DVector2 scale(0.0625 + (krand() & 3) * REPEAT_SCALE, 0.0625 + (krand() & 3) * REPEAT_SCALE); - CreateActor(sect, pos, PClass::FindActor(NAME_DukeWaterBubble), -32, scale, randomAngle(), 0., 0., ps[0].GetActor(), 5); - } - else - CreateActor(sect, pos, PClass::FindActor(NAME_DukeSmallSmoke), -32, DVector2(0.21875, 0.21875), nullAngle, 0., 0., ps[0].GetActor(), 5); - } - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - double hits(DDukeActor* actor) { double zoff; diff --git a/source/games/duke/src/player_d.cpp b/source/games/duke/src/player_d.cpp index e1202caf3..4a79c62e0 100644 --- a/source/games/duke/src/player_d.cpp +++ b/source/games/duke/src/player_d.cpp @@ -81,234 +81,6 @@ void incur_damage_d(player_struct* p) } -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - -static void shootweapon(DDukeActor *actor, int p, DVector3 pos, DAngle ang, int atwith) -{ - auto sectp = actor->sector(); - double vel = 1024, zvel = 0; - HitInfo hit{}; - - if (actor->spr.extra >= 0) actor->spr.shade = -96; - - if (p >= 0) - { - SetGameVarID(g_iAimAngleVarID, AUTO_AIM_ANGLE, actor, p); - OnEvent(EVENT_GETAUTOAIMANGLE, p, ps[p].GetActor(), -1); - int varval = GetGameVarID(g_iAimAngleVarID, actor, p).value(); - DDukeActor* aimed = nullptr; - if (varval > 0) - { - aimed = aim(actor, varval); - } - - if (aimed) - { - auto tex = TexMan.GetGameTexture(aimed->spr.spritetexture()); - double dal = ((aimed->spr.scale.X * tex->GetDisplayHeight()) * 0.5) + 5; - switch (aimed->spr.picnum) - { - case DTILE_GREENSLIME: - case DTILE_ROTATEGUN: - dal -= 8; - break; - } - double dist = (ps[p].GetActor()->spr.pos.XY() - aimed->spr.pos.XY()).Length(); - zvel = ((aimed->spr.pos.Z - pos.Z - dal) * 16) / dist; - ang = (aimed->spr.pos - pos).Angle(); - } - - if (isWW2GI()) - { - int angRange = 32; - double zRange = 1; - SetGameVarID(g_iAngRangeVarID, 32, actor, p); - SetGameVarID(g_iZRangeVarID, 256, actor, p); - OnEvent(EVENT_GETSHOTRANGE, p, ps[p].GetActor(), -1); - angRange = GetGameVarID(g_iAngRangeVarID, actor, p).value(); - zRange = GetGameVarID(g_iZRangeVarID, actor, p).value() / 256.; - - ang += DAngle::fromBuild((angRange / 2) - (krand() & (angRange - 1))); - if (aimed == nullptr) - { - // no target - setFreeAimVelocity(vel, zvel, ps[p].Angles.getPitchWithView(), 16.); - } - zvel += (zRange / 2) - krandf(zRange); - } - else if (aimed == nullptr || atwith != DTILE_SHOTSPARK1) - { - ang += DAngle22_5 / 8 - randomAngle(22.5 / 4); - if (aimed == nullptr) setFreeAimVelocity(vel, zvel, ps[p].Angles.getPitchWithView(), 16.); - zvel += 0.5 - krandf(1); - } - - pos.Z -= 2; - } - else - { - double x; - int j = findplayer(actor, &x); - pos.Z -= 4; - double dist = (ps[j].GetActor()->spr.pos.XY() - actor->spr.pos.XY()).Length(); - zvel = ((ps[j].GetActor()->getOffsetZ() - pos.Z) * 16) / dist; - zvel += 0.5 - krandf(1); - if (actor->spr.picnum != DTILE_BOSS1) - { - ang += DAngle22_5 / 8 - randomAngle(22.5 / 4); - } - else - { - ang = (ps[j].GetActor()->spr.pos.XY() - pos.XY()).Angle() + DAngle22_5 / 2 - randomAngle(22.5); - } - } - - actor->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL; - hitscan(pos, sectp, DVector3(ang.ToVector() * vel, zvel * 64), hit, CLIPMASK1); - actor->spr.cstat |= CSTAT_SPRITE_BLOCK_ALL; - - - if (hit.hitSector == nullptr) return; - - if ((krand() & 15) == 0 && hit.hitSector->lotag == 2) - tracers(hit.hitpos, pos, 8 - (ud.multimode >> 1)); - - DDukeActor* spark; - if (p >= 0) - { - spark = CreateActor(hit.hitSector, hit.hitpos, DTILE_SHOTSPARK1, -15, DVector2(0.15625, 0.15625), ang, 0., 0., actor, 4); - if (!spark) return; - - spark->spr.extra = ScriptCode[gs.actorinfo[atwith].scriptaddress]; - spark->spr.extra += (krand() % 6); - - if (hit.hitWall == nullptr && hit.actor() == nullptr) - { - if (zvel < 0) - { - if (hit.hitSector->ceilingstat & CSTAT_SECTOR_SKY) - { - spark->spr.scale = DVector2(0, 0); - return; - } - else - checkhitceiling(hit.hitSector); - } - spawn(spark, PClass::FindActor(NAME_DukeSmallSmoke)); - } - - if (hit.actor()) - { - checkhitsprite(hit.actor(), spark); - if (hit.actor()->isPlayer() && (ud.coop != 1 || ud.ffire == 1)) - { - spark->spr.scale = DVector2(0, 0); - auto jib = spawn(spark, DTILE_JIBS6); - if (jib) - { - jib->spr.pos.Z += 4; - jib->vel.X = 1; - jib->spr.scale = DVector2(0.375, 0.375); - jib->spr.Angles.Yaw += DAngle22_5 / 2 - randomAngle(22.5); - } - } - else spawn(spark, PClass::FindActor(NAME_DukeSmallSmoke)); - - if (p >= 0 && isshootableswitch(hit.actor()->spr.spritetexture())) - { - checkhitswitch(p, nullptr, hit.actor()); - return; - } - } - else if (hit.hitWall) - { - spawn(spark, PClass::FindActor(NAME_DukeSmallSmoke)); - - if (isadoorwall(hit.hitWall->walltexture) == 1) - goto SKIPBULLETHOLE; - if (isablockdoor(hit.hitWall->walltexture) == 1) - goto SKIPBULLETHOLE; - if (p >= 0 && isshootableswitch(hit.hitWall->walltexture)) - { - checkhitswitch(p, hit.hitWall, nullptr); - return; - } - - if (hit.hitWall->hitag != 0 || (hit.hitWall->twoSided() && hit.hitWall->nextWall()->hitag != 0)) - goto SKIPBULLETHOLE; - - if (hit.hitSector && hit.hitSector->lotag == 0) - if (!(tileflags(hit.hitWall->overtexture) & TFLAG_FORCEFIELD)) - if ((hit.hitWall->twoSided() && hit.hitWall->nextSector()->lotag == 0) || - (!hit.hitWall->twoSided() && hit.hitSector->lotag == 0)) - if ((hit.hitWall->cstat & CSTAT_WALL_MASKED) == 0) - { - if (hit.hitWall->twoSided()) - { - DukeSectIterator it(hit.hitWall->nextSector()); - while (auto l = it.Next()) - { - if (l->spr.statnum == STAT_EFFECTOR && l->spr.lotag == SE_13_EXPLOSIVE) - goto SKIPBULLETHOLE; - } - } - - DukeStatIterator it(STAT_MISC); - while (auto l = it.Next()) - { - if (l->spr.picnum == DTILE_BULLETHOLE) - if ((l->spr.pos - spark->spr.pos).Length() < 0.75 + krandf(0.5)) - goto SKIPBULLETHOLE; - } - auto hole = spawn(spark, DTILE_BULLETHOLE); - if (hole) - { - hole->vel.X = -1 / 16.; - hole->spr.Angles.Yaw = hit.hitWall->delta().Angle() - DAngle90; - ssp(hole, CLIPMASK0); - hole->spr.cstat2 |= CSTAT2_SPRITE_DECAL; - } - } - - SKIPBULLETHOLE: - - if (hit.hitWall->cstat & CSTAT_WALL_BOTTOM_SWAP) - if (hit.hitWall->twoSided()) - if (hit.hitpos.Z >= hit.hitWall->nextSector()->floorz) - hit.hitWall = hit.hitWall->nextWall(); - - checkhitwall(spark, hit.hitWall, hit.hitpos); - } - } - else - { - spark = CreateActor(hit.hitSector, hit.hitpos, DTILE_SHOTSPARK1, -15, DVector2(0.375, 0.375), ang, 0., 0., actor, 4); - if (spark) - { - spark->spr.extra = ScriptCode[gs.actorinfo[atwith].scriptaddress]; - - if (hit.actor()) - { - checkhitsprite(hit.actor(), spark); - if (!hit.actor()->isPlayer()) - spawn(spark, PClass::FindActor(NAME_DukeSmallSmoke)); - else spark->spr.scale = DVector2(0, 0); - } - else if (hit.hitWall) - checkhitwall(spark, hit.hitWall, hit.hitpos); - } - } - - if ((krand() & 255) < 4) - { - S_PlaySound3D(PISTOL_RICOCHET, spark, hit.hitpos); - } -} - //--------------------------------------------------------------------------- // // @@ -617,12 +389,6 @@ void shoot_d(DDukeActor* actor, int atwith, PClass *cls) switch (atwith) { - case DTILE_SHOTSPARK1: - case DTILE_SHOTGUN: - case DTILE_CHAINGUN: - shootweapon(actor, p, spos, sang, atwith); - return; - case DTILE_FIRELASER: case DTILE_SPIT: case DTILE_COOLEXPLOSION1: diff --git a/source/games/duke/src/player_r.cpp b/source/games/duke/src/player_r.cpp index 8494dcc92..96b0e8036 100644 --- a/source/games/duke/src/player_r.cpp +++ b/source/games/duke/src/player_r.cpp @@ -83,235 +83,6 @@ void incur_damage_r(player_struct* p) // //--------------------------------------------------------------------------- -static void shootweapon(DDukeActor* actor, int p, DVector3 pos, DAngle ang, int atwith) -{ - auto sectp = actor->sector(); - double vel = 1024., zvel = 0; - HitInfo hit{}; - - if (actor->spr.extra >= 0) actor->spr.shade = -96; - - if (p >= 0) - { - auto aimed = aim(actor, AUTO_AIM_ANGLE); - if (aimed) - { - auto tex = TexMan.GetGameTexture(aimed->spr.spritetexture()); - double dal = ((aimed->spr.scale.X * tex->GetDisplayHeight()) * 0.5) + 5; - double dist = (ps[p].GetActor()->spr.pos.XY() - aimed->spr.pos.XY()).Length(); - zvel = ((aimed->spr.pos.Z - pos.Z - dal) * 16) / dist; - ang = (aimed->spr.pos - pos).Angle(); - } - - if (atwith == RTILE_SHOTSPARK1) - { - if (aimed == nullptr) - { - ang += DAngle22_5 / 8 - randomAngle(22.5 / 4); - setFreeAimVelocity(vel, zvel, ps[p].Angles.getPitchWithView(), 16.); - zvel += 0.5 - krandf(1); - } - } - else - { - if (atwith == RTILE_SHOTGUN) - ang += DAngle22_5 / 2 - randomAngle(22.5); - else - ang += DAngle22_5 / 8 - randomAngle(22.5 / 4); - if (aimed == nullptr) setFreeAimVelocity(vel, zvel, ps[p].Angles.getPitchWithView(), 16.); - zvel += 0.5 - krandf(1); - } - pos.Z -= 2; - } - else - { - double x; - int j = findplayer(actor, &x); - pos.Z -= 4; - double dist = (ps[j].GetActor()->spr.pos.XY() - actor->spr.pos.XY()).Length(); - zvel = ((ps[j].GetActor()->getOffsetZ() - pos.Z) * 16) / dist; - zvel += 0.5 - krandf(1); - ang += DAngle22_5 / 4 - randomAngle(22.5 / 2); - } - - actor->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL; - hitscan(pos, sectp, DVector3(ang.ToVector() * vel, zvel * 64), hit, CLIPMASK1); - - if (isRRRA() && hit.hitSector != nullptr && (((hit.hitSector->lotag == ST_160_FLOOR_TELEPORT && zvel > 0) || (hit.hitSector->lotag == ST_161_CEILING_TELEPORT && zvel < 0)) - && hit.actor() == nullptr && hit.hitWall == nullptr)) - { - DukeStatIterator its(STAT_EFFECTOR); - while (auto effector = its.Next()) - { - auto Owner = effector->GetOwner(); - if (effector->sector() == hit.hitSector && iseffector(effector) && Owner && effector->spr.lotag == SE_7_TELEPORT) - { - DVector3 npos; - npos.XY() = hit.hitpos.XY() + (Owner->spr.pos.XY() - effector->spr.pos.XY()); - if (hit.hitSector->lotag == ST_161_CEILING_TELEPORT) - { - npos.Z = Owner->sector()->floorz; - } - else - { - npos.Z = Owner->sector()->ceilingz; - } - hitscan(npos, Owner->sector(), DVector3(ang.ToVector() * 1024, zvel * 0.25), hit, CLIPMASK1); - break; - } - } - } - - actor->spr.cstat |= CSTAT_SPRITE_BLOCK_ALL; - - if (hit.hitSector == nullptr) return; - - if (atwith == RTILE_SHOTGUN) - if (hit.hitSector->lotag == 1) - if (krand() & 1) - return; - - if ((krand() & 15) == 0 && hit.hitSector->lotag == 2) - tracers(hit.hitpos, pos, 8 - (ud.multimode >> 1)); - - DDukeActor* spark; - if (p >= 0) - { - spark = CreateActor(hit.hitSector, hit.hitpos, RTILE_SHOTSPARK1, -15, DVector2(0.15625, 0.15625), ang, 0., 0., actor, 4); - if (!spark) return; - spark->spr.extra = ScriptCode[gs.actorinfo[atwith].scriptaddress]; - spark->spr.extra += (krand() % 6); - - if (hit.hitWall == nullptr && hit.actor() == nullptr) - { - if (zvel < 0) - { - if (hit.hitSector->ceilingstat & CSTAT_SECTOR_SKY) - { - spark->spr.scale = DVector2(0, 0); - return; - } - else - checkhitceiling(hit.hitSector); - } - if (hit.hitSector->lotag != 1) - spawn(spark, PClass::FindActor(NAME_DukeSmallSmoke)); - } - - if (hit.actor()) - { - if (hit.actor()->spr.picnum == RTILE_TORNADO) - return; - checkhitsprite(hit.actor(), spark); - if (hit.actor()->isPlayer() && (ud.coop != 1 || ud.ffire == 1)) - { - auto jib = spawn(spark, RTILE_JIBS6); - spark->spr.scale = DVector2(0, 0); - if (jib) - { - jib->spr.pos.Z += 4; - jib->vel.X = 1; - jib->spr.scale = DVector2(0.375, 0.375); - jib->spr.Angles.Yaw += DAngle22_5 / 2 - randomAngle(22.5); - } - } - else spawn(spark, PClass::FindActor(NAME_DukeSmallSmoke)); - - if (p >= 0 && isshootableswitch(hit.actor()->spr.spritetexture())) - { - checkhitswitch(p, nullptr, hit.actor()); - return; - } - } - else if (hit.hitWall != nullptr) - { - spawn(spark, PClass::FindActor(NAME_DukeSmallSmoke)); - - if (isadoorwall(hit.hitWall->walltexture) == 1) - goto SKIPBULLETHOLE; - if (isablockdoor(hit.hitWall->walltexture) == 1) - goto SKIPBULLETHOLE; - if (p >= 0 && isshootableswitch(hit.hitWall->walltexture)) - { - checkhitswitch(p, hit.hitWall, nullptr); - return; - } - - if (hit.hitWall->hitag != 0 || (hit.hitWall->twoSided() && hit.hitWall->nextWall()->hitag != 0)) - goto SKIPBULLETHOLE; - - if (hit.hitSector != nullptr && hit.hitSector->lotag == 0) - if (!(tileflags(hit.hitWall->overtexture) & TFLAG_FORCEFIELD)) - if ((hit.hitWall->twoSided() && hit.hitWall->nextSector()->lotag == 0) || - (!hit.hitWall->twoSided() && hit.hitSector->lotag == 0)) - if ((hit.hitWall->cstat & CSTAT_WALL_MASKED) == 0) - { - if (hit.hitWall->twoSided()) - { - DukeSectIterator it(hit.hitWall->nextSector()); - while (auto l = it.Next()) - { - if (l->spr.statnum == STAT_EFFECTOR && l->spr.lotag == SE_13_EXPLOSIVE) - goto SKIPBULLETHOLE; - } - } - - DukeStatIterator it(STAT_MISC); - while (auto l = it.Next()) - { - if (l->spr.picnum == RTILE_BULLETHOLE) - if ((l->spr.pos - spark->spr.pos).Length() < 0.75 + krandf(0.5)) - goto SKIPBULLETHOLE; - } - auto hole = spawn(spark, RTILE_BULLETHOLE); - if (hole) - { - hole->vel.X = -1 / 16; - hole->spr.Angles.Yaw = hit.hitWall->delta().Angle() - DAngle90; - ssp(hole, CLIPMASK0); - hole->spr.cstat2 |= CSTAT2_SPRITE_DECAL; - } - } - - SKIPBULLETHOLE: - - if (hit.hitWall->cstat & CSTAT_WALL_BOTTOM_SWAP) - if (hit.hitWall->twoSided()) - if (hit.hitpos.Z >= hit.hitWall->nextSector()->floorz) - hit.hitWall = hit.hitWall->nextWall(); - - checkhitwall(spark, hit.hitWall, hit.hitpos); - } - } - else - { - spark = CreateActor(hit.hitSector, hit.hitpos, RTILE_SHOTSPARK1, -15, DVector2(0.375, 0.375), ang, 0., 0., actor, 4); - if (!spark) return; - spark->spr.extra = ScriptCode[gs.actorinfo[atwith].scriptaddress]; - - if (hit.actor()) - { - checkhitsprite(hit.actor(), spark); - if (!hit.actor()->isPlayer()) - spawn(spark, PClass::FindActor(NAME_DukeSmallSmoke)); - else spark->spr.scale = DVector2(0, 0); - } - else if (hit.hitWall != nullptr) - checkhitwall(spark, hit.hitWall, hit.hitpos); - } - - if ((krand() & 255) < 10) - { - S_PlaySound3D(PISTOL_RICOCHET, spark, hit.hitpos); - } -} - -//--------------------------------------------------------------------------- -// -// -// -//--------------------------------------------------------------------------- - static void shootstuff(DDukeActor* actor, int p, DVector3 pos, DAngle ang, int atwith) { auto sect = actor->sector(); @@ -655,12 +426,6 @@ void shoot_r(DDukeActor* actor, int atwith, PClass* cls) switch (atwith) { - case RTILE_SHOTSPARK1: - case RTILE_SHOTGUN: - case RTILE_CHAINGUN: - shootweapon(actor, p, spos, sang, atwith); - return; - case RTILE_OWHIP: case RTILE_UWHIP: shootwhip(actor, p, spos, sang, atwith); diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 2a3a8f416..be88b62b5 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -159,6 +159,7 @@ version "4.10" #include "zscript/games/duke/actors/dukeweapons/melee.zs" +#include "zscript/games/duke/actors/dukeweapons/hitscan.zs" #include "zscript/games/duke/actors/dukeweapons/shrinker.zs" #include "zscript/games/duke/actors/dukeweapons/grower.zs" #include "zscript/games/duke/actors/dukeweapons/tripbomb.zs" diff --git a/wadsrc/static/zscript/games/duke/actors/_placeholders.zs b/wadsrc/static/zscript/games/duke/actors/_placeholders.zs index 6d6a2f76e..113b31cbc 100644 --- a/wadsrc/static/zscript/games/duke/actors/_placeholders.zs +++ b/wadsrc/static/zscript/games/duke/actors/_placeholders.zs @@ -11,47 +11,11 @@ class DukeRadiusExplosion : DukeActor } } -class DukeShotgunShot : DukeActor -{ - default - { - pic "SHOTGUN"; - } -} - -class RedneckShotgunShot : DukeShotgunShot -{ -} - - -class DukeChaingunShot : DukeActor -{ - default - { - pic "CHAINGUN"; - } -} - -class RedneckChaingunShot : DukeChaingunShot -{ -} - class DukeSectorEffector : DukeActor { //This never gets ticked, the handler goes directly to the native implementations. } -class DukeShotSpark : DukeActor -{ - default - { - pic "SHOTSPARK1"; - +FORCERUNCON; - +LIGHTDAMAGE; - statnum STAT_MISC; - } -} - class RedneckMotoHit : DukeActor { default diff --git a/wadsrc/static/zscript/games/duke/actors/dukeweapons/hitscan.zs b/wadsrc/static/zscript/games/duke/actors/dukeweapons/hitscan.zs new file mode 100644 index 000000000..6549b5549 --- /dev/null +++ b/wadsrc/static/zscript/games/duke/actors/dukeweapons/hitscan.zs @@ -0,0 +1,355 @@ + +extend class DukeActor +{ + static void tracers(Vector3 start, Vector3 dest, int n) + { + sectortype sect = nullptr; + + let direction = dest - start; + + if (direction.XY.Sum() < 192.75) + return; + + let pos = start; + let add = direction / (n + 1); + for (int i = n; i > 0; i--) + { + pos += add; + sect = Raze.updatesector(pos.XY, sect); + if (sect) + { + if (sect.lotag == ST_2_UNDERWATER) + { + Vector2 scale = (0.0625 + random(0, 3) * REPEAT_SCALE, 0.0625 + random(0, 3) * REPEAT_SCALE); + dlevel.SpawnActor(sect, pos, "DukeWaterBubble", -32, scale, frandom(0, 360), 0., 0., Duke.GetLocalPlayer().actor, STAT_MISC); + } + else + dlevel.SpawnActor(sect, pos, "DukeSmallSmoke", -32, (0.21875, 0.21875), 0, 0., 0., Duke.GetLocalPlayer().actor, STAT_MISC); + } + } + } + + //--------------------------------------------------------------------------- + // + // + // + //--------------------------------------------------------------------------- + + bool HitscanAttack(DukeActor actor, DukePlayer p, Vector3 pos, double ang, double hspread, double vspread, double enemyspread, bool forcespread, bool waterhalfhitchance = false, class sparktype = "DukeShotSpark") const + { + let sectp = actor.sector; + double vel = 1024, zvel = 0; + HitInfo hit; + + if (actor.extra >= 0) actor.shade = -96; + + if (p != null) + { + let aimed = actor.aim(self); + if (aimed) + { + double dal = ((aimed.scale.X * aimed.spriteHeight()) * 0.5) + aimed.sparkoffset; + double dist = (p.actor.pos.XY - aimed.pos.XY).Length(); + zvel = ((aimed.pos.Z - pos.Z - dal) * 16) / dist; + ang = (aimed.pos - pos).Angle(); + } + + if (aimed == nullptr || forcespread) + { + ang += hspread / 2 - frandom(0, hspread); + if (aimed == nullptr) [vel, zvel] = Raze.setFreeAimVelocity(vel, zvel, p.getPitchWithView(), 16); + zvel += vspread / 8 - frandom(0, vspread / 4); + } + pos.Z -= 2; + } + else + { + let j = actor.findplayer(); + pos.Z -= 4; + double dist = (j.actor.pos.XY - actor.pos.XY).Length(); + zvel = ((j.actor.pos.Z + j.actor.viewzoffset - pos.Z) * 16) / dist; + zvel += frandom(-0.5, 0.5); + if (!actor.bALTHITSCANDIRECTION) + { + ang += enemyspread / 2 - frandom(0, enemyspread); + } + else + { + // one of those lousy hacks in Duke. + ang = (j.actor.pos.XY - pos.XY).Angle() + enemyspread / 4 - frandom(0, enemyspread / 2); + } + } + + actor.cstat &= ~CSTAT_SPRITE_BLOCK_ALL; + Raze.hitscan(pos, sectp, (ang.ToVector() * vel, zvel * 64), hit, CLIPMASK1); + + if (Raze.isRRRA() && hit.hitSector != nullptr && ((hit.hitSector.lotag == ST_160_FLOOR_TELEPORT && zvel > 0) || (hit.hitSector.lotag == ST_161_CEILING_TELEPORT && zvel < 0)) + && hit.hitActor == nullptr && hit.hitWall == nullptr) + { + DukeStatIterator its; + for (let effector = its.First(STAT_EFFECTOR); effector; effector = its.Next()) + { + if (effector.sector == hit.hitSector && effector.GetClassName() == 'DukeSectorEffector' && effector.ownerActor && effector.lotag == SE_7_TELEPORT) + { + let owner = effector.ownerActor; + Vector3 npos; + npos.XY = hit.hitpos.XY + (owner.pos.XY - effector.pos.XY); + if (hit.hitSector.lotag == ST_161_CEILING_TELEPORT) + { + npos.Z = owner.sector.floorz; + } + else + { + npos.Z = owner.sector.ceilingz; + } + Raze.hitscan(npos, owner.sector, (ang.ToVector() * 1024, zvel * 0.25), hit, CLIPMASK1); + break; + } + } + } + + actor.cstat |= CSTAT_SPRITE_BLOCK_ALL; + + if (hit.hitSector == nullptr) return true; + + // RR shotgun only + if (waterhalfhitchance && hit.hitSector.lotag == ST_1_ABOVE_WATER && random(0, 1)) + return true; + + if (random(0, 15) == 0 && hit.hitSector.lotag == ST_2_UNDERWATER) + tracers(hit.hitpos, pos, 8 - (ud.multimode >> 1)); + + DukeActor spark = null; + if (p != null) + { + spark = dlevel.SpawnActor(hit.hitSector, hit.hitpos, sparktype, -15, (0.15625, 0.15625), ang, 0., 0., actor, STAT_PROJECTILE); + if (!spark) return true; + + spark.extra = self.strength + random(0, 5); + + if (hit.hitWall == nullptr && hit.hitActor == nullptr) + { + if (zvel < 0) + { + if (hit.hitSector.ceilingstat & CSTAT_SECTOR_SKY) + { + spark.scale = (0, 0); + return true; + } + else + dlevel.checkhitceiling(hit.hitSector, spark); + } + if (zvel < 0 || hit.hitSector.lotag != ST_1_ABOVE_WATER) + spark.spawn("DukeSmallSmoke"); + } + + let hitActor = DukeActor(hit.hitActor); + if (hitActor) + { + if (hitActor.bNOHITSCANHIT) + return true; + hitActor.OnHit(spark); + if (hitActor.isPlayer() && (ud.coop != 1 || ud.ffire == 1)) + { + let jib = spark.spawn("DukeJibs6"); + spark.scale = (0, 0); + if (jib) + { + jib.pos.Z += 4; + jib.vel.X = 1; + jib.scale = (0.375, 0.375); + jib.Angle += frandom(-11.25, 11.25); + } + } + else spark.spawn("DukeSmallSmoke"); + + if (p != null && Duke.isshootableswitch(hitActor.spritetexture())) + { + p.checkhitswitch(nullptr, hitActor); + return true; + } + } + else if (hit.hitWall) + { + spark.spawn("DukeSmallSmoke"); + + if (!(Raze.tileflags(hit.hitWall.walltexture) & (Duke.TFLAG_DOORWALL | Duke.TFLAG_BLOCKDOOR))) + { + if (p != null && Duke.isshootableswitch(hit.hitWall.walltexture)) + { + p.checkhitswitch(hit.hitWall, nullptr); + return true; + } + + if (!(hit.hitWall.hitag != 0 || (hit.hitWall.twoSided() && hit.hitWall.nextWallp().hitag != 0))) + { + + if (hit.hitSector && hit.hitSector.lotag == 0 && !(Raze.tileflags(hit.hitWall.overtexture) & Duke.TFLAG_FORCEFIELD)) + { + if ((hit.hitWall.twoSided() && hit.hitWall.nextSectorp().lotag == 0) || (!hit.hitWall.twoSided() && hit.hitSector.lotag == 0)) + { + if ((hit.hitWall.cstat & CSTAT_WALL_MASKED) == 0) + { + bool ok = true; + if (hit.hitWall.twoSided()) + { + DukeSectIterator it; + for (let l = it.First(hit.hitWall.nextSectorp()); l; l = it.Next()) + { + if (l.statnum == STAT_EFFECTOR && l.lotag == SE_13_EXPLOSIVE) + { + ok = false; + break; + } + } + } + + if (ok) + { + DukeStatIterator it; + for (let l = it.First(STAT_MISC); l; l = it.Next()) + { + if (l is 'DukeBulletHole' && (l.pos - spark.pos).Length() < frandom(0.75, 1.25)) + { + ok = false; + break; + } + } + if (ok) + { + let hole = spark.spawn("DukeBulletHole"); + if (hole) + { + hole.vel.X = -1 / 16.; + hole.Angle = hit.hitWall.delta().Angle() - 90; + hole.DoMove(CLIPMASK0); + hole.cstat2 |= CSTAT2_SPRITE_DECAL; + } + } + } + } + } + } + } + } + + if (hit.hitWall.cstat & CSTAT_WALL_BOTTOM_SWAP) + if (hit.hitWall.twoSided()) + if (hit.hitpos.Z >= hit.hitWall.nextSectorp().floorz) + hit.hitWall = hit.hitWall.nextWallp(); + + dlevel.checkhitwall(hit.hitWall, spark, hit.hitpos); + } + } + else + { + spark = dlevel.SpawnActor(hit.hitSector, hit.hitpos, sparktype, -15, (0.375, 0.375), ang, 0., 0., actor, STAT_PROJECTILE); + if (spark) + { + spark.extra = self.strength; + let hitActor = DukeActor(hit.hitActor); + + if (hitActor) + { + hitActor.OnHit(spark); + if (!hitActor.isPlayer()) + spark.spawn("DukeSmallSmoke"); + else spark.scale = (0, 0); + } + else if (hit.hitWall) + dlevel.checkhitwall(hit.hitWall, spark, hit.hitpos); + } + } + + if (spark && random(0, 255) < 4) + { + spark.PlayActorSound("PISTOL_RICOCHET"); + } + return true; + } +} + +//--------------------------------------------------------------------------- +// +// +// +//--------------------------------------------------------------------------- + +class DukeShotSpark : DukeActor +{ + default + { + pic "SHOTSPARK1"; + +FORCERUNCON; + +LIGHTDAMAGE; + statnum STAT_MISC; + } + + override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang) + { + return HitscanAttack(actor, p, pos, ang, 5.625, 4, 5.625, false); + } +} + +class DukeShotgunShot : DukeActor +{ + default + { + pic "SHOTGUN"; + } + + override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang) + { + return HitscanAttack(actor, p, pos, ang, 5.625, 4, 5.625, true); + } +} + +class DukeChaingunShot : DukeActor +{ + default + { + pic "CHAINGUN"; + } + + override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang) + { + return HitscanAttack(actor, p, pos, ang, 5.625, 4, 5.625, true); + } +} + +// RR' damage properties are a bit different. +class RedneckShotSpark : DukeShotSpark +{ + override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang) + { + return HitscanAttack(actor, p, pos, ang, 5.625, 4, 11.25, false); + } +} + + +class RedneckShotgunShot : DukeShotgunShot +{ + override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang) + { + return HitscanAttack(actor, p, pos, ang, 22.5, 4, 11.25, true, true); + } +} + + +class RedneckChaingunShot : DukeChaingunShot +{ + override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang) + { + return HitscanAttack(actor, p, pos, ang, 5.625, 4, 11.25, true); + } +} + +// WW2GI uses different spread settings for its pistol +class WW2GIShotSpark : DukeShotSpark +{ + override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang) + { + return HitscanAttack(actor, p, pos, ang, 0.3515625, 0.25, 5.625, true); + } +} +