//------------------------------------------------------------------------- /* Copyright (C) 1997, 2005 - 3D Realms Entertainment This file is part of Shadow Warrior version 1.2 Shadow Warrior is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Original Source: 1997 - Frank Maddin and Jim Norwood Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms */ //------------------------------------------------------------------------- #include "ns.h" #include "build.h" #include "names2.h" #include "panel.h" #include "misc.h" #include "tags.h" #include "sector.h" #include "player.h" #include "weapon.h" #include "jtags.h" #include "network.h" #include "break.h" #include "misc.h" #include "sprite.h" #include "light.h" #include "gstrings.h" #include "secrets.h" BEGIN_SW_NS #define LAVASIZ 128 #define LAVALOGSIZ 7 #define LAVAMAXDROPS 32 #define DEFAULT_DOOR_SPEED 800 enum { SINE_FLOOR = (1 << 0), SINE_CEILING = (1 << 1), SINE_SLOPED = BIT(3), }; ANIMATOR DoGrating; void DoPlayerBeginForceJump(PLAYER*); sectortype* FindNextSectorByTag(sectortype* sect, int tag); short LevelSecrets; bool TestVatorMatchActive(short match); bool TestSpikeMatchActive(short match); bool TestRotatorMatchActive(short match); bool TestSlidorMatchActive(short match); int PlayerCheckDeath(PLAYER*, DSWActor*); void DoVatorOperate(PLAYER*, sectortype*); void DoVatorMatch(PLAYER* pp, short match); void DoRotatorOperate(PLAYER*, sectortype*); void DoRotatorMatch(PLAYER* pp, short match, bool); void DoSlidorOperate(PLAYER*, sectortype*); void DoSlidorMatch(PLAYER* pp, short match, bool); void KillMatchingCrackSprites(short match); int DoTrapReset(short match); int DoTrapMatch(short match); PLAYER* GlobPlayerP; ANIM Anim[MAXANIM]; short AnimCnt = 0; SINE_WAVE_FLOOR SineWaveFloor[MAX_SINE_WAVE][21]; SINE_WALL SineWall[MAX_SINE_WALL][MAX_SINE_WALL_POINTS]; SPRING_BOARD SpringBoard[20]; void SetSectorWallBits(sectortype* sect, int bit_mask, bool set_sectwall, bool set_nextwall) { auto start_wall = sect->firstWall(); auto wall_num = start_wall; do { if (set_sectwall) wall_num->extra |= bit_mask; if (set_nextwall) { if (wall_num->twoSided()) wall_num->nextWall()->extra |= bit_mask; } wall_num = wall_num->point2Wall(); } while (wall_num != start_wall); } void WallSetupDontMove(void) { walltype* wallp; SWStatIterator it(STAT_WALL_DONT_MOVE_UPPER); while (auto iActor = it.Next()) { SWStatIterator it1(STAT_WALL_DONT_MOVE_LOWER); while (auto jActor = it1.Next()) { if (iActor->spr.lotag == jActor->spr.lotag) { for(auto& wal : wall) { if (wal.pos.X < jActor->spr.pos.X && wal.pos.X > iActor->spr.pos.X && wal.pos.Y < jActor->spr.pos.Y && wal.pos.Y > iActor->spr.pos.Y) { wal.extra |= WALLFX_DONT_MOVE; } } } } } } static void WallSetupLoop(walltype* wp, int16_t lotag, int16_t extra) { // set first wall { wp->extra |= extra; if (wp->twoSided()) wp->nextWall()->extra |= extra; } // Travel all the way around loop setting wall bits for (auto wall_num = wp->point2Wall(); wall_num->lotag != lotag; wall_num = wall_num->point2Wall()) { wall_num->extra |= extra; if (wall_num->twoSided()) wall_num->nextWall()->extra |= extra; } } void WallSetup(void) { short NextSineWall = 0; WallSetupDontMove(); memset(SineWall, 0, sizeof(SineWall)); extern int x_min_bound, y_min_bound, x_max_bound, y_max_bound; for (auto& wal : wall) { if (wal.picnum == FAF_PLACE_MIRROR_PIC) wal.picnum = FAF_MIRROR_PIC; if (wal.picnum == FAF_PLACE_MIRROR_PIC+1) wal.picnum = FAF_MIRROR_PIC+1; // this overwrites the lotag so it needs to be called LAST - its down there // SetupWallForBreak(wp); switch (wal.lotag) { case TAG_WALL_LOOP_DONT_SPIN: { WallSetupLoop(&wal, TAG_WALL_LOOP_DONT_SPIN, WALLFX_LOOP_DONT_SPIN); break; } case TAG_WALL_LOOP_DONT_SCALE: { WallSetupLoop(&wal, TAG_WALL_LOOP_DONT_SCALE, WALLFX_DONT_SCALE); wal.lotag = 0; break; } case TAG_WALL_LOOP_OUTER_SECONDARY: { // make sure it's a red wall if (wal.twoSided()) { WallSetupLoop(&wal, TAG_WALL_LOOP_OUTER_SECONDARY, WALLFX_LOOP_OUTER | WALLFX_LOOP_OUTER_SECONDARY); } else { Printf(PRINT_HIGH, "one-sided wall %d in loop setup\n", wallnum(&wal)); } break; } case TAG_WALL_LOOP_OUTER: { // make sure it's a red wall if (wal.twoSided()) { WallSetupLoop(&wal, TAG_WALL_LOOP_OUTER, WALLFX_LOOP_OUTER); } else { Printf(PRINT_HIGH, "one-sided wall %d in loop setup\n", wallnum(&wal)); } wal.lotag = 0; break; } case TAG_WALL_DONT_MOVE: { // set first wall wal.extra |= WALLFX_DONT_MOVE; break; } case TAG_WALL_LOOP_SPIN_2X: { WallSetupLoop(&wal, TAG_WALL_LOOP_SPIN_2X, WALLFX_LOOP_SPIN_2X); break; } case TAG_WALL_LOOP_SPIN_4X: { WallSetupLoop(&wal, TAG_WALL_LOOP_SPIN_4X, WALLFX_LOOP_SPIN_4X); break; } case TAG_WALL_LOOP_REVERSE_SPIN: { WallSetupLoop(&wal, TAG_WALL_LOOP_REVERSE_SPIN, WALLFX_LOOP_REVERSE_SPIN); break; } case TAG_WALL_SINE_Y_BEGIN: case TAG_WALL_SINE_X_BEGIN: { walltype* wall_num; int cnt, num_points, type, tag_end; SINE_WALL* sw; int range = 250, speed = 3, peak = 0; tag_end = wal.lotag + 2; type = wal.lotag - TAG_WALL_SINE_Y_BEGIN; // count up num_points for (wall_num = &wal, num_points = 0; num_points < MAX_SINE_WALL_POINTS && wall_num->lotag != tag_end; wall_num = wall_num->point2Wall(), num_points++) { if (num_points == 0) { if (wall_num->hitag) range = wall_num->hitag; } else if (num_points == 1) { if (wall_num->hitag) speed = wall_num->hitag; } else if (num_points == 2) { if (wall_num->hitag) peak = wall_num->hitag; } } if (peak) num_points = peak; for (wall_num = &wal, cnt = 0; cnt < MAX_SINE_WALL_POINTS && wall_num->lotag != tag_end; wall_num = wall_num->point2Wall(), cnt++) { // set the first on up sw = &SineWall[NextSineWall][cnt]; sw->type = type; sw->wallp = wall_num; sw->speed_shift = speed; sw->range = range; // don't allow bullet holes/stars wall_num->extra |= WALLFX_DONT_STICK; if (!sw->type) sw->orig_xy = wall_num->pos.Y - (sw->range >> 2); else sw->orig_xy = wall_num->pos.X - (sw->range >> 2); sw->sintable_ndx = cnt * (2048 / num_points); } NextSineWall++; ASSERT(NextSineWall < MAX_SINE_WALL); } } // this overwrites the lotag so it needs to be called LAST SetupWallForBreak(&wal); } } void SectorLiquidSet(sectortype* sectp) { // /////////////////////////////////// // // CHECK for pics that mean something // // /////////////////////////////////// if (sectp->floorpicnum >= 300 && sectp->floorpicnum <= 307) { sectp->u_defined = true; sectp->extra |= (SECTFX_LIQUID_WATER); } else if (sectp->floorpicnum >= 320 && sectp->floorpicnum <= 343) { sectp->u_defined = true; sectp->extra |= (SECTFX_LIQUID_WATER); } else if (sectp->floorpicnum >= 780 && sectp->floorpicnum <= 794) { sectp->u_defined = true; sectp->extra |= (SECTFX_LIQUID_WATER); } else if (sectp->floorpicnum >= 890 && sectp->floorpicnum <= 897) { sectp->u_defined = true; sectp->extra |= (SECTFX_LIQUID_WATER); } else if (sectp->floorpicnum >= 175 && sectp->floorpicnum <= 182) { sectp->u_defined = true; sectp->extra |= (SECTFX_LIQUID_LAVA); if (!sectp->damage) sectp->damage = 40; } } void SectorSetup(void) { int tag; int NextSineWave = 0; int ndx; WallSetup(); for (ndx = 0; ndx < MAX_SECTOR_OBJECTS; ndx++) { memset(&SectorObject[ndx], -1, sizeof(SectorObject[0])); // 0 pointers memset(&SectorObject[ndx].sectp, 0, sizeof(SectorObject[0].sectp)); memset(&SectorObject[ndx].so_actors, 0, sizeof(SectorObject[0].so_actors)); SectorObject[ndx].match_event_actor = nullptr; SectorObject[ndx].PreMoveAnimator = nullptr; SectorObject[ndx].PostMoveAnimator = nullptr; SectorObject[ndx].Animator = nullptr; SectorObject[ndx].controller = nullptr; SectorObject[ndx].sp_child = nullptr; SectorObject[ndx].mid_sector = nullptr; SectorObject[ndx].op_main_sector = nullptr; SectorObject[ndx].morph_wall_point = nullptr; SectorObject[ndx].pmid.X = INT32_MAX; } memset(SineWaveFloor, 0, sizeof(SineWaveFloor)); memset(SpringBoard, 0, sizeof(SpringBoard)); LevelSecrets = 0; for(auto§: sector) { auto const sectp = § tag = sectp->lotag; // /////////////////////////////////// // // CHECK for pics that mean something // // /////////////////////////////////// // /////////////////////////////////// // // CHECK for flags // // /////////////////////////////////// if ((sectp->extra & SECTFX_SINK)) { SectorLiquidSet(sectp); } if ((sectp->floorstat & CSTAT_SECTOR_SKY)) { // don't do a z adjust for FAF area if (sectp->floorpicnum != FAF_PLACE_MIRROR_PIC) { sectp->extra |= (SECTFX_Z_ADJUST); } } if ((sectp->ceilingstat & CSTAT_SECTOR_SKY)) { // don't do a z adjust for FAF area if (sectp->ceilingpicnum != FAF_PLACE_MIRROR_PIC) { sectp->extra |= (SECTFX_Z_ADJUST); } } // /////////////////////////////////// // // CHECK for sector/sprite objects // // /////////////////////////////////// if (tag >= TAG_OBJECT_CENTER && tag < TAG_OBJECT_CENTER + 100) { SetupSectorObject(sectp, tag); } // /////////////////////////////////// // // CHECK lo and hi tags // // /////////////////////////////////// switch (tag) { case TAG_SECRET_AREA_TRIGGER: LevelSecrets++; break; case TAG_DOOR_SLIDING: SetSectorWallBits(sectp, WALLFX_DONT_STICK, true, true); break; case TAG_SINE_WAVE_FLOOR: case TAG_SINE_WAVE_CEILING: case TAG_SINE_WAVE_BOTH: { SINE_WAVE_FLOOR *swf; uint16_t swf_ndx = 0; short cnt = 0, sector_cnt; int range; int range_diff = 0; int wave_diff = 0; short peak_dist = 0; short speed_shift = 3; short num; num = (tag - TAG_SINE_WAVE_FLOOR) / 20; // set the first on up swf = &SineWaveFloor[NextSineWave][swf_ndx]; swf->flags = 0; switch (num) { case 0: swf->flags |= (SINE_FLOOR); if ((sectp->floorstat & CSTAT_SECTOR_SLOPE)) { swf->flags |= (SINE_SLOPED); } break; case 1: swf->flags |= (SINE_CEILING); break; case 2: swf->flags |= (SINE_FLOOR | SINE_CEILING); break; } swf->sectp = sectp; ASSERT(swf->sectp->hitag != 0); swf->range = range = Z(swf->sectp->hitag); swf->floor_origz = swf->sectp->floorz - (range >> 2); swf->ceiling_origz = swf->sectp->ceilingz - (range >> 2); // look for the rest by distance auto near_sectp = sectp, base_sectp = sectp; for (swf_ndx = 1, sector_cnt = 1; true; swf_ndx++) { near_sectp = FindNextSectorByTag(base_sectp, tag + swf_ndx); if (near_sectp != nullptr) { swf = &SineWaveFloor[NextSineWave][swf_ndx]; if (swf_ndx == 1 && near_sectp->hitag) range_diff = near_sectp->hitag; else if (swf_ndx == 2 && near_sectp->hitag) speed_shift = near_sectp->hitag; else if (swf_ndx == 3 && near_sectp->hitag) peak_dist = near_sectp->hitag; swf->sectp = near_sectp; swf->floor_origz = swf->sectp->floorz - (range >> 2); swf->ceiling_origz = swf->sectp->ceilingz - (range >> 2); range -= range_diff; swf->range = range; base_sectp = swf->sectp; sector_cnt++; } else break; } ASSERT(swf_ndx <= SIZ(SineWaveFloor[NextSineWave])); // more than 6 waves and something in high tag - set up wave // dissapate if (sector_cnt > 8 && base_sectp->hitag) { wave_diff = base_sectp->hitag; } // setup the sintable_ndx based on the actual number of // sectors (swf_ndx) for (swf = &SineWaveFloor[NextSineWave][0], cnt = 0; swf->sectp != 0 && swf < (SINE_WAVE_FLOOR*)&SineWaveFloor[SIZ(SineWaveFloor)]; swf++, cnt++) { if (peak_dist) swf->sintable_ndx = cnt * (2048 / peak_dist); else swf->sintable_ndx = cnt * (2048 / swf_ndx); swf->speed_shift = speed_shift; } // set up the a real wave that dissapates at the end if (wave_diff) { for (cnt = sector_cnt - 1; cnt >= 0; cnt--) { // only do the last (actually the first) few for the // dissapate if (cnt > 8) continue; swf = &SineWaveFloor[NextSineWave][cnt]; swf->range -= wave_diff; wave_diff += wave_diff; if (swf->range < Z(4)) swf->range = Z(4); // reset origz's based on new range swf->floor_origz = swf->sectp->floorz - (swf->range >> 2); swf->ceiling_origz = swf->sectp->ceilingz - (swf->range >> 2); } } NextSineWave++; ASSERT(NextSineWave < MAX_SINE_WAVE); break; } } } } void SectorMidPoint(sectortype* sectp, int *xmid, int *ymid, int *zmid) { int xsum = 0, ysum = 0; for(auto& wal : wallsofsector(sectp)) { xsum += wal.pos.X; ysum += wal.pos.Y; } *xmid = xsum / (sectp->wallnum); *ymid = ysum / (sectp->wallnum); *zmid = (sectp->floorz + sectp->ceilingz) >> 1; } void DoSpringBoard(PLAYER* pp) { pp->jump_speed = -pp->cursector->hitag; DoPlayerBeginForceJump(pp); return; } void DoSpringBoardDown(void) { unsigned sb; SPRING_BOARD *sbp; for (sb = 0; sb < SIZ(SpringBoard); sb++) { sbp = &SpringBoard[sb]; // if empty set up an entry to close the sb later if (sbp->sectp != nullptr) { if ((sbp->TimeOut -= synctics) <= 0) { int destz; destz = safenextsectorneighborzptr(sbp->sectp, sbp->sectp->floorz, 1, 1)->floorz; AnimSet(ANIM_Floorz, sbp->sectp, destz, 256); sbp->sectp->lotag = TAG_SPRING_BOARD; sbp->sectp = nullptr; } } } return; } sectortype* FindNextSectorByTag(sectortype* sect, int tag) { for(auto& wal : wallsofsector(sect)) { if (wal.twoSided()) { if (wal.nextSector()->lotag == tag) { return wal.nextSector(); } } } return nullptr; } short DoSpawnActorTrigger(short match) { short spawn_count = 0; SWStatIterator it(STAT_SPAWN_TRIGGER); while (auto actor = it.Next()) { if (actor->spr.hitag == match) { if (ActorSpawn(actor)) { DoSpawnTeleporterEffectPlace(actor); PlaySound(DIGI_PLAYER_TELEPORT, actor, v3df_none); spawn_count++; } } } return spawn_count; } int OperateSector(sectortype* sect, short player_is_operating) { PLAYER* pp = GlobPlayerP; // Don't let actors operate locked or secret doors if (!player_is_operating) { if (sect->hasU() && sect->stag == SECT_LOCK_DOOR) return false; SWSectIterator it(sect); while (auto actor = it.Next()) { auto fsect = actor->sector(); if (fsect->hasU() && fsect->stag == SECT_LOCK_DOOR) return false; if (actor->spr.statnum == STAT_VATOR && SP_TAG1(actor) == SECT_VATOR && TEST_BOOL7(actor)) return false; if (actor->spr.statnum == STAT_ROTATOR && SP_TAG1(actor) == SECT_ROTATOR && TEST_BOOL7(actor)) return false; if (actor->spr.statnum == STAT_SLIDOR && SP_TAG1(actor) == SECT_SLIDOR && TEST_BOOL7(actor)) return false; } } switch (sect->lotag) { case TAG_VATOR: DoVatorOperate(pp, sect); return true; case TAG_ROTATOR: DoRotatorOperate(pp, sect); return true; case TAG_SLIDOR: DoSlidorOperate(pp, sect); return true; } return false; } enum { SWITCH_LEVER = 581, SWITCH_FUSE = 558, SWITCH_FLIP = 561, SWITCH_RED_CHAIN = 563, SWITCH_GREEN_CHAIN = 565, SWITCH_TOUCH = 567, SWITCH_DRAGON = 569, SWITCH_LIGHT = 551, SWITCH_1 = 575, SWITCH_3 = 579, SWITCH_SHOOTABLE_1 = 577, SWITCH_4 = 571, SWITCH_5 = 573, SWITCH_6 = 583, EXIT_SWITCH = 2470, SWITCH_SKULL = 553, }; int AnimateSwitch(DSWActor* actor, int tgt_value) { // if the value is not ON or OFF // then it is a straight toggle switch (actor->spr.picnum) { // set to true/ON case SWITCH_SKULL: case SWITCH_LEVER: case SWITCH_LIGHT: case SWITCH_SHOOTABLE_1: case SWITCH_1: case SWITCH_3: case SWITCH_FLIP: case SWITCH_RED_CHAIN: case SWITCH_GREEN_CHAIN: case SWITCH_TOUCH: case SWITCH_DRAGON: case SWITCH_4: case SWITCH_5: case SWITCH_6: case EXIT_SWITCH: // dont toggle - return the current state if (tgt_value == 999) return false; actor->spr.picnum += 1; // if the tgt_value should be true // flip it again - recursive but only once if (tgt_value == false) { AnimateSwitch(actor, tgt_value); return false; } return true; // set to true case SWITCH_SKULL + 1: case SWITCH_LEVER + 1: case SWITCH_LIGHT + 1: case SWITCH_1 + 1: case SWITCH_3 + 1: case SWITCH_FLIP + 1: case SWITCH_RED_CHAIN + 1: case SWITCH_GREEN_CHAIN + 1: case SWITCH_TOUCH + 1: case SWITCH_DRAGON + 1: case SWITCH_SHOOTABLE_1 + 1: case SWITCH_4+1: case SWITCH_5+1: case SWITCH_6+1: case EXIT_SWITCH+1: // dont toggle - return the current state if (tgt_value == 999) return true; actor->spr.picnum -= 1; if (tgt_value == int(true)) { AnimateSwitch(actor, tgt_value); return true; } return false; } return false; } void SectorExp(DSWActor* actor, sectortype* sectp, short orig_ang, int zh) { int x,y,z; actor->spr.cstat &= ~(CSTAT_SPRITE_ALIGNMENT_WALL|CSTAT_SPRITE_ALIGNMENT_FLOOR); SectorMidPoint(sectp, &x, &y, &z); actor->spr.ang = orig_ang; actor->spr.pos.X = x; actor->spr.pos.Y = y; actor->spr.pos.Z = z; // randomize the explosions actor->spr.ang += RANDOM_P2(256) - 128; actor->spr.pos.X += RANDOM_P2(1024) - 512; actor->spr.pos.Y += RANDOM_P2(1024) - 512; actor->spr.pos.Z = zh; // setup vars needed by SectorExp ChangeActorSect(actor, sectp); getzsofslopeptr(actor->sector(), actor->spr.pos.X, actor->spr.pos.Y, &actor->user.hiz, &actor->user.loz); // spawn explosion auto exp = SpawnSectorExp(actor); if (!exp) return; exp->spr.xrepeat += (RANDOM_P2(32<<8)>>8) - 16; exp->spr.yrepeat += (RANDOM_P2(32<<8)>>8) - 16; exp->user.change.X = MOVEx(92, exp->spr.ang); exp->user.change.Y = MOVEy(92, exp->spr.ang); } void DoExplodeSector(short match) { short orig_ang; int zh; sectortype* sectp; orig_ang = 0; //actor->spr.ang; SWStatIterator it(STAT_EXPLODING_CEIL_FLOOR); while (auto actor = it.Next()) { if (match != actor->spr.lotag) continue; if (!actor->hasU()) SpawnUser(actor, 0, nullptr); sectp = actor->sector(); sectp->addceilingz(-Z(SP_TAG4(actor))); if (SP_TAG5(actor)) { sectp->setfloorslope(SP_TAG5(actor)); } if (SP_TAG6(actor)) { sectp->setceilingslope(SP_TAG6(actor)); } for (zh = sectp->ceilingz; zh < sectp->floorz; zh += Z(60)) { SectorExp(actor, actor->sector(), orig_ang, zh + Z(RANDOM_P2(64)) - Z(32)); } // don't need it any more KillActor(actor); } } int DoSpawnSpot(DSWActor* actor) { if ((actor->user.WaitTics -= synctics) < 0) { change_actor_stat(actor, STAT_SPAWN_SPOT); SpawnShrap(actor, nullptr); if (actor->user.LastDamage == 1) { KillActor(actor); return 0; } } return 0; } // spawns shrap when killing an object void DoSpawnSpotsForKill(short match) { if (match < 0) return; SWStatIterator it(STAT_SPAWN_SPOT); while (auto actor = it.Next()) { // change the stat num and set the delay correctly to call SpawnShrap if (actor->spr.hitag == SPAWN_SPOT && actor->spr.lotag == match) { change_actor_stat(actor, STAT_NO_STATE); actor->user.ActorActionFunc = DoSpawnSpot; actor->user.WaitTics = SP_TAG5(actor) * 15; SetActorZ(actor, &actor->spr.pos); // setting for Killed actor->user.LastDamage = 1; } } } // spawns shrap when damaging an object void DoSpawnSpotsForDamage(short match) { if (match < 0) return; SWStatIterator it(STAT_SPAWN_SPOT); while (auto actor = it.Next()) { // change the stat num and set the delay correctly to call SpawnShrap if (actor->spr.hitag == SPAWN_SPOT && actor->spr.lotag == match) { change_actor_stat(actor, STAT_NO_STATE); actor->user.ActorActionFunc = DoSpawnSpot; actor->user.WaitTics = SP_TAG7(actor) * 15; // setting for Damaged actor->user.LastDamage = 0; } } } void DoSoundSpotMatch(short match, short sound_num, short sound_type) { int flags; short snd2play; //sound_type is not used sound_num--; ASSERT(sound_num >= 0); SWStatIterator it(STAT_SOUND_SPOT); while (auto actor = it.Next()) { if (SP_TAG2(actor) == match && !TEST_BOOL6(actor)) { short snd[3]; snd[0] = SP_TAG13(actor); // tag4 is copied to tag13 snd[1] = SP_TAG5(actor); snd[2] = SP_TAG6(actor); snd2play = 0; flags = 0; if (TEST_BOOL2(actor)) flags = v3df_follow|v3df_nolookup|v3df_init; // play once and only once if (TEST_BOOL1(actor)) SET_BOOL6(actor); // don't pan if (TEST_BOOL4(actor)) flags |= v3df_dontpan; // add doppler if (TEST_BOOL5(actor)) flags |= v3df_doppler; // random if (TEST_BOOL3(actor)) { if (snd[0] && snd[1]) { snd2play = snd[RandomRange(2)]; } else if (snd[0] && snd[1] && snd[2]) { snd2play = snd[RandomRange(3)]; } } else if (snd[sound_num]) { snd2play = snd[sound_num]; } if (snd2play <= 0) continue; if (TEST_BOOL7(actor)) { PLAYER* pp = GlobPlayerP; if (pp) { if (pp == Player+myconnectindex) PlayerSound(snd2play, v3df_dontpan|v3df_follow,pp); } } else { PlaySound(snd2play, actor, flags); } } } } void DoSoundSpotStopSound(short match) { SWStatIterator it(STAT_SOUND_SPOT); while (auto actor = it.Next()) { // found match and is a follow type if (SP_TAG2(actor) == match && TEST_BOOL2(actor)) { DeleteNoSoundOwner(actor); } } } void DoStopSoundSpotMatch(short match) { SWStatIterator it(STAT_STOP_SOUND_SPOT); while (auto actor = it.Next()) { if (SP_TAG2(actor) == match) { DoSoundSpotStopSound(SP_TAG5(actor)); } } } bool TestKillSectorObject(SECTOR_OBJECT* sop) { if ((sop->flags & SOBJ_KILLABLE)) { KillMatchingCrackSprites(sop->match_event); // get new sectnums CollapseSectorObject(sop, sop->pmid.X, sop->pmid.Y); DoSpawnSpotsForKill(sop->match_event); KillSectorObjectSprites(sop); return true; } return false; } short DoSectorObjectKillMatch(short match) { SECTOR_OBJECT* sop; for (sop = SectorObject; sop < &SectorObject[MAX_SECTOR_OBJECTS]; sop++) { if (SO_EMPTY(sop)) continue; if (sop->match_event == match) return TestKillSectorObject(sop); } return false; } bool SearchExplodeSectorMatch(short match) { // THIS IS ONLY CALLED FROM DoMatchEverything SWStatIterator it(STAT_SPRITE_HIT_MATCH); while (auto actor = it.Next()) { if (actor->spr.hitag == match) { KillMatchingCrackSprites(match); DoExplodeSector(match); return true; } } return false; } void KillMatchingCrackSprites(short match) { SWStatIterator it(STAT_SPRITE_HIT_MATCH); while (auto actor = it.Next()) { if (actor->spr.hitag == match) { if (SP_TAG8(actor) & BIT(2)) continue; KillActor(actor); } } } void WeaponExplodeSectorInRange(DSWActor* wActor) { int dist; int radius; SWStatIterator it(STAT_SPRITE_HIT_MATCH); while (auto actor = it.Next()) { // test to see if explosion is close to crack sprite dist = FindDistance3D(wActor->spr.pos - actor->spr.pos); if (actor->spr.clipdist == 0) continue; radius = (((int)actor->spr.clipdist) << 2) * 8; if ((unsigned int)dist > (wActor->user.Radius/2) + radius) continue; if (!FAFcansee(wActor->spr.pos.X,wActor->spr.pos.Y,wActor->spr.pos.Z,wActor->sector(),actor->spr.pos.X,actor->spr.pos.Y,actor->spr.pos.Z,actor->sector())) continue; // pass in explosion type MissileHitMatch(wActor, WPN_ROCKET, actor); } } void ShootableSwitch(DSWActor* actor) { switch (actor->spr.picnum) { case SWITCH_SHOOTABLE_1: //actor->spr.cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN); OperateSprite(actor, false); actor->spr.picnum = SWITCH_SHOOTABLE_1 + 1; break; case SWITCH_FUSE: case SWITCH_FUSE + 1: actor->spr.cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN); OperateSprite(actor, false); actor->spr.picnum = SWITCH_FUSE + 2; break; } } void DoDeleteSpriteMatch(short match) { static short StatList[] = { STAT_DEFAULT, STAT_VATOR, STAT_SPIKE, STAT_TRAP, STAT_ITEM, STAT_LIGHTING, STAT_STATIC_FIRE, STAT_AMBIENT, STAT_FAF }; int del_x = 0,del_y = 0; unsigned stat; while (true) { DSWActor* found = nullptr; // search for a DELETE_SPRITE with same match tag SWStatIterator it(STAT_DELETE_SPRITE); while (auto actor = it.Next()) { if (actor->spr.lotag == match) { found = actor; del_x = actor->spr.pos.X; del_y = actor->spr.pos.Y; break; } } if (found == nullptr) return; for (stat = 0; stat < SIZ(StatList); stat++) { it.Reset(StatList[stat]); while (auto actor = it.Next()) { if (del_x == actor->spr.pos.X && del_y == actor->spr.pos.Y) { // special case lighting delete of Fade On/off after fades if (StatList[stat] == STAT_LIGHTING) { // set shade to darkest and then kill it actor->spr.shade = int8_t(SP_TAG6(actor)); actor->spr.pal = 0; SectorLightShade(actor, actor->spr.shade); DiffuseLighting(actor); } SpriteQueueDelete(actor); KillActor(actor); } } } // kill the DELETE_SPRITE KillActor(found); } } void DoChangorMatch(short match) { SWStatIterator it(STAT_CHANGOR); while (auto actor = it.Next()) { auto sectp = actor->sector(); if (SP_TAG2(actor) != match) continue; if (TEST_BOOL1(actor)) { sectp->ceilingpicnum = SP_TAG4(actor); sectp->addceilingz(Z(SP_TAG5(actor))); sectp->ceilingheinum += SP_TAG6(actor); if (sectp->ceilingheinum) sectp->ceilingstat |= CSTAT_SECTOR_SLOPE; else sectp->ceilingstat &= ~CSTAT_SECTOR_SLOPE; sectp->ceilingshade += SP_TAG7(actor); sectp->ceilingpal += SP_TAG8(actor); } else { sectp->floorpicnum = SP_TAG4(actor); sectp->addfloorz(Z(SP_TAG5(actor))); sectp->floorheinum += SP_TAG6(actor); if (sectp->floorheinum) sectp->floorstat |= CSTAT_SECTOR_SLOPE; else sectp->floorstat &= ~CSTAT_SECTOR_SLOPE; sectp->floorshade += SP_TAG7(actor); sectp->floorpal += SP_TAG8(actor); } sectp->visibility += SP_TAG9(actor); // if not set then go ahead and kill it if (TEST_BOOL2(actor) == 0) { KillActor(actor); } } } void DoMatchEverything(PLAYER* pp, short match, short state) { PLAYER* bak; bak = GlobPlayerP; GlobPlayerP = pp; // CAREFUL! pp == nullptr is a valid case for this routine DoStopSoundSpotMatch(match); DoSoundSpotMatch(match, 1, SOUND_EVERYTHING_TYPE); GlobPlayerP = bak; DoLightingMatch(match, state); DoQuakeMatch(match); // make sure all vators are inactive before allowing // to repress switch if (!TestVatorMatchActive(match)) DoVatorMatch(pp, match); if (!TestSpikeMatchActive(match)) DoSpikeMatch(match); if (!TestRotatorMatchActive(match)) DoRotatorMatch(pp, match, false); if (!TestSlidorMatchActive(match)) DoSlidorMatch(pp, match, false); DoSectorObjectKillMatch(match); DoSectorObjectSetScale(match); DoSOevent(match, state); DoSpawnActorTrigger(match); // this may or may not find an exploding sector SearchExplodeSectorMatch(match); CopySectorMatch(match); DoWallMoveMatch(match); DoSpawnSpotsForKill(match); DoTrapReset(match); DoTrapMatch(match); SpawnItemsMatch(match); DoChangorMatch(match); DoDeleteSpriteMatch(match); } bool ComboSwitchTest(short combo_type, short match) { int state; SWStatIterator it(STAT_DEFAULT); while (auto actor = it.Next()) { if (actor->spr.lotag == combo_type && actor->spr.hitag == match) { // dont toggle - get the current state state = AnimateSwitch(actor, 999); // if any one is not set correctly then switch is not set if (state != SP_TAG3(actor)) { return false; } } } return true; } // NOTE: switches are always wall sprites int OperateSprite(DSWActor* actor, short player_is_operating) { PLAYER* pp = nullptr; short state; short key_num=0; extern STATE s_Pachinko1Operate[]; extern STATE s_Pachinko2Operate[]; extern STATE s_Pachinko3Operate[]; extern STATE s_Pachinko4Operate[]; if (Prediction) return false; if (actor->spr.picnum == ST1) return false; if (player_is_operating) { pp = GlobPlayerP; if (!FAFcansee(pp->pos.X, pp->pos.Y, pp->pos.Z, pp->cursector, actor->spr.pos.X, actor->spr.pos.Y, actor->spr.pos.Z - (ActorSizeZ(actor) >> 1), actor->sector())) return false; } switch (actor->spr.lotag) { case TOILETGIRL_R0: case WASHGIRL_R0: case CARGIRL_R0: case MECHANICGIRL_R0: case SAILORGIRL_R0: case PRUNEGIRL_R0: { short choose_snd; actor->user.FlagOwner = 1; actor->user.WaitTics = SEC(4); if (pp != Player+myconnectindex) return true; choose_snd = StdRandomRange(1000); if (actor->spr.lotag == CARGIRL_R0) { if (choose_snd > 700) PlayerSound(DIGI_JG44052, v3df_dontpan|v3df_follow,pp); else if (choose_snd > 500) PlayerSound(DIGI_JG45014, v3df_dontpan|v3df_follow,pp); else if (choose_snd > 250) PlayerSound(DIGI_JG44068, v3df_dontpan|v3df_follow,pp); else PlayerSound(DIGI_JG45010, v3df_dontpan|v3df_follow,pp); } else if (actor->spr.lotag == MECHANICGIRL_R0) { if (choose_snd > 700) PlayerSound(DIGI_JG44027, v3df_dontpan|v3df_follow,pp); else if (choose_snd > 500) PlayerSound(DIGI_JG44038, v3df_dontpan|v3df_follow,pp); else if (choose_snd > 250) PlayerSound(DIGI_JG44039, v3df_dontpan|v3df_follow,pp); else PlayerSound(DIGI_JG44048, v3df_dontpan|v3df_follow,pp); } else if (actor->spr.lotag == SAILORGIRL_R0) { if (choose_snd > 700) PlayerSound(DIGI_JG45018, v3df_dontpan|v3df_follow,pp); else if (choose_snd > 500) PlayerSound(DIGI_JG45030, v3df_dontpan|v3df_follow,pp); else if (choose_snd > 250) PlayerSound(DIGI_JG45033, v3df_dontpan|v3df_follow,pp); else PlayerSound(DIGI_JG45043, v3df_dontpan|v3df_follow,pp); } else if (actor->spr.lotag == PRUNEGIRL_R0) { if (choose_snd > 700) PlayerSound(DIGI_JG45053, v3df_dontpan|v3df_follow,pp); else if (choose_snd > 500) PlayerSound(DIGI_JG45067, v3df_dontpan|v3df_follow,pp); else if (choose_snd > 250) PlayerSound(DIGI_JG46005, v3df_dontpan|v3df_follow,pp); else PlayerSound(DIGI_JG46010, v3df_dontpan|v3df_follow,pp); } else if (actor->spr.lotag == TOILETGIRL_R0) { if (choose_snd > 700) PlayerSound(DIGI_WHATYOUEATBABY, v3df_dontpan|v3df_follow,pp); else if (choose_snd > 500) PlayerSound(DIGI_WHATDIEDUPTHERE, v3df_dontpan|v3df_follow,pp); else if (choose_snd > 250) PlayerSound(DIGI_YOUGOPOOPOO, v3df_dontpan|v3df_follow,pp); else PlayerSound(DIGI_PULLMYFINGER, v3df_dontpan|v3df_follow,pp); } else { if (choose_snd > 700) PlayerSound(DIGI_SOAPYOUGOOD, v3df_dontpan|v3df_follow,pp); else if (choose_snd > 500) PlayerSound(DIGI_WASHWANG, v3df_dontpan|v3df_follow,pp); else if (choose_snd > 250) PlayerSound(DIGI_DROPSOAP, v3df_dontpan|v3df_follow,pp); else PlayerSound(DIGI_REALTITS, v3df_dontpan|v3df_follow,pp); } } return true; case PACHINKO1: // Don't mess with it if it's already going if (actor->user.WaitTics > 0) return true; PlaySound(DIGI_PFLIP, actor, v3df_none); actor->user.WaitTics = SEC(3) + SEC(RandomRange(10)); ChangeState(actor,s_Pachinko1Operate); return true; case PACHINKO2: // Don't mess with it if it's already going if (actor->user.WaitTics > 0) return true; PlaySound(DIGI_PFLIP, actor, v3df_none); actor->user.WaitTics = SEC(3) + SEC(RandomRange(10)); ChangeState(actor,s_Pachinko2Operate); return true; case PACHINKO3: // Don't mess with it if it's already going if (actor->user.WaitTics > 0) return true; PlaySound(DIGI_PFLIP, actor, v3df_none); actor->user.WaitTics = SEC(3) + SEC(RandomRange(10)); ChangeState(actor,s_Pachinko3Operate); return true; case PACHINKO4: // Don't mess with it if it's already going if (actor->user.WaitTics > 0) return true; PlaySound(DIGI_PFLIP, actor, v3df_none); actor->user.WaitTics = SEC(3) + SEC(RandomRange(10)); ChangeState(actor,s_Pachinko4Operate); return true; case SWITCH_LOCKED: key_num = actor->spr.hitag; if (pp->HasKey[key_num - 1]) { for(auto& sect: sector) { if (sect.hasU() && sect.stag == SECT_LOCK_DOOR && sect.number == key_num) sect.number = 0; // unlock all doors of this type } UnlockKeyLock(key_num, actor); } return true; case TAG_COMBO_SWITCH_EVERYTHING: // change the switch state AnimateSwitch(actor, -1); PlaySound(DIGI_REGULARSWITCH, actor, v3df_none); if (ComboSwitchTest(TAG_COMBO_SWITCH_EVERYTHING, actor->spr.hitag)) { DoMatchEverything(pp, actor->spr.hitag, true); } return true; case TAG_COMBO_SWITCH_EVERYTHING_ONCE: // change the switch state AnimateSwitch(actor, -1); PlaySound(DIGI_REGULARSWITCH, actor, v3df_none); if (ComboSwitchTest(TAG_COMBO_SWITCH_EVERYTHING, actor->spr.hitag)) { DoMatchEverything(pp, actor->spr.hitag, true); } actor->spr.lotag = 0; actor->spr.hitag = 0; return true; case TAG_SWITCH_EVERYTHING: state = AnimateSwitch(actor, -1); DoMatchEverything(pp, actor->spr.hitag, state); return true; case TAG_SWITCH_EVERYTHING_ONCE: state = AnimateSwitch(actor, -1); DoMatchEverything(pp, actor->spr.hitag, state); actor->spr.lotag = 0; actor->spr.hitag = 0; return true; case TAG_LIGHT_SWITCH: state = AnimateSwitch(actor, -1); DoLightingMatch(actor->spr.hitag, state); return true; case TAG_SPRITE_SWITCH_VATOR: { // make sure all vators are inactive before allowing // to repress switch if (!TestVatorMatchActive(actor->spr.hitag)) DoVatorMatch(pp, actor->spr.hitag); if (!TestSpikeMatchActive(actor->spr.hitag)) DoSpikeMatch(actor->spr.hitag); if (!TestRotatorMatchActive(actor->spr.hitag)) DoRotatorMatch(pp, actor->spr.hitag, false); if (!TestSlidorMatchActive(actor->spr.hitag)) DoSlidorMatch(pp, actor->spr.hitag, false); return true; } case TAG_LEVEL_EXIT_SWITCH: { AnimateSwitch(actor, -1); PlaySound(DIGI_BIGSWITCH, actor, v3df_none); MapRecord *map; if (actor->spr.hitag) map = FindMapByLevelNum(actor->spr.hitag); else map = FindNextMap(currentLevel); ChangeLevel(map, g_nextskill); return true; } case TAG_SPRITE_GRATING: { change_actor_stat(actor, STAT_NO_STATE); SpawnUser(actor, 0, nullptr); actor->user.ActorActionFunc = DoGrating; actor->spr.lotag = 0; actor->spr.hitag /= 2; return true; } case TAG_SO_SCALE_SWITCH: AnimateSwitch(actor, -1); DoSectorObjectSetScale(actor->spr.hitag); return true; case TAG_SO_SCALE_ONCE_SWITCH: AnimateSwitch(actor, -1); DoSectorObjectSetScale(actor->spr.hitag); actor->spr.lotag = 0; actor->spr.hitag = 0; return true; case TAG_SO_EVENT_SWITCH: { state = AnimateSwitch(actor, -1); DoMatchEverything(nullptr, actor->spr.hitag, state); actor->spr.hitag = 0; actor->spr.lotag = 0; PlaySound(DIGI_REGULARSWITCH, actor, v3df_none); break; } case TAG_ROTATE_SO_SWITCH: { short so_num; SECTOR_OBJECT* sop; so_num = actor->spr.hitag; ASSERT(so_num <= 20); ASSERT(SectorObject[so_num].num_sectors != -1); AnimateSwitch(actor, -1); sop = &SectorObject[so_num]; sop->ang_tgt = NORM_ANGLE(sop->ang_tgt + 512); PlaySound(DIGI_BIGSWITCH, actor, v3df_none); return true; break; } } return false; } int DoTrapReset(short match) { SWStatIterator it(STAT_TRAP); while (auto actor = it.Next()) { if (actor->spr.lotag != match) continue; // if correct type and matches if (actor->spr.hitag == FIREBALL_TRAP) actor->user.WaitTics = 0; // if correct type and matches if (actor->spr.hitag == BOLT_TRAP) actor->user.WaitTics = 0; // if correct type and matches if (actor->spr.hitag == SPEAR_TRAP) actor->user.WaitTics = 0; } return 0; } int DoTrapMatch(short match) { // may need to be reset to fire immediately SWStatIterator it(STAT_TRAP); while (auto actor = it.Next()) { if (actor->spr.lotag != match) continue; // if correct type and matches if (actor->spr.hitag == FIREBALL_TRAP) { actor->user.WaitTics -= synctics; if (actor->user.WaitTics <= 0) { actor->user.WaitTics = 1 * 120; InitFireballTrap(actor); } } // if correct type and matches if (actor->spr.hitag == BOLT_TRAP) { actor->user.WaitTics -= synctics; if (actor->user.WaitTics <= 0) { actor->user.WaitTics = 1 * 120; InitBoltTrap(actor); } } // if correct type and matches if (actor->spr.hitag == SPEAR_TRAP) { actor->user.WaitTics -= synctics; if (actor->user.WaitTics <= 0) { actor->user.WaitTics = 1 * 120; InitSpearTrap(actor); } } } return 0; } void OperateTripTrigger(PLAYER* pp) { if (Prediction) return; if (!pp->insector()) return; sectortype* sectp = pp->cursector; // old method switch (pp->cursector->lotag) { // same tag for sector as for switch case TAG_LEVEL_EXIT_SWITCH: { MapRecord *map; if (sectp->hitag) map = FindMapByLevelNum(sectp->hitag); else map = FindNextMap(currentLevel); ChangeLevel(map, g_nextskill); break; } case TAG_SECRET_AREA_TRIGGER: if (pp == Player+myconnectindex) PlayerSound(DIGI_ANCIENTSECRET, v3df_dontpan|v3df_doppler|v3df_follow,pp); SECRET_Trigger(sectnum(pp->cursector)); PutStringInfo(pp, GStrings("TXTS_SECRET")); // always give to the first player Player->SecretsFound++; sectp->lotag = 0; sectp->hitag = 0; break; case TAG_TRIGGER_EVERYTHING: DoMatchEverything(pp, sectp->hitag, -1); break; case TAG_TRIGGER_EVERYTHING_ONCE: DoMatchEverything(pp, sectp->hitag, -1); sectp->lotag = 0; sectp->hitag = 0; break; case TAG_SECTOR_TRIGGER_VATOR: if (!TestVatorMatchActive(sectp->hitag)) DoVatorMatch(pp, sectp->hitag); if (!TestSpikeMatchActive(sectp->hitag)) DoSpikeMatch(sectp->hitag); if (!TestRotatorMatchActive(sectp->hitag)) DoRotatorMatch(pp, sectp->hitag, false); if (!TestSlidorMatchActive(sectp->hitag)) DoSlidorMatch(pp, sectp->hitag, false); break; case TAG_LIGHT_TRIGGER: DoLightingMatch(sectp->hitag, -1); break; case TAG_SO_SCALE_TRIGGER: DoSectorObjectSetScale(sectp->hitag); break; case TAG_SO_SCALE_ONCE_TRIGGER: DoSectorObjectSetScale(sectp->hitag); sectp->lotag = 0; sectp->hitag = 0; break; case TAG_TRIGGER_ACTORS: { int dist; int i; dist = sectp->hitag; SWStatIterator it(STAT_ENEMY); while (auto actor = it.Next()) { if (actor->user.Flags & (SPR_WAIT_FOR_TRIGGER)) { if (Distance(actor->spr.pos.X, actor->spr.pos.Y, pp->pos.X, pp->pos.Y) < dist) { actor->user.targetActor = pp->actor; actor->user.Flags &= ~(SPR_WAIT_FOR_TRIGGER); } } } break; } case TAG_TRIGGER_MISSILE_TRAP: { // reset traps so they fire immediately DoTrapReset(pp->cursector->hitag); break; } case TAG_TRIGGER_EXPLODING_SECTOR: { DoMatchEverything(nullptr, pp->cursector->hitag, -1); break; } case TAG_SPAWN_ACTOR_TRIGGER: { DoMatchEverything(nullptr, pp->cursector->hitag, -1); pp->cursector->hitag = 0; pp->cursector->lotag = 0; break; } case TAG_SO_EVENT_TRIGGER: { DoMatchEverything(nullptr, pp->cursector->hitag, -1); pp->cursector->hitag = 0; pp->cursector->lotag = 0; PlaySound(DIGI_REGULARSWITCH, pp, v3df_none); break; } } } void OperateContinuousTrigger(PLAYER* pp) { if (Prediction) return; if (!pp->insector()) return; switch (pp->cursector->lotag) { case TAG_TRIGGER_MISSILE_TRAP: { DoTrapMatch(pp->cursector->hitag); break; } } } short PlayerTakeSectorDamage(PLAYER* pp) { auto sectu = pp->cursector; DSWActor* actor = pp->actor; // the calling routine must make sure sectu exists if ((actor->user.DamageTics -= synctics) < 0) { actor->user.DamageTics = DAMAGE_TIME; PlayerUpdateHealth(pp, -sectu->damage); PlayerCheckDeath(pp, nullptr); } return 0; } // Needed in order to see if Player should grunt if he can't find a wall to operate on // If player is too far away, don't grunt enum { PLAYER_SOUNDEVENT_TAG = 900 }; bool NearThings(PLAYER* pp) { HitInfo near; // Check player's current sector for triggered sound if (pp->cursector->hitag == PLAYER_SOUNDEVENT_TAG) { if (pp == Player+myconnectindex) PlayerSound(pp->cursector->lotag, v3df_follow|v3df_dontpan,pp); return false; } neartag(pp->pos, pp->cursector, pp->angle.ang.asbuild(), near, 1024, NTAG_SEARCH_LO_HI); // hit a sprite? Check to see if it has sound info in it! // This can work with any sprite! if (near.actor() != nullptr) { auto actor = near.actor(); // Go through list of cases if (actor->spr.hitag == PLAYER_SOUNDEVENT_TAG) { if (pp == Player+myconnectindex) PlayerSound(actor->spr.lotag, v3df_follow|v3df_dontpan,pp); } return false; // Return false so he doesn't grunt } if (near.hitWall != nullptr) { // Check player's current sector for triggered sound if (near.hitWall->hitag == PLAYER_SOUNDEVENT_TAG) { if (pp == Player+myconnectindex) PlayerSound(near.hitWall->lotag, v3df_follow|v3df_dontpan,pp); return false; // We are playing a sound so don't return true } return true; } // This only gets called if nothing else worked, check for nearness to a wall { HitInfo hit{}; short dang = pp->angle.ang.asbuild(); FAFhitscan(pp->pos.X, pp->pos.Y, pp->pos.Z - Z(30), pp->cursector, // Start position bcos(dang), // X vector of 3D ang bsin(dang), // Y vector of 3D ang 0, // Z vector of 3D ang hit, CLIPMASK_MISSILE); if (hit.hitSector == nullptr) return false; if (Distance(hit.hitpos.X, hit.hitpos.Y, pp->pos.X, pp->pos.Y) > 1500) return false; // hit a sprite? if (hit.actor() != nullptr) return false; if (near.hitSector != nullptr) return true; if (hit.hitWall != nullptr) { // Near a plain old vanilla wall. Can't do anything but grunt. if (!(hit.hitWall->extra & WALLFX_DONT_STICK) && pp == Player+myconnectindex) { if (StdRandomRange(1000) > 970) PlayerSound(DIGI_HITTINGWALLS, v3df_follow|v3df_dontpan,pp); else PlayerSound(DIGI_SEARCHWALL, v3df_follow|v3df_dontpan,pp); } return true; } return false; } } short nti_cnt; void NearTagList(NEAR_TAG_INFO* ntip, PLAYER* pp, int z, int dist, int type, int count) { short save_lotag, save_hitag; HitInfo near; neartag({ pp->pos.X, pp->pos.Y, z }, pp->cursector, pp->angle.ang.asbuild(), near, dist, type); if (near.hitSector != nullptr) { auto ntsec = near.hitSector; // save off values save_lotag = ntsec->lotag; save_hitag = ntsec->hitag; ntip->dist = near.hitpos.X; ntip->sectp = ntsec; ntip->wallp = nullptr; ntip->actor = nullptr; nti_cnt++; ntip++; if (nti_cnt >= count) return; // remove them ntsec->lotag = 0; ntsec->hitag = 0; NearTagList(ntip, pp, z, dist, type, count); // reset off values ntsec->lotag = save_lotag; ntsec->hitag = save_hitag; } else if (near.hitWall != nullptr) { auto ntwall = near.hitWall; // save off values save_lotag = ntwall->lotag; save_hitag = ntwall->hitag; ntip->dist = near.hitpos.X; ntip->sectp = nullptr; ntip->wallp = ntwall; ntip->actor = nullptr; nti_cnt++; ntip++; if (nti_cnt >= count) return; // remove them ntwall->lotag = 0; ntwall->hitag = 0; NearTagList(ntip, pp, z, dist, type, count); // reset off values ntwall->lotag = save_lotag; ntwall->hitag = save_hitag; } else if (near.actor() != nullptr) { auto actor = near.actor(); // save off values save_lotag = actor->spr.lotag; save_hitag = actor->spr.hitag; ntip->dist = near.hitpos.X; ntip->sectp = nullptr; ntip->wallp = nullptr; ntip->actor = actor; nti_cnt++; ntip++; if (nti_cnt >= count) return; // remove them actor->spr.lotag = 0; actor->spr.hitag = 0; NearTagList(ntip, pp, z, dist, type, count); // reset off values actor->spr.lotag = save_lotag; actor->spr.hitag = save_hitag; } else { ntip->dist = -1; ntip->sectp = nullptr; ntip->wallp = nullptr; ntip->actor = nullptr; nti_cnt++; ntip++; return; } } void BuildNearTagList(NEAR_TAG_INFO* ntip, int size, PLAYER* pp, int z, int dist, int type, int count) { memset(ntip, -1, size); nti_cnt = 0; NearTagList(ntip, pp, z, dist, type, count); } int DoPlayerGrabStar(PLAYER* pp) { int i; // MUST check exact z's of each star or it will never work for (i = 0; i < MAX_STAR_QUEUE; i++) { auto actor = StarQueue[i]; if (actor != nullptr) { if (FindDistance3D(actor->spr.pos.X - pp->pos.X, actor->spr.pos.Y - pp->pos.Y, actor->spr.pos.Z - pp->pos.Z + Z(12)) < 500) { break; } } } if (i < MAX_STAR_QUEUE) { // Pull a star out of wall and up your ammo PlayerUpdateAmmo(pp, WPN_STAR, 1); PlaySound(DIGI_ITEM, StarQueue[i], v3df_none); KillActor(StarQueue[i]); StarQueue[i] = nullptr; if (pp->WpnFlags & (BIT(WPN_STAR))) return true; pp->WpnFlags |= (BIT(WPN_STAR)); InitWeaponStar(pp); return true; } return false; } void PlayerOperateEnv(PLAYER* pp) { bool found; if (Prediction || !pp->actor) return; // // Switch & door activations // if (pp->input.actions & SB_OPEN) { if (pp->KeyPressBits & SB_OPEN) { // if space bar pressed short nt_ndx; NEAR_TAG_INFO nti[16]; if (DoPlayerGrabStar(pp)) { pp->KeyPressBits &= ~SB_OPEN; } else { NearThings(pp); // Check for player sound specified in a level sprite } BuildNearTagList(nti, sizeof(nti), pp, pp->pos.Z, 2048L, NTAG_SEARCH_LO_HI, 8); found = false; // try and find a sprite for (nt_ndx = 0; nti[nt_ndx].dist >= 0; nt_ndx++) { if (nti[nt_ndx].actor != nullptr && nti[nt_ndx].dist < 1024 + 768) { if (OperateSprite(nti[nt_ndx].actor, true)) { pp->KeyPressBits &= ~SB_OPEN; found = true; } } } // if not found look at different z positions if (!found) { int z[3]; DSWActor* plActor = pp->actor; z[0] = plActor->spr.pos.Z - ActorSizeZ(plActor) - Z(10); z[1] = plActor->spr.pos.Z; z[2] = (z[0] + z[1]) >> 1; for (unsigned i = 0; i < SIZ(z); i++) { BuildNearTagList(nti, sizeof(nti), pp, z[i], 1024 + 768L, NTAG_SEARCH_LO_HI, 8); for (nt_ndx = 0; nti[nt_ndx].dist >= 0; nt_ndx++) { if (nti[nt_ndx].actor != nullptr && nti[nt_ndx].dist < 1024 + 768) { if (OperateSprite(nti[nt_ndx].actor, true)) { pp->KeyPressBits &= ~SB_OPEN; break; } } } } } { int neartaghitdist; sectortype* neartagsector; neartaghitdist = nti[0].dist; neartagsector = nti[0].sectp; if (neartagsector != nullptr && neartaghitdist < 1024) { if (OperateSector(neartagsector, true)) { // Release the key pp->KeyPressBits &= ~SB_OPEN; } } } // // Trigger operations // switch (pp->cursector->lotag) { case TAG_VATOR: DoVatorOperate(pp, pp->cursector); DoSpikeOperate(pp->cursector); DoRotatorOperate(pp, pp->cursector); DoSlidorOperate(pp, pp->cursector); break; case TAG_SPRING_BOARD: DoSpringBoard(pp); pp->KeyPressBits &= ~SB_OPEN; break; case TAG_DOOR_ROTATE: if (OperateSector(pp->cursector, true)) pp->KeyPressBits &= ~SB_OPEN; break; } } } else { // Reset the key when syncbit key is not in use pp->KeyPressBits |= SB_OPEN; } // //////////////////////////// // // Sector Damage // // //////////////////////////// sectortype* sectp = pp->cursector; if (pp->insector() && sectp->hasU() && sectp->damage) { if ((sectp->flags & SECTFU_DAMAGE_ABOVE_SECTOR)) { PlayerTakeSectorDamage(pp); } else if ((ActorZOfBottom(pp->actor) >= sectp->floorz) && !(pp->Flags & PF_DIVING)) { PlayerTakeSectorDamage(pp); } } else { pp->actor->user.DamageTics = 0; } // //////////////////////////// // // Trigger stuff // // //////////////////////////// OperateContinuousTrigger(pp); // just changed sectors if (pp->lastcursector != pp->cursector) { OperateTripTrigger(pp); if (pp->insector() && (pp->cursector->extra & SECTFX_WARP_SECTOR)) { if (!(pp->Flags2 & PF2_TELEPORTED)) { DoPlayerWarpTeleporter(pp); } } pp->Flags2 &= ~(PF2_TELEPORTED); } } void DoSineWaveFloor(void) { SINE_WAVE_FLOOR *swf; int newz; int wave; int flags; for (wave = 0; wave < MAX_SINE_WAVE; wave++) { for (swf = &SineWaveFloor[wave][0], flags = swf->flags; swf->sectp != nullptr && swf < &SineWaveFloor[wave][SIZ(SineWaveFloor[wave])]; swf++) { swf->sintable_ndx = NORM_ANGLE(swf->sintable_ndx + (synctics << swf->speed_shift)); if ((flags & SINE_FLOOR)) { newz = swf->floor_origz + MulScale(swf->range, bsin(swf->sintable_ndx), 14); swf->sectp->setfloorz(newz); } if ((flags & SINE_CEILING)) { newz = swf->ceiling_origz + MulScale(swf->range, bsin(swf->sintable_ndx), 14); swf->sectp->setceilingz(newz); } } } /* SLOPED SIN-WAVE FLOORS: It's best to program sloped sin-wave floors in 2 steps: 1. First set the floorz of the floor as the sin code normally does it. 2. Adjust the slopes by calling alignflorslope once for each sector. Note: For this to work, the first wall of each sin-wave sector must be aligned on the same side of each sector for the entire strip. */ for (wave = 0; wave < MAX_SINE_WAVE; wave++) { for (swf = &SineWaveFloor[wave][0], flags = swf->flags; swf->sectp != nullptr && swf < &SineWaveFloor[wave][SIZ(SineWaveFloor[wave])]; swf++) { auto sect = swf->sectp; if (!(sect->floorstat & CSTAT_SECTOR_SLOPE)) continue; if ((flags & SINE_SLOPED)) { walltype* wal; if (sect->wallnum == 4) { //Set wal to the wall on the opposite side of the sector wal = sect->firstWall() + 2; //Pass (Sector, x, y, z) alignflorslope(sect,wal->pos.X,wal->pos.Y, wal->nextSector()->floorz); } } } } } void DoSineWaveWall(void) { SINE_WALL *sw; int New; short sw_num; for (sw_num = 0; sw_num < MAX_SINE_WAVE; sw_num++) { for (sw = &SineWall[sw_num][0]; sw->wallp != nullptr && sw < &SineWall[sw_num][MAX_SINE_WALL_POINTS]; sw++) { auto wal = sw->wallp; // move through the sintable sw->sintable_ndx = NORM_ANGLE(sw->sintable_ndx + (synctics << sw->speed_shift)); if (!sw->type) { New = sw->orig_xy + MulScale(sw->range, bsin(sw->sintable_ndx), 14); dragpoint(wal, wal->pos.X, New); } else { New = sw->orig_xy + MulScale(sw->range, bsin(sw->sintable_ndx), 14); dragpoint(wal, New, wal->pos.Y); } } } } void DoAnim(int numtics) { int i, animval; for (i = AnimCnt - 1; i >= 0; i--) { animval = Anim[i].Addr(true); // if LESS THAN goal if (animval < Anim[i].goal) { // move it animval += (numtics * PIXZ(Anim[i].vel)); Anim[i].vel += Anim[i].vel_adj * numtics; // if the other way make it equal if (animval > Anim[i].goal) animval = Anim[i].goal; } // if GREATER THAN goal if (animval > Anim[i].goal) { animval -= (numtics * PIXZ(Anim[i].vel)); Anim[i].vel += Anim[i].vel_adj * numtics; if (animval < Anim[i].goal) animval = Anim[i].goal; } Anim[i].Addr(true) =animval; // EQUAL this entry has finished if (animval == Anim[i].goal) { ANIM_CALLBACKp acp = Anim[i].callback; // do a callback when done if not nullptr if (Anim[i].callback) (*Anim[i].callback)(&Anim[i], Anim[i].callbackdata); // only delete it if the callback has not changed // Logic here is that if the callback changed then something // else must be happening with it - dont delete it if (Anim[i].callback == acp) { // decrement the count AnimCnt--; // move the last entry to the current one to free the last // entry up Anim[i] = Anim[AnimCnt]; } } } } void AnimClear(void) { AnimCnt = 0; } short AnimGetGoal(int animtype, int animindex, DSWActor* animactor) { int i, j; j = -1; for (i = 0; i < AnimCnt; i++) { if (animtype == Anim[i].animtype && animindex == Anim[i].animindex && animactor == Anim[i].animactor ) { j = i; break; } } return j; } void AnimDelete(int animtype, int animindex, DSWActor* animactor) { int i, j; j = -1; for (i = 0; i < AnimCnt; i++) { if (animtype == Anim[i].animtype && animindex == Anim[i].animindex && animactor == Anim[i].animactor ) { j = i; break; } } if (j == -1) return; // decrement the count AnimCnt--; // move the last entry to the current one to free the last entry up Anim[j] = Anim[AnimCnt]; } int AnimSet(int animtype, int animindex, DSWActor* animactor, fixed_t thegoal, int thevel) { int i, j; ASSERT(AnimCnt < MAXANIM - 1); j = AnimCnt; // look for existing animation and reset it for (i = 0; i < AnimCnt; i++) { if (animtype == Anim[i].animtype && animindex == Anim[i].animindex && animactor == Anim[i].animactor ) { j = i; break; } } Anim[j].animtype = animtype; Anim[j].animindex = animindex; Anim[j].animactor = animactor; Anim[j].goal = thegoal; Anim[j].vel = Z(thevel); Anim[j].vel_adj = 0; Anim[j].callback = nullptr; Anim[j].callbackdata = nullptr; if (j == AnimCnt) AnimCnt++; return j; } short AnimSetCallback(short anim_ndx, ANIM_CALLBACKp call, SECTOR_OBJECT* data) { ASSERT(anim_ndx < AnimCnt); if (anim_ndx == -1) return anim_ndx; Anim[anim_ndx].callback = call; Anim[anim_ndx].callbackdata = data; return anim_ndx; } short AnimSetVelAdj(short anim_ndx, short vel_adj) { ASSERT(anim_ndx < AnimCnt); if (anim_ndx == -1) return anim_ndx; Anim[anim_ndx].vel_adj = vel_adj; return anim_ndx; } void DoPanning(void) { int nx, ny; int i; sectortype* sectp; walltype* wallp; SWStatIterator it(STAT_FLOOR_PAN); while (auto actor = it.Next()) { sectp = actor->sector(); nx = MulScale(actor->spr.xvel, bcos(actor->spr.ang), 20); ny = MulScale(actor->spr.xvel, bsin(actor->spr.ang), 20); sectp->addfloorxpan((float)nx); sectp->addfloorypan((float)ny); } it.Reset(STAT_CEILING_PAN); while (auto actor = it.Next()) { sectp = actor->sector(); nx = MulScale(actor->spr.xvel, bcos(actor->spr.ang), 20); ny = MulScale(actor->spr.xvel, bsin(actor->spr.ang), 20); sectp->addceilingxpan((float)nx); sectp->addceilingypan((float)ny); } it.Reset(STAT_WALL_PAN); while (auto actor = it.Next()) { wallp = actor->tempwall; nx = MulScale(actor->spr.xvel, bcos(actor->spr.ang), 20); ny = MulScale(actor->spr.xvel, bsin(actor->spr.ang), 20); wallp->addxpan((float)nx); wallp->addypan((float)ny); } } void DoSector(void) { SECTOR_OBJECT* sop; bool riding; int sync_flag; short pnum; int min_dist,dist,a,b,c; PLAYER* pp; for (sop = SectorObject; sop < &SectorObject[MAX_SECTOR_OBJECTS]; sop++) { if (SO_EMPTY(sop)) continue; riding = false; min_dist = 999999; TRAVERSE_CONNECT(pnum) { pp = &Player[pnum]; if (pp->sop_riding == sop) { riding = true; pp->sop_riding = nullptr; break; } else { DISTANCE(pp->pos.X, pp->pos.Y, sop->pmid.X, sop->pmid.Y, dist, a, b, c); if (dist < min_dist) min_dist = dist; } } if (sop->Animator) { (*sop->Animator)(sop); continue; } // force sync SOs to be updated regularly if ((sync_flag = (sop->flags & (SOBJ_SYNC1|SOBJ_SYNC2))) != 0) { if (sync_flag == SOBJ_SYNC1) MoveSectorObjects(sop, synctics); else { if (MoveSkip2 == 0) MoveSectorObjects(sop, synctics*2); } continue; } if (riding) { // if riding move smoothly // update every time MoveSectorObjects(sop, synctics); } else { if (min_dist < 15000) { // if close update every other time if (MoveSkip2 == 0) MoveSectorObjects(sop, synctics * 2); } else { // if further update every 4th time if (MoveSkip4 == 0) MoveSectorObjects(sop, synctics * 4); } } } DoPanning(); DoLighting(); DoSineWaveFloor(); DoSineWaveWall(); DoSpringBoardDown(); } #include "saveable.h" static saveable_code saveable_sector_code[] = { SAVE_CODE(DoSpawnSpot), }; saveable_module saveable_sector = { // code saveable_sector_code, SIZ(saveable_sector_code), // data nullptr, 0 }; END_SW_NS