//------------------------------------------------------------------------- /* Copyright (C) 2010 EDuke32 developers and contributors This file is part of EDuke32. EDuke32 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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. */ //------------------------------------------------------------------------- #include "baselayer.h" #include "renderlayer.h" #include "duke3d.h" #include "animlib.h" #include "mouse.h" #include "compat.h" #include "input.h" #include "anim.h" #ifdef USE_LIBVPX # include "animvpx.h" #endif // animsound_t.sound EDUKE32_STATIC_ASSERT(INT16_MAX >= MAXSOUNDS); hashtable_t h_dukeanim = { 8, NULL }; dukeanim_t * g_animPtr; dukeanim_t *Anim_Find(const char *s) { intptr_t ptr = hash_findcase(&h_dukeanim, s); if (ptr == -1) { int const siz = Bstrlen(s) + 5; char * const str = (char *)Xcalloc(1, siz); maybe_append_ext(str, siz, s, ".anm"); ptr = hash_findcase(&h_dukeanim, str); if (ptr == -1) { maybe_append_ext(str, siz, s, ".ivf"); ptr = hash_findcase(&h_dukeanim, str); } Bfree(str); } return (dukeanim_t *)(ptr == -1 ? NULL : (dukeanim_t *)ptr); } dukeanim_t * Anim_Create(char const * fn) { dukeanim_t * anim = (dukeanim_t *)Xcalloc(1, sizeof(dukeanim_t)); hash_add(&h_dukeanim, fn, (intptr_t)anim, 0); return anim; } #ifndef EDUKE32_STANDALONE #ifdef DYNSOUNDREMAP_ENABLE static int32_t const StopAllSounds = -1; #else # define StopAllSounds -1 #endif #endif void Anim_Init(void) { hash_init(&h_dukeanim); struct defaultanmsound { #ifdef DYNSOUNDREMAP_ENABLE int32_t const & sound; #else int16_t sound; #endif uint8_t frame; }; static defaultanmsound const logo[] = { { FLY_BY, 1 }, { PIPEBOMB_EXPLODE, 19 }, }; #ifndef EDUKE32_STANDALONE static defaultanmsound const cineov2[] = { { WIND_AMBIENCE, 1 }, { ENDSEQVOL2SND1, 26 }, { ENDSEQVOL2SND2, 36 }, { THUD, 54 }, { ENDSEQVOL2SND3, 62 }, { ENDSEQVOL2SND4, 75 }, { ENDSEQVOL2SND5, 81 }, { ENDSEQVOL2SND6, 115 }, { ENDSEQVOL2SND7, 124 }, }; static defaultanmsound const cineov3[] = { { WIND_REPEAT, 1 }, { DUKE_GRUNT, 98 }, { THUD, 82+20 }, { SQUISHED, 82+20 }, { ENDSEQVOL3SND3, 104+20 }, { ENDSEQVOL3SND2, 114+20 }, { PIPEBOMB_EXPLODE, 158 }, }; static defaultanmsound const vol42a[] = { { INTRO4_B, 1 }, { SHORT_CIRCUIT, 12 }, { INTRO4_5, 18 }, { SHORT_CIRCUIT, 34 }, }; static defaultanmsound const vol41a[] = { { INTRO4_1, 1 }, { INTRO4_3, 7 }, { INTRO4_2, 12 }, { INTRO4_4, 26 }, }; static defaultanmsound const vol43a[] = { { INTRO4_6, 10 }, }; static defaultanmsound const vol4e1[] = { { DUKE_UNDERWATER, 3 }, { VOL4ENDSND1, 35 }, }; static defaultanmsound const vol4e2[] = { { DUKE_UNDERWATER, 11 }, { VOL4ENDSND1, 20 }, { VOL4ENDSND2, 39 }, { StopAllSounds, 50 }, }; static defaultanmsound const vol4e3[] = { { BOSS4_DEADSPEECH, 1 }, { VOL4ENDSND1, 40 }, { DUKE_UNDERWATER, 40 }, { BIGBANG, 50 }, }; #endif struct defaultanm { char const *fn; defaultanmsound const *sounds; uint8_t numsounds; uint8_t fdelay; }; #define anmsnd(x) (x), ARRAY_SIZE(x) static defaultanm const anms[] = { { "logo.anm", anmsnd(logo), 9 }, { "3dr.anm", NULL, 0, 10 }, #ifndef EDUKE32_STANDALONE { "vol4e1.anm", anmsnd(vol4e1), 10 }, { "vol4e2.anm", anmsnd(vol4e2), 14 }, { "vol4e3.anm", anmsnd(vol4e3), 10 }, { "vol41a.anm", anmsnd(vol41a), 14 }, { "vol42a.anm", anmsnd(vol42a), 18 }, { "vol43a.anm", anmsnd(vol43a), 10 }, { "duketeam.anm", NULL, 0, 10 }, { "radlogo.anm", NULL, 0, 10 }, { "cineov2.anm", anmsnd(cineov2), 18 }, { "cineov3.anm", anmsnd(cineov3), 10 }, #endif }; #undef anmsnd for (defaultanm const & anm : anms) { dukeanim_t * anim = Anim_Create(anm.fn); anim->framedelay = anm.fdelay; if (anm.numsounds) { anim->sounds = (animsound_t *)Xmalloc(anm.numsounds * sizeof(animsound_t)); size_t const numsounds = anm.numsounds; for (size_t i = 0; i < numsounds; ++i) { defaultanmsound const & src = anm.sounds[i]; animsound_t & dst = anim->sounds[i]; dst.sound = src.sound; dst.frame = src.frame; } anim->numsounds = numsounds; } } } int32_t Anim_Play(const char *fn) { dukeanim_t *anim = Anim_Find(fn); if (!anim) { OSD_Printf("Animation %s is undefined!\n", fn); return 0; } uint16_t soundidx = 0; // custom anim sounds int32_t running = 1, i; I_ClearAllInput(); #ifdef USE_LIBVPX uint16_t framenum = 0; while (getrendermode() >= REND_POLYMOST) // if, really { char const * dot = Bstrrchr(fn, '.'); if (!dot) break; dukeanim_t const * origanim = anim; int32_t handle = -1; if (!Bstrcmp(dot, ".ivf")) { handle = kopen4loadfrommod(fn, 0); if (handle == -1) break; } else { char vpxfn[BMAX_PATH]; Bstrncpyz(vpxfn, fn, BMAX_PATH); ptrdiff_t dotpos = dot - fn; if (dotpos + 4 >= BMAX_PATH) break; char *vpxfndot = vpxfn + dotpos; vpxfndot[1] = 'i'; vpxfndot[2] = 'v'; vpxfndot[3] = 'f'; vpxfndot[4] = '\0'; handle = kopen4loadfrommod(vpxfn, 0); if (handle == -1) break; anim = Anim_Find(vpxfn); } animvpx_ivf_header_t info; i = animvpx_read_ivf_header(handle, &info); if (i) { OSD_Printf("Failed reading IVF file: %s\n", animvpx_read_ivf_header_errmsg[i]); kclose(handle); return 0; } animvpx_setup_glstate(); animvpx_codec_ctx codec; if (animvpx_init_codec(&info, handle, &codec)) { OSD_Printf("Error initializing VPX codec.\n"); animvpx_restore_glstate(); return 0; } uint32_t const convnumer = 120 * info.fpsdenom; uint32_t const convdenom = info.fpsnumer * origanim->framedelay; uint32_t const msecsperframe = scale(info.fpsdenom, 1000, info.fpsnumer); uint32_t nextframetime = getticks(); uint8_t *pic; // OSD_Printf("msecs per frame: %d\n", msecsperframe); do { nextframetime += msecsperframe; i = animvpx_nextpic(&codec, &pic); if (i) { OSD_Printf("Failed getting next pic: %s\n", animvpx_nextpic_errmsg[i]); if (codec.errmsg) { OSD_Printf(" %s\n", codec.errmsg); if (codec.errmsg_detail) OSD_Printf(" detail: %s\n", codec.errmsg_detail); } break; } if (!pic) break; // no more pics! VM_OnEventWithReturn(EVENT_PRECUTSCENE, -1, myconnectindex, framenum); animvpx_render_frame(&codec); VM_OnEventWithReturn(EVENT_CUTSCENE, -1, myconnectindex, framenum); // after rendering the frame but before displaying: maybe play sound... framenum++; if (anim) { while (soundidx < anim->numsounds && anim->sounds[soundidx].frame <= framenum) { int16_t sound = anim->sounds[soundidx].sound; if (sound == -1) FX_StopAllSounds(); else S_PlaySound(sound); soundidx++; } } else { uint16_t convframenum = scale(framenum, convnumer, convdenom); while (soundidx < origanim->numsounds && origanim->sounds[soundidx].frame <= convframenum) { int16_t sound = origanim->sounds[soundidx].sound; if (sound == -1) FX_StopAllSounds(); else S_PlaySound(sound); soundidx++; } } // this and showframe() instead of nextpage() are so that // nobody tramples on our carefully set up GL state! palfadedelta = 0; showframe(0); // I_ClearAllInput(); do { G_HandleAsync(); if (I_CheckAllInput()) { running = 0; break; } } while (getticks() < nextframetime); } while (running); animvpx_print_stats(&codec); // kclose(handle); animvpx_restore_glstate(); animvpx_uninit_codec(&codec); I_ClearAllInput(); return !running; // done with playing VP8! } #endif // ANM playback --- v v v --- #ifdef USE_OPENGL int32_t ogltexfiltermode = gltexfiltermode; #endif int32_t handle = kopen4load(fn, 0); if (handle == -1) return 0; int32_t length = kfilelength(handle); if (length <= 4) { OSD_Printf("Warning: skipping playback of empty ANM file \"%s\".\n", fn); goto end_anim; } walock[TILE_ANIM] = 219; anim->animlock = 1; if (!anim->animbuf) allocache((intptr_t *)&anim->animbuf, length + 1, &anim->animlock); tilesiz[TILE_ANIM].x = 200; tilesiz[TILE_ANIM].y = 320; kread(handle, anim->animbuf, length); kclose(handle); uint32_t firstfour; Bmemcpy(&firstfour, anim->animbuf, 4); // "DKIF" (.ivf) if (firstfour == B_LITTLE32(0x46494B44)) goto end_anim; int32_t numframes; // "LPF " (.anm) if (firstfour != B_LITTLE32(0x2046504C) || ANIM_LoadAnim(anim->animbuf, length) < 0 || (numframes = ANIM_NumFrames()) <= 0) { // XXX: ANM_LoadAnim() still checks less than the bare minimum, // e.g. ANM file could still be too small and not contain any frames. OSD_Printf("Error: malformed ANM file \"%s\".\n", fn); goto end_anim; } setbasepal(ANIMPAL, ANIM_GetPalette()); // setpalette(0L,256L,tempbuf); // setbrightness(ud.brightness>>2,tempbuf,2); P_SetGamePalette(g_player[myconnectindex].ps, ANIMPAL, 8 + 2); #ifdef USE_OPENGL gltexfiltermode = 0; gltexapplyprops(); #endif ototalclock = totalclock; i = 1; int32_t frametime; frametime = 0; do { if (i > 4 && totalclock > frametime + 60) { OSD_Printf("WARNING: slowdown in %s, skipping playback\n", fn); goto end_anim_restore_gl; } G_HandleAsync(); if (totalclock < ototalclock - 1) continue; i = VM_OnEventWithReturn(EVENT_PRECUTSCENE, -1, myconnectindex, i); waloff[TILE_ANIM] = (intptr_t)ANIM_DrawFrame(i); invalidatetile(TILE_ANIM, 0, 1 << 4); // JBF 20031228 if (I_CheckAllInput()) { running = 0; goto end_anim_restore_gl; } if (g_restorePalette == 1) { P_SetGamePalette(g_player[myconnectindex].ps, ANIMPAL, 0); g_restorePalette = 0; } frametime = totalclock; clearallviews(0); rotatesprite_fs(0 << 16, 0 << 16, 65536L, 512, TILE_ANIM, 0, 0, 2 + 4 + 8 + 16 + 64 + BGSTRETCH); g_animPtr = anim; i = VM_OnEventWithReturn(EVENT_CUTSCENE, -1, myconnectindex, i); g_animPtr = NULL; nextpage(); I_ClearAllInput(); ototalclock += anim->framedelay; while (soundidx < anim->numsounds && anim->sounds[soundidx].frame <= (uint16_t)i) { int16_t sound = anim->sounds[soundidx].sound; if (sound == -1) FX_StopAllSounds(); else S_PlaySound(sound); soundidx++; } ++i; } while (i < numframes); end_anim_restore_gl: #ifdef USE_OPENGL gltexfiltermode = ogltexfiltermode; gltexapplyprops(); #endif end_anim: I_ClearAllInput(); ANIM_FreeAnim(); walock[TILE_ANIM] = 1; anim->animlock = 0; return !running; }