//------------------------------------------------------------------------- /* Copyright (C) 1996, 2003 - 3D Realms Entertainment This file is part of Duke Nukem 3D version 1.5 - Atomic Edition Duke Nukem 3D 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Original Source: 1996 - Todd Replogle Prepared for public release: 03/21/2003 - Charlie Wiederhold, 3D Realms Modifications for JonoF's port by Jonathon Fowler (jf@jonof.id.au) */ //------------------------------------------------------------------------- #include "ns.h" #include "duke3d.h" #include "build.h" #include "v_video.h" #include "prediction.h" BEGIN_DUKE_NS void DrawBorder(); //--------------------------------------------------------------------------- // // Floor Over Floor // If standing in sector with SE42 // then draw viewing to SE41 and raise all =hi SE43 cielings. // If standing in sector with SE43 // then draw viewing to SE40 and lower all =hi SE42 floors. // If standing in sector with SE44 // then draw viewing to SE40. // If standing in sector with SE45 // then draw viewing to SE41. // //--------------------------------------------------------------------------- static int tempsectorz[MAXSECTORS]; static int tempsectorpicnum[MAXSECTORS]; //short tempcursectnum; void SE40_Draw(int tag, int spnum, int x, int y, int z, binangle a, fixedhoriz h, int smoothratio) { int i, j = 0, k = 0; int floor1, floor2 = 0, ok = 0, fofmode = 0; int offx, offy; if (sprite[spnum].ang != 512) return; i = FOF; //Effect TILE tileDelete(FOF); if (!(gotpic[i >> 3] & (1 << (i & 7)))) return; gotpic[i >> 3] &= ~(1 << (i & 7)); floor1 = spnum; if (sprite[spnum].lotag == tag + 2) fofmode = tag + 0; if (sprite[spnum].lotag == tag + 3) fofmode = tag + 1; if (sprite[spnum].lotag == tag + 4) fofmode = tag + 0; if (sprite[spnum].lotag == tag + 5) fofmode = tag + 1; ok++; for (j = 0; j < MAXSPRITES; j++) { if ( sprite[j].picnum == 1 && sprite[j].lotag == fofmode && sprite[j].hitag == sprite[floor1].hitag ) { floor1 = j; fofmode = sprite[j].lotag; ok++; break; } } // if(ok==1) { Message("no floor1",RED); return; } if (fofmode == tag + 0) k = tag + 1; else k = tag + 0; for (j = 0; j < MAXSPRITES; j++) { if ( sprite[j].picnum == 1 && sprite[j].lotag == k && sprite[j].hitag == sprite[floor1].hitag ) { floor2 = j; ok++; break; } } // if(ok==2) { Message("no floor2",RED); return; } for (j = 0; j < MAXSPRITES; j++) // raise ceiling or floor { if (sprite[j].picnum == 1 && sprite[j].lotag == k + 2 && sprite[j].hitag == sprite[floor1].hitag ) { if (k == tag + 0) { tempsectorz[sprite[j].sectnum] = sector[sprite[j].sectnum].floorz; sector[sprite[j].sectnum].floorz += (((z - sector[sprite[j].sectnum].floorz) / 32768) + 1) * 32768; tempsectorpicnum[sprite[j].sectnum] = sector[sprite[j].sectnum].floorpicnum; sector[sprite[j].sectnum].floorpicnum = 13; } if (k == tag + 1) { tempsectorz[sprite[j].sectnum] = sector[sprite[j].sectnum].ceilingz; sector[sprite[j].sectnum].ceilingz += (((z - sector[sprite[j].sectnum].ceilingz) / 32768) - 1) * 32768; tempsectorpicnum[sprite[j].sectnum] = sector[sprite[j].sectnum].ceilingpicnum; sector[sprite[j].sectnum].ceilingpicnum = 13; } } } i = floor1; offx = x - sprite[i].x; offy = y - sprite[i].y; i = floor2; renderDrawRoomsQ16(sprite[i].x + offx, sprite[i].y + offy, z, a.asq16(), h.asq16(), sprite[i].sectnum); fi.animatesprites(offx + sprite[i].x, offy + sprite[i].y, a.asbuild(), smoothratio); renderDrawMasks(); for (j = 0; j < MAXSPRITES; j++) // restore ceiling or floor { if (sprite[j].picnum == 1 && sprite[j].lotag == k + 2 && sprite[j].hitag == sprite[floor1].hitag ) { if (k == tag + 0) { sector[sprite[j].sectnum].floorz = tempsectorz[sprite[j].sectnum]; sector[sprite[j].sectnum].floorpicnum = tempsectorpicnum[sprite[j].sectnum]; } if (k == tag + 1) { sector[sprite[j].sectnum].ceilingz = tempsectorz[sprite[j].sectnum]; sector[sprite[j].sectnum].ceilingpicnum = tempsectorpicnum[sprite[j].sectnum]; } }// end if }// end for } // end SE40 //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void se40code(int x, int y, int z, binangle a, fixedhoriz h, int smoothratio) { int i, tag; if (!isRR()) tag = 40; else if (isRRRA()) tag = 150; else return; i = headspritestat[STAT_RAROR]; while (i >= 0) { switch (sprite[i].lotag - tag + 40) { // case 40: // case 41: // SE40_Draw(i,x,y,a,smoothratio); // break; case 42: case 43: case 44: case 45: if (ps[screenpeek].cursectnum == sprite[i].sectnum) SE40_Draw(tag, i, x, y, z, a, h, smoothratio); break; } i = nextspritestat[i]; } } //--------------------------------------------------------------------------- // // split out so it can also be applied to camera views // //--------------------------------------------------------------------------- void renderMirror(int cposx, int cposy, int cposz, binangle cang, fixedhoriz choriz, int smoothratio) { if ((gotpic[TILE_MIRROR >> 3] & (1 << (TILE_MIRROR & 7))) > 0) { int dst = 0x7fffffff, i = 0; for (int k = 0; k < mirrorcnt; k++) { int j = abs(wall[mirrorwall[k]].x - cposx) + abs(wall[mirrorwall[k]].y - cposy); if (j < dst) dst = j, i = k; } if (wall[mirrorwall[i]].overpicnum == TILE_MIRROR) { int tposx, tposy; fix16_t tang; renderPrepareMirror(cposx, cposy, cposz, cang.asq16(), choriz.asq16(), mirrorwall[i], &tposx, &tposy, &tang); int j = g_visibility; g_visibility = (j >> 1) + (j >> 2); renderDrawRoomsQ16(tposx, tposy, cposz, tang, choriz.asq16(), mirrorsector[i] + MAXSECTORS); display_mirror = 1; fi.animatesprites(tposx, tposy, tang, smoothratio); display_mirror = 0; renderDrawMasks(); renderCompleteMirror(); //Reverse screen x-wise in this function g_visibility = j; } gotpic[TILE_MIRROR >> 3] &= ~(1 << (TILE_MIRROR & 7)); } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void animatecamsprite(int smoothRatio) { const int VIEWSCREEN_ACTIVE_DISTANCE = 8192; if (camsprite < 0) return; int spriteNum = camsprite; auto p = &ps[screenpeek]; auto sp = &sprite[spriteNum]; if (p->newowner >= 0) sp->owner = p->newowner; if (sp->owner >= 0 && dist(&sprite[p->i], sp) < VIEWSCREEN_ACTIVE_DISTANCE) { auto tex = tileGetTexture(sp->picnum); TileFiles.MakeCanvas(TILE_VIEWSCR, tex->GetDisplayWidth(), tex->GetDisplayHeight()); auto canvas = renderSetTarget(TILE_VIEWSCR); if (!canvas) return; screen->RenderTextureView(canvas, [=](IntRect& rect) { // fixme: This needs to interpolate the camera's angle. Position is not relevant because cameras do not move. auto camera = &sprite[sp->owner]; // Note: no ROR or camera here for now - the current setup has no means to detect these things before rendering the scene itself. drawrooms(camera->x, camera->y, camera->z, camera->ang, 100 + camera->shade, camera->sectnum); // why 'shade'...? display_mirror = 1; // should really be 'display external view'. fi.animatesprites(camera->x, camera->y, camera->ang, smoothRatio); display_mirror = 0; renderDrawMasks(); }); renderRestoreTarget(); } } //--------------------------------------------------------------------------- // // RRRA's drug distortion effect // //--------------------------------------------------------------------------- void setdrugmode(player_struct *p, int oyrepeat) { if (playrunning()) { if (p->DrugMode > 0) { int var_8c; if (p->drug_stat[0] == 0) { p->drug_stat[1]++; var_8c = oyrepeat + p->drug_stat[1] * 5000; if (oyrepeat * 3 < var_8c) { renderSetAspect(oyrepeat * 3, yxaspect); p->drug_aspect = oyrepeat * 3; p->drug_stat[0] = 2; } else { renderSetAspect(var_8c, yxaspect); p->drug_aspect = var_8c; } setpal(p); } else if (p->drug_stat[0] == 3) { p->drug_stat[1]--; var_8c = oyrepeat + p->drug_stat[1] * 5000; if (var_8c < oyrepeat) { renderSetAspect(oyrepeat, yxaspect); p->DrugMode = 0; p->drug_stat[0] = 0; p->drug_stat[2] = 0; p->drug_stat[1] = 0; } else { renderSetAspect(var_8c, yxaspect); p->drug_aspect = var_8c; } setpal(p); } else if (p->drug_stat[0] == 2) { if (p->drug_stat[2] > 30) { p->drug_stat[0] = 1; } else { p->drug_stat[2]++; renderSetAspect(p->drug_stat[2] * 500 + oyrepeat * 3, yxaspect); p->drug_aspect = oyrepeat * 3 + p->drug_stat[2] * 500; setpal(p); } } else { if (p->drug_stat[2] < 1) { p->drug_stat[0] = 2; p->DrugMode--; if (p->DrugMode == 1) p->drug_stat[0] = 3; } else { p->drug_stat[2]--; renderSetAspect(p->drug_stat[2] * 500 + oyrepeat * 3, yxaspect); p->drug_aspect = oyrepeat * 3 + p->drug_stat[2] * 500; setpal(p); } } } } else if (p->DrugMode > 0) { renderSetAspect(p->drug_aspect, yxaspect); setpal(p); } } //--------------------------------------------------------------------------- // // used by RR to inject some external geometry into a scene. // //--------------------------------------------------------------------------- static void geometryEffect(int cposx, int cposy, int cposz, binangle cang, fixedhoriz choriz, int sect, int smoothratio) { short gs, tgsect, nextspr, geosect, geoid = 0; int spr; renderDrawRoomsQ16(cposx, cposy, cposz, cang.asq16(), choriz.asq16(), sect); fi.animatesprites(cposx, cposy, cang.asbuild(), smoothratio); renderDrawMasks(); for (gs = 0; gs < geocnt; gs++) { tgsect = geosector[gs]; spr = headspritesect[tgsect]; while (spr != -1) { nextspr = nextspritesect[spr]; changespritesect((short)spr, geosectorwarp[gs]); setsprite((short)spr, sprite[spr].x -= geox[gs], sprite[spr].y -= geoy[gs], sprite[spr].z); spr = nextspr; } if (geosector[gs] == sect) { geosect = geosectorwarp[gs]; geoid = gs; } } cposx -= geox[geoid]; cposy -= geoy[geoid]; renderDrawRoomsQ16(cposx, cposy, cposz, cang.asq16(), choriz.asq16(), sect); cposx += geox[geoid]; cposy += geoy[geoid]; for (gs = 0; gs < geocnt; gs++) { tgsect = geosectorwarp[gs]; spr = headspritesect[tgsect]; while (spr != -1) { nextspr = nextspritesect[spr]; changespritesect((short)spr, geosector[gs]); setsprite((short)spr, sprite[spr].x += geox[gs], sprite[spr].y += geoy[gs], sprite[spr].z); spr = nextspr; } } fi.animatesprites(cposx, cposy, cang.asbuild(), smoothratio); renderDrawMasks(); for (gs = 0; gs < geocnt; gs++) { tgsect = geosector[gs]; spr = headspritesect[tgsect]; while (spr != -1) { nextspr = nextspritesect[spr]; changespritesect((short)spr, geosectorwarp2[gs]); setsprite((short)spr, sprite[spr].x -= geox2[gs], sprite[spr].y -= geoy2[gs], sprite[spr].z); spr = nextspr; } if (geosector[gs] == sect) { geosect = geosectorwarp2[gs]; geoid = gs; } } cposx -= geox2[geoid]; cposy -= geoy2[geoid]; renderDrawRoomsQ16(cposx, cposy, cposz, cang.asq16(), choriz.asq16(), sect); cposx += geox2[geoid]; cposy += geoy2[geoid]; for (gs = 0; gs < geocnt; gs++) { tgsect = geosectorwarp2[gs]; spr = headspritesect[tgsect]; while (spr != -1) { nextspr = nextspritesect[spr]; changespritesect((short)spr, geosector[gs]); setsprite((short)spr, sprite[spr].x += geox2[gs], sprite[spr].y += geoy2[gs], sprite[spr].z); spr = nextspr; } } fi.animatesprites(cposx, cposy, cang.asbuild(), smoothratio); renderDrawMasks(); } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void displayrooms(int snum, int smoothratio) { int cposx, cposy, cposz, fz, cz; short sect; binangle cang; fixedhoriz choriz; struct player_struct* p; int tiltcs = 0; // JBF 20030807 p = &ps[snum]; DrawBorder(); if (ud.overhead_on == 2 || p->cursectnum == -1) return; // Do not light up the fog in RRRA's E2L1. Ideally this should apply to all foggy levels but all others use lookup table hacks for their fog. if (isRRRA() && fogactive) { p->visibility = ud.const_visibility; } g_visibility = p->visibility; newaspect_enable = 1; videoSetCorrectedAspect(); smoothratio = min(max(smoothratio, 0), 65536); if (!playrunning() || ps[snum].on_crane > -1) smoothratio = 65536; sect = p->cursectnum; if (sect < 0 || sect >= MAXSECTORS) return; GLInterface.SetMapFog(fogactive != 0); dointerpolations(smoothratio); setgamepalette(BASEPAL); animatecamsprite(smoothratio); // The camera texture must be rendered with the base palette, so this is the only place where the current global palette can be set. // The setting here will be carried over to the rendering of the weapon sprites, but other 2D content will always default to the main palette. setgamepalette(p->palette); if (ud.camerasprite >= 0) { spritetype* s; s = &sprite[ud.camerasprite]; if (s->yvel < 0) s->yvel = -100; else if (s->yvel > 199) s->yvel = 300; cang = buildang(hittype[ud.camerasprite].tempang + mulscale16((int)(((s->ang + 1024 - hittype[ud.camerasprite].tempang) & 2047) - 1024), smoothratio)); auto bh = buildhoriz(s->yvel); se40code(s->x, s->y, s->z, cang, bh, smoothratio); renderMirror(s->x, s->y, s->z, cang, bh, smoothratio); renderDrawRoomsQ16(s->x, s->y, s->z - (4 << 8), cang.asq16(), bh.asq16(), s->sectnum); fi.animatesprites(s->x, s->y, cang.asbuild(), smoothratio); renderDrawMasks(); } else { int i = divscale22(1, isRR() ? 64 : sprite[p->i].yrepeat + 28); fixed_t dang = 1024 << FRACBITS; if (!isRRRA() || !p->DrugMode) { // Fixme: This should get the aspect ratio from the backend, not the current viewport size. int viewingRange = xs_CRoundToInt(double(i) * tan(r_fov * (pi::pi() / 360.))); renderSetAspect(mulscale16(viewingRange, viewingrange), yxaspect); } else { setdrugmode(p, i); } if (!cl_syncinput) renderSetRollAngle(p->q16rotscrnang / (float)(FRACUNIT)); else renderSetRollAngle((p->oq16rotscrnang + mulscale16(((p->q16rotscrnang - p->oq16rotscrnang + dang) & 0x7FFFFFF) - dang, smoothratio)) / (float)(FRACUNIT)); if ((snum == myconnectindex) && (numplayers > 1)) { cposx = omyx + mulscale16((int)(myx - omyx), smoothratio); cposy = omyy + mulscale16((int)(myy - omyy), smoothratio); cposz = omyz + mulscale16((int)(myz - omyz), smoothratio); if (cl_syncinput) { fixed_t ohorz = (oq16myhoriz + oq16myhorizoff); fixed_t horz = (q16myhoriz + q16myhorizoff); choriz = q16horiz(ohorz + mulscale16(horz - ohorz, smoothratio)); cang = q16ang(oq16myang + mulscale16(((q16myang + dang - oq16myang) & 0x7FFFFFF) - dang, smoothratio)); } else { cang = q16ang(q16myang); choriz = q16horiz(q16myhoriz + q16myhorizoff); } sect = mycursectnum; } else { cposx = p->oposx + mulscale16((int)(p->posx - p->oposx), smoothratio); cposy = p->oposy + mulscale16((int)(p->posy - p->oposy), smoothratio); cposz = p->oposz + mulscale16((int)(p->posz - p->oposz), smoothratio); if (cl_syncinput) { // Original code for when the values are passed through the sync struct fixed_t ohorz = (p->oq16horiz + p->oq16horizoff); fixed_t horz = (p->q16horiz + p->q16horizoff); choriz = q16horiz(ohorz + mulscale16(horz - ohorz, smoothratio)); fixed_t oang = (p->oq16ang + p->oq16look_ang); fixed_t ang = (p->q16ang + p->q16look_ang); cang = q16ang(oang + mulscale16(((ang + dang - oang) & 0x7FFFFFF) - dang, smoothratio)); } else { // This is for real time updating of the view direction. cang = q16ang(p->q16ang + p->q16look_ang); choriz = q16horiz(p->q16horiz + p->q16horizoff); } } if (p->newowner >= 0) { fixed_t oang = hittype[p->newowner].oq16ang; cang = q16ang(oang + mulscale16(((p->q16ang + dang - oang) & 0x7FFFFFF) - dang, smoothratio)); choriz = q16horiz(p->q16horiz + p->q16horizoff); cposx = p->posx; cposy = p->posy; cposz = p->posz; sect = sprite[p->newowner].sectnum; smoothratio = 65536L; } else if (p->over_shoulder_on == 0) { if (cl_viewbob) cposz += p->opyoff + mulscale16((int)(p->pyoff - p->opyoff), smoothratio); } else view(p, &cposx, &cposy, &cposz, §, cang.asbuild(), choriz.asbuild()); cz = hittype[p->i].ceilingz; fz = hittype[p->i].floorz; if (earthquaketime > 0 && p->on_ground == 1) { cposz += 256 - (((earthquaketime) & 1) << 9); cang += buildang((2 - ((earthquaketime) & 2)) << 2); } if (sprite[p->i].pal == 1) cposz -= (18 << 8); if (p->newowner >= 0) choriz = buildhoriz(100 + sprite[p->newowner].shade); else if (p->spritebridge == 0) { if (cposz < (p->truecz + (4 << 8))) cposz = cz + (4 << 8); else if (cposz > (p->truefz - (4 << 8))) cposz = fz - (4 << 8); } if (sect >= 0) { getzsofslope(sect, cposx, cposy, &cz, &fz); if (cposz < cz + (4 << 8)) cposz = cz + (4 << 8); if (cposz > fz - (4 << 8)) cposz = fz - (4 << 8); } choriz = clamp(choriz, buildhoriz(HORIZ_MIN), buildhoriz(HORIZ_MAX)); if (isRR() && sector[sect].lotag == 848) { geometryEffect(cposx, cposy, cposz, cang, choriz, sect, smoothratio); } else { se40code(cposx, cposy, cposz, cang, choriz, smoothratio); renderMirror(cposx, cposy, cposz, cang, choriz, smoothratio); renderDrawRoomsQ16(cposx, cposy, cposz, cang.asq16(), choriz.asq16(), sect); fi.animatesprites(cposx, cposy, cang.asbuild(), smoothratio); renderDrawMasks(); } } GLInterface.SetMapFog(false); restoreinterpolations(); if (!isRRRA() || !fogactive) { if (totalclock < lastvisinc) { if (abs(p->visibility - ud.const_visibility) > 8) p->visibility += (ud.const_visibility - p->visibility) >> 2; } else p->visibility = ud.const_visibility; } } bool GameInterface::GenerateSavePic() { displayrooms(myconnectindex, 65536); return true; } END_DUKE_NS