//------------------------------------------------------------------------- /* 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" #define QUIET #include "build.h" #include "automap.h" #include "names2.h" #include "panel.h" #include "game.h" #include "jsector.h" #include "gamecontrol.h" #include "gamefuncs.h" #include "network.h" #include "pal.h" #include "player.h" #include "jtags.h" #include "parent.h" #include "misc.h" #include "menus.h" #include "interpolate.h" #include "interpso.h" #include "sector.h" #include "razemenu.h" #include "v_2ddrawer.h" #include "v_video.h" #include "v_draw.h" #include "render.h" #include "razefont.h" #include "models/modeldata.h" extern DCoreActor* wall_to_sprite_actors[8]; BEGIN_SW_NS int display_mirror; static int OverlapDraw = false; extern bool QuitFlag, SpriteInfo; extern bool Voxel; extern int f_c; extern TILE_INFO_TYPE aVoxelArray[MAXTILES]; void PreDrawStackedWater(void); //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- #if 1 void ShadeSprite(tspritetype* tsp) { // set shade of sprite tsp->shade = tsp->sectp->floorshade - 25; if (tsp->shade > -3) tsp->shade = -3; if (tsp->shade < -30) tsp->shade = -30; } #else #endif //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- int GetRotation(tspriteArray& tsprites, int tSpriteNum, const DVector2& view) { static const uint8_t RotTable8[] = {0, 7, 6, 5, 4, 3, 2, 1}; static const uint8_t RotTable5[] = {0, 1, 2, 3, 4, 3, 2, 1}; int rotation; tspritetype* tsp = tsprites.get(tSpriteNum); auto ownerActor = static_cast(tsp->ownerActor); if (!ownerActor->hasU() || ownerActor->user.RotNum == 0) return 0; // Get which of the 8 angles of the sprite to draw (0-7) // rotation ranges from 0-7 DAngle angle2 = (tsp->pos - view).Angle(); rotation = (tsp->Angles.Yaw + DAngle180 + DAngle22_5 * 0.5 - angle2).Buildang() & 2047; rotation = (rotation >> 8) & 7; if (ownerActor->user.RotNum == 5) { if ((ownerActor->user.Flags & SPR_XFLIP_TOGGLE)) { if (rotation <= 4) { // leave rotation alone tsp->cstat &= ~(CSTAT_SPRITE_XFLIP); } else { rotation = (8 - rotation); tsp->cstat |= (CSTAT_SPRITE_XFLIP); // clear x-flipping bit } } else { if (rotation > 3 || rotation == 0) { // leave rotation alone tsp->cstat &= ~(CSTAT_SPRITE_XFLIP); // clear x-flipping bit } else { rotation = (8 - rotation); tsp->cstat |= (CSTAT_SPRITE_XFLIP); // set } } // Special case bunk int ID = ownerActor->user.ID; if (ID == TOILETGIRL_R0 || ID == WASHGIRL_R0 || ID == TRASHCAN || ID == CARGIRL_R0 || ID == MECHANICGIRL_R0 || ID == PRUNEGIRL_R0 || ID == SAILORGIRL_R0) tsp->cstat &= ~(CSTAT_SPRITE_XFLIP); // clear x-flipping bit return RotTable5[rotation]; } return RotTable8[rotation]; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- /* !AIC - At draw time this is called for actor rotation. GetRotation() is more complex than needs to be in part because importing of actor rotations and x-flip directions was not standardized. */ int SetActorRotation(tspriteArray& tsprites, int tSpriteNum, const DVector2& viewpos) { tspritetype* tsp = tsprites.get(tSpriteNum); auto ownerActor = static_cast(tsp->ownerActor); int StateOffset, Rotation; if (!ownerActor->hasU()) return 0; // don't modify ANY tu vars - back them up! STATE* State = ownerActor->user.State; STATE* StateStart = ownerActor->user.StateStart; if (ownerActor->user.RotNum == 0) return 0; // Get the offset into the State animation StateOffset = int(State - StateStart); // Get the rotation angle Rotation = GetRotation(tsprites, tSpriteNum, viewpos); ASSERT(Rotation < 5); // Reset the State animation start based on the Rotation StateStart = ownerActor->user.Rot[Rotation]; // Set the sprites state State = StateStart + StateOffset; // set the picnum here - may be redundant, but we just changed states and // thats a big deal tsp->picnum = State->Pic; return 0; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- double DoShadowFindGroundPoint(tspritetype* tspr) { // USES TSPRITE !!!!! auto ownerActor = static_cast(tspr->ownerActor); Collision ceilhit, florhit; double hiz, loz = ownerActor->user.loz; ESpriteFlags save_cstat, bak_cstat; // recursive routine to find the ground - either sector or floor sprite // skips over enemy and other types of sprites // IMPORTANT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // This will return invalid FAF ceiling and floor heights inside of analyzesprite // because the ceiling and floors get moved out of the way for drawing. save_cstat = tspr->cstat; tspr->cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN); FAFgetzrangepoint(tspr->pos, tspr->sectp, &hiz, &ceilhit, &loz, &florhit); tspr->cstat = save_cstat; switch (florhit.type) { case kHitSprite: { auto hitactor = florhit.actor(); if ((hitactor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR)) { // found a sprite floor return loz; } else { // reset the blocking bit of what you hit and try again - // recursive bak_cstat = hitactor->spr.cstat; hitactor->spr.cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN); loz = DoShadowFindGroundPoint(tspr); hitactor->spr.cstat = bak_cstat; } break; } case kHitSector: break; default: ASSERT(true == false); break; } return loz; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void DoShadows(tspriteArray& tsprites, tspritetype* tsp, double viewz) { auto ownerActor = static_cast(tsp->ownerActor); int ground_dist = 0; int view_dist = 0; double loz; DVector2 scale; if (!ownerActor->hasU()) return; auto sect = tsp->sectp; // make sure its the correct sector // DoShadowFindGroundPoint calls FAFgetzrangepoint and this is sensitive updatesector(tsp->pos, §); if (sect == nullptr) { return; } tsp->sectp = sect; if (tsp->scale.Y > 0.25) { auto tex = TexMan.GetGameTexture(tsp->spritetexture()); double sizey = tex->GetDisplayHeight() * tsp->scale.Y; scale.Y = (tsp->scale.Y * 0.25) - (sizey / 2048.); scale.X = tsp->scale.X; } else { scale = tsp->scale; } loz = ownerActor->user.loz; if (ownerActor->user.lowActor) { if (!(ownerActor->user.lowActor->spr.cstat & (CSTAT_SPRITE_ALIGNMENT_WALL | CSTAT_SPRITE_ALIGNMENT_FLOOR))) { loz = DoShadowFindGroundPoint(tsp); } } // need to find the ground here // if below or close to sprites z don't bother to draw it if ((viewz - loz) > -8) { return; } tspritetype* tSpr = tsprites.newTSprite(); *tSpr = *tsp; // shadow is ALWAYS draw last - status is priority tSpr->statnum = MAXSTATUS; tSpr->shade = 127; tSpr->cstat |= CSTAT_SPRITE_TRANSLUCENT; tSpr->pos.Z = loz; // if close to shadows z shrink it view_dist = int(abs(loz - viewz)); if (view_dist < 32) view_dist = 256/view_dist; else view_dist = 0; // make shadow smaller depending on height from ground ground_dist = int(abs(loz - GetSpriteZOfBottom(tsp)) * (1./16)); double scaleofs = (ground_dist - view_dist) * REPEAT_SCALE; scale.X = clamp(scale.X + scaleofs, 0.0625, 4.); scale.Y = clamp(scale.Y + scaleofs, 0.0625, 4.); tSpr->scale = scale; if (tilehasmodelorvoxel(tsp->picnum,tsp->pal)) { tSpr->scale.Y = (0); // cstat: trans reverse // clipdist: tell mdsprite.cpp to use Z-buffer hacks to hide overdraw issues tSpr->clipdist |= TSPR_FLAGS_MDHACK; tSpr->cstat |= CSTAT_SPRITE_TRANS_FLIP; } else { // Alter the shadow's position so that it appears behind the sprite itself. auto look = (tSpr->pos.XY() - Player[screenpeek].si.XY()).Angle(); tSpr->pos.XY() += look.ToVector() * 2; } // Check for voxel items and use a round generic pic if so //DoVoxelShadow(New); } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void DoMotionBlur(tspriteArray& tsprites, tspritetype const * const tsp) { auto ownerActor = static_cast(tsp->ownerActor); DVector3 npos(0, 0, 0), dpos(0, 0, 0); int i; double repeat_adj = 0; DVector2 scale; double z_amt_per_pixel; auto angle = tsp->Angles.Yaw + DAngle180; if (!ownerActor->hasU() || ownerActor->vel.X == 0) { return; } if ((tsp->extra & SPRX_PLAYER_OR_ENEMY)) { z_amt_per_pixel = (- ownerActor->user.jump_speed * ACTORMOVETICS) * maptoworld / tsp->ownerActor->vel.X; } else { z_amt_per_pixel = -ownerActor->vel.Z / tsp->ownerActor->vel.X; } dpos.XY() = npos.XY() = angle.ToVector() * ownerActor->user.motion_blur_dist; dpos.Z = npos.Z = z_amt_per_pixel * ownerActor->user.motion_blur_dist * (1./16); scale = tsp->scale; switch ((ownerActor->user.Flags2 & SPR2_BLUR_TAPER)) { case 0: repeat_adj = 0; break; case SPR2_BLUR_TAPER_SLOW: repeat_adj = scale.X / (ownerActor->user.motion_blur_num*2); break; case SPR2_BLUR_TAPER_FAST: repeat_adj = scale.X / ownerActor->user.motion_blur_num; break; } for (i = 0; i < ownerActor->user.motion_blur_num; i++) { tspritetype* tSpr = tsprites.newTSprite(); *tSpr = *tsp; tSpr->cstat |= CSTAT_SPRITE_TRANSLUCENT|CSTAT_SPRITE_TRANS_FLIP; tSpr->pos += dpos; dpos += npos; tSpr->scale = scale; scale.X -= repeat_adj; scale.Y -= repeat_adj; } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void WarpCopySprite(tspriteArray& tsprites) { int spnum; int match; // look for the first one SWStatIterator it(STAT_WARP_COPY_SPRITE1); while (auto itActor = it.Next()) { match = itActor->spr.lotag; // look for the second one SWStatIterator it1(STAT_WARP_COPY_SPRITE2); while (auto itActor1 = it.Next()) { if (itActor1->spr.lotag == match) { auto sect1 = itActor->sector(); auto sect2 = itActor1->sector(); SWSectIterator it2(sect1); while (auto itActor2 = it.Next()) { if (itActor2 == itActor) continue; if (itActor2->spr.picnum == ST1) continue; tspritetype* newTSpr = renderAddTsprite(tsprites, itActor2); newTSpr->statnum = 0; newTSpr->pos += itActor1->spr.pos - itActor->spr.pos; newTSpr->sectp = itActor1->sector(); } it2.Reset(sect2); while (auto itActor2 = it2.Next()) { if (itActor2 == itActor1) continue; if (itActor2->spr.picnum == ST1) continue; tspritetype* newTSpr = renderAddTsprite(tsprites, itActor2); newTSpr->statnum = 0; newTSpr->pos += itActor->spr.pos - itActor1->spr.pos; newTSpr->sectp = itActor->sector(); } } } } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void DoStarView(tspritetype* tsp, DSWActor* tActor, double viewz) { extern STATE s_Star[], s_StarDown[]; extern STATE s_StarStuck[], s_StarDownStuck[]; double zdiff = viewz - tsp->pos.Z; if (abs(zdiff) > 24) { if (tActor->user.StateStart == s_StarStuck) tsp->picnum = s_StarDownStuck[tActor->user.State - s_StarStuck].Pic; else tsp->picnum = s_StarDown[tActor->user.State - s_Star].Pic; if (zdiff > 0) tsp->cstat |= (CSTAT_SPRITE_YFLIP); } else { if (zdiff > 0) tsp->cstat |= (CSTAT_SPRITE_YFLIP); } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- DSWActor* CopySprite(spritetypebase const* tsp, sectortype* newsector) { auto actorNew = insertActor(newsector, STAT_FAF_COPY); actorNew->spr.pos = tsp->pos; actorNew->spr.cstat = tsp->cstat; actorNew->spr.picnum = tsp->picnum; actorNew->spr.pal = tsp->pal; actorNew->spr.scale = tsp->scale; actorNew->spr.xoffset = tsp->xoffset; actorNew->spr.yoffset = tsp->yoffset; actorNew->spr.Angles.Yaw = tsp->Angles.Yaw; actorNew->spr.xint = tsp->xint; actorNew->spr.yint = tsp->yint; actorNew->spr.inittype = tsp->inittype; actorNew->spr.shade = tsp->shade; // this later also needs to copy the real velocity, because the sprite renderer accesses it. :( actorNew->spr.cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN); return actorNew; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- DSWActor* ConnectCopySprite(spritetypebase const* tsp) { sectortype* newsector; double testz; if (FAF_ConnectCeiling(tsp->sectp)) { newsector = tsp->sectp; testz = GetSpriteZOfTop(tsp) - 10; if (testz < tsp->sectp->ceilingz) updatesectorz(DVector3(tsp->pos, testz), &newsector); if (newsector != nullptr && newsector != tsp->sectp) { return CopySprite(tsp, newsector); } } if (FAF_ConnectFloor(tsp->sectp)) { newsector = tsp->sectp; testz = GetSpriteZOfBottom(tsp) + 10; if (testz > tsp->sectp->floorz) updatesectorz(DVector3(tsp->pos, testz), &newsector); if (newsector != nullptr && newsector != tsp->sectp) { return CopySprite(tsp, newsector); } } return nullptr; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- static void analyzesprites(tspriteArray& tsprites, const DVector3& viewpos, double interpfrac) { int tSpriteNum; static int ang = 0; PLAYER* pp = Player + screenpeek; int newshade=0; const int DART_PIC = 2526; const double DART_REPEAT = 0.25; ang = NORM_ANGLE(ang + 12); double smr4 = interpfrac + MoveSkip4; double smr2 = interpfrac + MoveSkip2; for (tSpriteNum = (int)tsprites.Size() - 1; tSpriteNum >= 0; tSpriteNum--) { tspritetype* tsp = tsprites.get(tSpriteNum); auto tActor = static_cast(tsp->ownerActor); auto tsectp = tsp->sectp; #if 0 // Brighten up the sprite if set somewhere else to do so if (tu && tActor->user.Vis > 0) { int tmpshade; // Having this prevent overflow tmpshade = tsp->shade - tActor->user.Vis; if (tmpshade < -128) tmpshade = -128; tsp->shade = tmpshade; tActor->user.Vis -= 8; } #endif // don't draw these if (tsp->statnum >= STAT_DONT_DRAW) { tsp->ownerActor = nullptr; continue; } if (tActor->hasU()) { if (tsp->statnum != STAT_DEFAULT) { if ((tActor->user.Flags & SPR_SKIP4)) { if (tsp->statnum <= STAT_SKIP4_INTERP_END) { tsp->pos = tActor->interpolatedpos(smr4 * 0.25); } } if ((tActor->user.Flags & SPR_SKIP2)) { if (tsp->statnum <= STAT_SKIP2_INTERP_END) { tsp->pos = tActor->interpolatedpos(smr2 * 0.5); } } } // workaround for mines and floor decals beneath the floor if (tsp->picnum == BETTY_R0 || tsp->picnum == FLOORBLOOD1) { double const florz = getflorzofslopeptr(tActor->sector(), tActor->spr.pos); if (tActor->spr.pos.Z > florz) tsp->pos.Z = florz; } if (r_shadows && (tActor->user.Flags & SPR_SHADOW)) { DoShadows(tsprites, tsp, viewpos.Z); } if (sw_darts) if (tActor->user.ID == 1793 || tsp->picnum == 1793) { tsp->picnum = 2519; tsp->scale = DVector2(0.421875, 0.453125); } if (tActor->user.ID == STAR1) { if (sw_darts) { tsp->picnum = DART_PIC; tsp->Angles.Yaw -= DAngle90 + mapangle(24); tsp->scale = DVector2(DART_REPEAT, DART_REPEAT); tsp->cstat |= (CSTAT_SPRITE_ALIGNMENT_WALL); } else DoStarView(tsp, tActor, viewpos.Z); } // rotation if (tActor->user.RotNum > 0) SetActorRotation(tsprites, tSpriteNum, viewpos.XY()); if (tActor->user.motion_blur_num) { DoMotionBlur(tsprites, tsp); } // set palette lookup correctly if (tsp->pal != tsectp->floorpal) { if (tsectp->floorpal == PALETTE_DEFAULT) { // default pal for sprite is stored in tActor->user.spal // mostly for players and other monster types tsp->pal = tActor->user.spal; } else { // if sector pal is something other than default uint8_t pal = tsectp->floorpal; bool nosectpal=false; // sprite does not take on the new pal if sector flag is set if (tsectp->hasU() && (tsectp->flags & SECTFU_DONT_COPY_PALETTE)) { pal = PALETTE_DEFAULT; nosectpal = true; } //if(tActor->user.spal == PALETTE_DEFAULT) if (tsp->hitag != SECTFU_DONT_COPY_PALETTE && tsp->hitag != LUMINOUS && !nosectpal && pal != PALETTE_FOG && pal != PALETTE_DIVE && pal != PALETTE_DIVE_LAVA) tsp->pal = pal; else tsp->pal = tActor->user.spal; } } // Sprite debug information mode if (tsp->hitag == 9997) { tsp->pal = PALETTE_RED_LIGHTING; // Turn it off, it gets reset by PrintSpriteInfo tActor->spr.hitag = 0; } } if (sw_darts) if (tsp->statnum == STAT_STAR_QUEUE) { tsp->picnum = DART_PIC; tsp->Angles.Yaw -= DAngle90; tsp->scale = DVector2(DART_REPEAT, DART_REPEAT); tsp->cstat |= (CSTAT_SPRITE_ALIGNMENT_WALL); } // Call my sprite handler // Does autosizing and voxel handling JAnalyzeSprites(tsp); // only do this of you are a player sprite //if (tsp->statnum >= STAT_PLAYER0 && tsp->statnum < STAT_PLAYER0 + MAX_SW_PLAYERS) if (tActor->hasU() && tActor->user.PlayerP) { // Shadow spell if (!(tsp->cstat & CSTAT_SPRITE_TRANSLUCENT)) ShadeSprite(tsp); // sw if its your playersprite if (Player[screenpeek].actor == tActor) { pp = Player + screenpeek; if (display_mirror || (pp->Flags & (PF_VIEW_FROM_OUTSIDE|PF_VIEW_FROM_CAMERA))) { if (pp->Flags & (PF_VIEW_FROM_OUTSIDE)) tsp->cstat |= (CSTAT_SPRITE_TRANSLUCENT); DVector3 pos; if (pp->Flags & (PF_CLIMBING)) { // move sprite forward some so he looks like he's // climbing pos.XY() = pp->si.XY() + tsp->Angles.Yaw.ToVector() * 13; } else { pos.X = pp->si.X; pos.Y = pp->si.Y; } pos.Z = tsp->pos.Z + pp->si.Z + pp->getViewHeightDiff(); tsp->pos = pos; tsp->Angles.Yaw = pp->siang; //continue; } else { // dont draw your sprite tsp->ownerActor = nullptr; //tsp->cstat |= (CSTAT_SPRITE_INVISIBLE); } } else // Otherwise just interpolate the player sprite { pp = tActor->user.PlayerP; tsp->pos = pp->actor->getRenderPos(interpfrac); tsp->Angles.Yaw = pp->actor->interpolatedyaw(interpfrac); } } if (OverlapDraw && FAF_ConnectArea(tsp->sectp) && tsp->ownerActor) { ConnectCopySprite(tsp); } // // kens original sprite shade code he moved out of the engine // switch (tsp->statnum) { case STAT_ENEMY: case STAT_DEAD_ACTOR: case STAT_FAF_COPY: break; default: newshade = tsp->shade; newshade += 6; if (newshade > 127) newshade = 127; tsp->shade = int8_t(newshade); } if ((tsectp->ceilingstat & CSTAT_SECTOR_SKY)) { newshade = tsp->shade; newshade += tsectp->ceilingshade; if (newshade > 127) newshade = 127; if (newshade < -128) newshade = -128; tsp->shade = int8_t(newshade); } else { newshade = tsp->shade; newshade += tsectp->floorshade; if (newshade > 127) newshade = 127; if (newshade < -128) newshade = -128; tsp->shade = int8_t(newshade); } if (tsp->hitag == 9998) tsp->shade = 127; // Invisible enemy ninjas // Correct shades for luminous sprites if (tsp->hitag == LUMINOUS) { tsp->shade = -128; } if (pp->NightVision && (tsp->extra & SPRX_PLAYER_OR_ENEMY)) { if (tActor->hasU() && tActor->user.ID == TRASHCAN) continue; // Don't light up trashcan tsp->pal = PALETTE_ILLUMINATE; // Make sprites REALLY bright green. tsp->shade = -128; } if (tActor->hasU() && tActor->user.PlayerP) { if ((tActor->user.Flags2 & SPR2_VIS_SHADING)) { if (Player[screenpeek].actor != tActor) { if (!(tActor->user.PlayerP->Flags & PF_VIEW_FROM_OUTSIDE)) { tsp->cstat &= ~(CSTAT_SPRITE_TRANSLUCENT); } } tsp->shade = 12 - StdRandomRange(30); } } } WarpCopySprite(tsprites); } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- tspritetype* get_tsprite(tspriteArray& tsprites, DSWActor* actor) { int tSpriteNum; for (tSpriteNum = (int)tsprites.Size() - 1; tSpriteNum >= 0; tSpriteNum--) { if (tsprites.get(tSpriteNum)->ownerActor == actor) return tsprites.get(tSpriteNum); } return nullptr; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void post_analyzesprites(tspriteArray& tsprites) { int tSpriteNum; for (tSpriteNum = (int)tsprites.Size() - 1; tSpriteNum >= 0; tSpriteNum--) { auto actor = static_cast(tsprites.get(tSpriteNum)->ownerActor); if (!actor) continue; // JBF: verify this is safe tspritetype* tsp = tsprites.get(tSpriteNum); if (actor->hasU()) { if (actor->user.ID == FIREBALL_FLAMES && actor->user.attachActor != nullptr) { tspritetype* const atsp = get_tsprite(tsprites, actor->user.attachActor); if (!atsp) { continue; } tsp->pos.X = atsp->pos.X; tsp->pos.Y = atsp->pos.Y; // statnum is priority - draw this ALWAYS first at 0 // statnum is priority - draw this ALWAYS last at MAXSTATUS if ((atsp->extra & SPRX_BURNABLE)) { atsp->statnum = 1; tsp->statnum = 0; } else tsp->statnum = MAXSTATUS; } } } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- std::pair GameInterface::GetCoordinates() { auto ppActor = Player[myconnectindex].actor; if (!ppActor) return std::make_pair(DVector3(DBL_MAX, 0, 0), nullAngle); return std::make_pair(ppActor->spr.pos, ppActor->spr.Angles.Yaw); } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void PrintSpriteInfo(PLAYER* pp) { const int Y_STEP = 7; //if (SpriteInfo && !LocationInfo) { auto actor = DoPickTarget(pp->actor, DAngle22_5/4, 2); actor->spr.hitag = 9997; // Special tag to make the actor glow red for one frame if (actor == nullptr) { Printf("SPRITENUM: NONE TARGETED\n"); return; } else Printf("SPRITENUM:%d\n", actor->GetIndex()); if (actor->hasU()) { Printf("ID:%d, ", actor->user.ID); Printf("PALETTE:%d, ", actor->user.spal); Printf("HEALTH:%d, ", actor->user.Health); Printf("WAITTICS:%d, ", actor->user.WaitTics); Printf("COUNTER:%d, ", actor->user.Counter); Printf("COUNTER2:%d\n", actor->user.Counter); } { Printf("POSX:%2.3f, ", actor->spr.pos.X); Printf("POSY:%2.3f, ", actor->spr.pos.Y); Printf("POSZ:%2.3f,", actor->spr.pos.Z); Printf("ANG:%2.0f\n", actor->spr.Angles.Yaw.Degrees()); } } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- static void DrawCrosshair(PLAYER* pp, const double interpfrac) { auto offsets = pp->Angles.getCrosshairOffsets(interpfrac); ::DrawCrosshair(2326, pp->actor->user.Health, offsets.first.X, offsets.first.Y + ((pp->Flags & PF_VIEW_FROM_OUTSIDE) ? 5 : 0), 2, offsets.second, shadeToLight(10)); } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void PreDraw(void) { int i; PreDrawStackedWater(); SWStatIterator it(STAT_FLOOR_SLOPE_DONT_DRAW); while (auto actor = it.Next()) { actor->sector()->floorstat &= ~(CSTAT_SECTOR_SLOPE); } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void PostDraw(void) { int i; SWStatIterator it(STAT_FLOOR_SLOPE_DONT_DRAW); while (auto actor = it.Next()) { actor->sector()->floorstat |= (CSTAT_SECTOR_SLOPE); } it.Reset(STAT_FAF_COPY); while (auto actor = it.Next()) { actor->clearUser(); actor->Destroy(); } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void PreDrawStackedWater(void) { SWStatIterator it(STAT_CEILING_FLOOR_PIC_OVERRIDE); while (auto itActor = it.Next()) { SWSectIterator it2(itActor->sector()); while (auto itActor2 = it2.Next()) { if (itActor2->hasU()) { if (itActor2->spr.statnum == STAT_ITEM) continue; if (itActor2->spr.statnum <= STAT_DEFAULT || itActor2->spr.statnum > STAT_PLAYER0 + MAX_SW_PLAYERS) continue; // code so that a copied sprite will not make another copy if (itActor2->spr.intangle == 0x4711) continue; auto actorNew = ConnectCopySprite(&itActor2->spr); if (actorNew != nullptr) { // spawn a user actorNew->allocUser(); actorNew->spr.intangle = 0x4711; // copy everything reasonable from the user that // analyzesprites() needs to draw the image actorNew->user.State = itActor2->user.State; actorNew->user.Rot = itActor2->user.Rot; actorNew->user.StateStart = itActor2->user.StateStart; actorNew->user.StateEnd = itActor2->user.StateEnd; actorNew->user.Flags = itActor2->user.Flags; actorNew->user.Flags2 = itActor2->user.Flags2; actorNew->user.RotNum = itActor2->user.RotNum; actorNew->user.ID = itActor2->user.ID; actorNew->user.PlayerP = itActor2->user.PlayerP; actorNew->user.spal = itActor2->user.spal; } } } } } void DoPlayerDiveMeter(PLAYER* pp); //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void UpdateWallPortalState() { // This is too obtuse to be maintained statically, but with 8 mirrors at most easy to be kept up to date. for (int i = 0; i < mirrorcnt; i++) { if (mirror[i].mirrorWall == nullptr) { continue; } walltype* wal = mirror[i].mirrorWall; if (wal->wallpicnum != MIRRORLABEL + i) { wal->portalflags = 0; continue; } wal->portalflags = 0; wal->portalnum = 0; if (!mirror[i].ismagic) { // a simple mirror wal->portalflags = PORTAL_WALL_MIRROR; } else { DSWActor* cam = mirror[i].cameraActor; if (cam) { if (!TEST_BOOL1(cam)) { wal->portalflags = PORTAL_WALL_TO_SPRITE; wal->portalnum = i; wall_to_sprite_actors[i] = cam; } } } } SWStatIterator it(STAT_CEILING_FLOOR_PIC_OVERRIDE); while (auto actor = it.Next()) { if (SP_TAG3(actor) == 0) { // back up ceilingpicnum and ceilingstat SP_TAG5(actor) = actor->sector()->ceilingpicnum; actor->sector()->ceilingpicnum = SP_TAG2(actor); SP_TAG4(actor) = actor->sector()->ceilingstat; actor->sector()->ceilingstat |= (ESectorFlags::FromInt(SP_TAG6(actor))); actor->sector()->ceilingstat &= ~(CSTAT_SECTOR_SKY); } else if (SP_TAG3(actor) == 1) { SP_TAG5(actor) = actor->sector()->floorpicnum; actor->sector()->floorpicnum = SP_TAG2(actor); SP_TAG4(actor) = actor->sector()->floorstat; actor->sector()->floorstat |= (ESectorFlags::FromInt(SP_TAG6(actor))); actor->sector()->floorstat &= ~(CSTAT_SECTOR_SKY); } } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void RestorePortalState() { SWStatIterator it(STAT_CEILING_FLOOR_PIC_OVERRIDE); while (auto actor = it.Next()) { if (SP_TAG3(actor) == 0) { // restore ceilingpicnum and ceilingstat actor->sector()->ceilingpicnum = SP_TAG5(actor); actor->sector()->ceilingstat = ESectorFlags::FromInt(SP_TAG4(actor)); actor->sector()->ceilingstat &= ~(CSTAT_SECTOR_SKY); } else if (SP_TAG3(actor) == 1) { actor->sector()->floorpicnum = SP_TAG5(actor); actor->sector()->floorstat = ESectorFlags::FromInt(SP_TAG4(actor)); actor->sector()->floorstat &= ~(CSTAT_SECTOR_SKY); } } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void drawscreen(PLAYER* pp, double interpfrac, bool sceneonly) { // prediction player if prediction is on, else regular player PLAYER* camerapp = (PredictionOn && CommEnabled && pp == Player+myconnectindex) ? ppp : pp; PreDraw(); PreUpdatePanel(interpfrac); if (!sceneonly) { // Stick at beginning of drawscreen DoInterpolations(interpfrac); if (cl_sointerpolation) so_dointerpolations(interpfrac); } // update render angles. pp->Angles.updateRenderAngles(interpfrac); // Get initial player position, interpolating if required. DVector3 tpos = camerapp->actor->getRenderPos(interpfrac); DRotator tangles = camerapp->Angles.getRenderAngles(interpfrac); sectortype* tsect = camerapp->cursector; updatesector(tpos, &tsect); if (pp->sop_riding || pp->sop_control) { if (pp->sop_control && (!cl_sointerpolation || (CommEnabled && !pp->sop_remote))) { tpos = pp->actor->getPosWithOffsetZ(); tangles.Yaw = pp->actor->spr.Angles.Yaw; } tsect = pp->cursector; updatesectorz(tpos, &tsect); } pp->si = tpos.plusZ(-pp->actor->getOffsetZ()); pp->siang = tangles.Yaw; QuakeViewChange(camerapp, tpos, tangles.Yaw); int vis = g_visibility; VisViewChange(camerapp, &vis); g_relvisibility = vis - g_visibility; if (pp->sop_remote) { DSWActor* ractor = pp->remoteActor; tangles.Yaw = TEST_BOOL1(ractor) ? ractor->spr.Angles.Yaw : (pp->sop_remote->pmid.XY() - tpos.XY()).Angle(); } if (pp->Flags & (PF_VIEW_FROM_OUTSIDE)) { tpos.Z -= 33; if (!calcChaseCamPos(tpos, pp->actor, &tsect, tangles, interpfrac, 128.)) { tpos.Z += 33; calcChaseCamPos(tpos, pp->actor, &tsect, tangles, interpfrac, 128.); } } if (!(pp->Flags & (PF_VIEW_FROM_CAMERA|PF_VIEW_FROM_OUTSIDE))) { if (cl_viewbob) { tpos.Z += interpolatedvalue(pp->obob_z + pp->opbob_amt, pp->bob_z + pp->pbob_amt, interpfrac); } // recoil only when not in camera tangles.Pitch -= interpolatedvalue(pp->recoil_ohorizoff, pp->recoil_horizoff, interpfrac); } if (automapMode != am_full) { // Cameras must be done before the main loop. JS_CameraParms(pp, tpos); } if (!sceneonly) UpdatePanel(interpfrac); UpdateWallPortalState(); render_drawrooms(pp->actor, tpos, tsect, tangles, interpfrac); RestorePortalState(); if (sceneonly) { PostDraw(); return; } MarkSectorSeen(pp->cursector); if ((automapMode != am_off) && pp == Player+myconnectindex) { SWSpriteIterator it; while (auto actor = it.Next()) { // Don't show sprites tagged with 257 if (actor->spr.lotag == 257) { if (actor->spr.cstat & (CSTAT_SPRITE_ALIGNMENT_FLOOR)) { actor->spr.cstat &= ~(CSTAT_SPRITE_ALIGNMENT_FLOOR); actor->spr.intowner = -2; } } } DrawOverheadMap(tpos.XY(), tangles.Yaw, interpfrac); } SWSpriteIterator it; while (auto actor = it.Next()) { // Don't show sprites tagged with 257 if (actor->spr.lotag == 257 && actor->spr.intowner == -2) { actor->spr.cstat |= (CSTAT_SPRITE_ALIGNMENT_FLOOR); actor->spr.intowner = -1; } } UpdateStatusBar(); DrawCrosshair(pp, interpfrac); // Do the underwater breathing bar DoPlayerDiveMeter(pp); // Boss Health Meter, if Boss present BossHealthMeter(); // Stick at end of drawscreen RestoreInterpolations(); if (cl_sointerpolation) so_restoreinterpolations(); if (paused && !M_Active()) { auto str = GStrings("Game Paused"); auto font = PickSmallFont(str); int w = font->StringWidth(str); DrawText(twod, font, CR_UNTRANSLATED, 160-w, 100, str, DTA_FullscreenScale, FSMode_Fit320x200, TAG_DONE); } if (!CommEnabled && (pp->Flags & PF_DEAD) && ReloadPrompt) ReloadPrompt = false; PostDraw(); } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- bool GameInterface::GenerateSavePic() { drawscreen(Player + myconnectindex, 65536, true); return true; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- bool GameInterface::DrawAutomapPlayer(const DVector2& mxy, const DVector2& cpos, const DAngle cang, const DVector2& xydim, const double czoom, double const interpfrac) { static int pspr_ndx[8] = { 0,0,0,0,0,0,0,0 }; bool sprisplayer = false; // Pre-caculate incoming angle vector. auto cangvect = cang.ToVector(); // Draw sprites if (gFullMap) { for (unsigned i = 0; i < sector.Size(); i++) { SWSectIterator it(i); while (auto actor = it.Next()) { if (actor->spr.cstat2 & CSTAT2_SPRITE_MAPPED) { // 1=white / 31=black / 44=green / 56=pink / 128=yellow / 210=blue / 248=orange / 255=purple PalEntry col = (actor->spr.cstat & CSTAT_SPRITE_BLOCK) > 0 ? GPalette.BaseColors[248] : actor == Player[screenpeek].actor ? GPalette.BaseColors[31] : GPalette.BaseColors[56]; auto statnum = actor->spr.statnum; auto sprxy = ((statnum >= 1) && (statnum <= 8) && (statnum != 2) ? actor->interpolatedpos(interpfrac) : actor->spr.pos).XY() - cpos; switch (actor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK) { case CSTAT_SPRITE_ALIGNMENT_FACING: // Regular sprite DrawAutomapAlignmentFacing(actor->spr, sprxy, cangvect, czoom, xydim, col); break; case CSTAT_SPRITE_ALIGNMENT_WALL: // Rotated sprite DrawAutomapAlignmentWall(actor->spr, sprxy, cangvect, czoom, xydim, col); break; case CSTAT_SPRITE_ALIGNMENT_FLOOR: // Floor sprite if (automapMode == am_overlay) DrawAutomapAlignmentFloor(actor->spr, sprxy, cangvect, czoom, xydim, col); break; } } } } } for (int p = connecthead; p >= 0; p = connectpoint2[p]) { if (p == screenpeek) { auto actor = Player[p].actor; if (actor->vel.X > 1) pspr_ndx[myconnectindex] = ((PlayClock >> 4) & 3); sprisplayer = true; if (czoom > 0.1875) { // Special case tiles if (actor->spr.picnum == 3123) break; int spnum = -1; if (sprisplayer) { if (gNet.MultiGameType != MULTI_GAME_COMMBAT || actor == Player[screenpeek].actor) spnum = 1196 + pspr_ndx[myconnectindex]; } else spnum = actor->spr.picnum; if (spnum >= 0) { const auto daang = -((!SyncInput() ? actor->spr.Angles.Yaw : actor->interpolatedyaw(interpfrac)) - cang).Normalized360().Degrees(); auto vect = OutAutomapVector(mxy - cpos, cangvect, czoom, xydim); // This repeat scale is correct. double sc = czoom * actor->spr.scale.Y * 2; DrawTexture(twod, tileGetTexture(1196 + pspr_ndx[myconnectindex], true), vect.X, vect.Y, DTA_ScaleX, sc, DTA_ScaleY, sc, DTA_Rotate, daang, DTA_CenterOffsetRel, 2, DTA_TranslationIndex, TRANSLATION(Translation_Remap, actor->spr.pal), DTA_Color, shadeToLight(actor->spr.shade), DTA_Alpha, (actor->spr.cstat & CSTAT_SPRITE_TRANSLUCENT) ? 0.33 : 1., TAG_DONE); } } } } return true; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void GameInterface::processSprites(tspriteArray& tsprites, const DVector3& view, DAngle viewang, double interpfrac) { analyzesprites(tsprites, view, interpfrac); post_analyzesprites(tsprites); } END_SW_NS