raze/wadsrc/static/zscript/games/exhumed/ui/screens.zs
2021-08-14 10:54:51 +02:00

878 lines
22 KiB
Text

//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT
Copyright (C) 2020-2021 Christoph Oelckers
This file is part of Raze.
PCExhumed 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.
*/
//-------------------------------------------------------------------------
struct LMFDecoder native
{
static native bool Identify(String fn);
static native LMFDecoder Create(String fn);
native bool Frame(double clock);
native TextureID GetTexture();
native void Close();
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class LmfPlayer : SkippableScreenJob
{
LMFDecoder decoder;
double nextclock;
String fn;
ScreenJob Init(String filename)
{
fn = filename;
return self;
}
override void Start()
{
decoder = LMFDecoder.Create(fn);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
override void Draw(double smoothratio)
{
double clock = (ticks + smoothratio) * 1000000000. / GameTicRate;
if (clock >= nextclock)
{
if (decoder.Frame(clock))
{
jobstate = finished;
return;
}
}
double duration = clock * (120. / 8000000000.);
double z = 2048 * duration;
if (z > 65536) z = 65536;
double angle = 1536. + 16. * duration;
if (angle >= 2048.) angle = 0.;
Screen.DrawTexture(decoder.getTexture(), false, 160, 100, DTA_FullscreenScale, FSMode_Fit320x200,
DTA_CenterOffset, true, DTA_FlipY, true, DTA_ScaleX, z / 65536., DTA_ScaleY, z / 65536., DTA_Rotate, (-angle - 512) * (360. / 2048.));
}
override void OnDestroy()
{
decoder.Close();
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class LobotomyScreen : ImageScreen
{
ScreenJob Init(String texname, int fade)
{
Super.InitNamed(texname, fade);
return self;
}
override void OnSkip()
{
Exhumed.StopLocalSound();
}
override void Start()
{
Exhumed.PlayLocalSound(ExhumedSnd.kSoundJonLaugh2, 7000, false, CHANF_UI);
}
override void OnTick()
{
Super.OnTick();
if (jobstate == finished) Exhumed.StopLocalSound();
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class MainTitle : SkippableScreenJob
{
String a, b;
int mystate;
int duration;
int var_4;
int esi;
int nCount;
int starttime;
static const short skullDurations[] = { 6, 25, 43, 50, 68, 78, 101, 111, 134, 158, 173, 230, 600 };
ScreenJob Init()
{
Super.Init(fadein);
a = StringTable.Localize("$TXT_EX_COPYRIGHT1");
b = StringTable.Localize("$TXT_EX_COPYRIGHT2");
duration = skullDurations[0];
esi = 130;
return self;
}
override void Start()
{
Exhumed.PlayLocalSound(59, 0, true, CHANF_UI);
Exhumed.playCDtrack(19, true);
}
override void OnTick()
{
int ticker = ticks * 120 / GameTicRate;
if (ticks > 1 && mystate == 0 && !Exhumed.LocalSoundPlaying())
{
if (random(0, 15))
Exhumed.PlayLocalSound(ExhumedSnd.kSoundJonLaugh2, 0, false, CHANF_UI);
else
Exhumed.PlayLocalSound(61, 0, false, CHANF_UI);
mystate = 1;
starttime = ticker;
}
if (mystate == 1)
{
if (ticker > duration)
{
nCount++;
if (nCount > 12)
{
jobstate = finished;
return;
}
duration = starttime + skullDurations[nCount];
var_4 = var_4 == 0;
}
}
}
override void Draw(double sr)
{
Exhumed.DrawPlasma();
Exhumed.DrawRel("SkullHead", 160, 100);
if (mystate == 0)
{
Exhumed.DrawRel("SkullJaw", 161, 130);
}
else
{
int nStringWidth = SmallFont.StringWidth(a);
Screen.DrawText(SmallFont, Font.CR_UNTRANSLATED, 160 - nStringWidth / 2, 200 - 24, a, DTA_FullscreenScale, FSMode_Fit320x200);
nStringWidth = SmallFont.StringWidth(b);
Screen.DrawText(SmallFont, Font.CR_UNTRANSLATED, 160 - nStringWidth / 2, 200 - 16, b, DTA_FullscreenScale, FSMode_Fit320x200);
String nTile = "SkullJaw";
if (var_4)
{
if (esi >= 135) nTile = "SkullJaw2";
else esi += 5;
}
else if (esi <= 130) esi = 130;
else esi -= 2;
int y;
if (nTile == "SkullJaw2")
{
y = 131;
}
else
{
y = esi;
if (y > 135) y = 135;
}
Exhumed.DrawRel(nTile, 161, y);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class MapScreen : ScreenJob
{
static const int MapLevelOffsets[] = { 0, 50, 10, 20, 0, 45, -20, 20, 5, 0, -10, 10, 30, -20, 0, 20, 0, 0, 0, 0 };
static const int MapPlaqueX[] = { 100, 230, 180, 10, 210, 10, 10, 140, 30, 200, 145, 80, 15, 220, 190, 20, 220, 20, 200, 20 };
static const int MapPlaqueY[] = { 170, 10, 125, 95, 160, 110, 50, 0, 20, 150, 170, 80, 0, 35, 40, 130, 160, 10, 10, 10 };
static const int MapPlaqueTextX[] = { 18, 18, 18, 18, 18, 18, 18, 18, 18, 20, 18, 18, 18, 18, 18, 19, 18, 18, 18, 19 };
static const int MapPlaqueTextY[] = { 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 6, 5, 6, 6, 6, 6, 6, 5, 4 };
static const int FireTilesX[] = { 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1 };
static const int FireTilesY[] = { 3, 0, 3, 0, 0, 0, 1, 1, 2, 0, 2, 0 };
static const int MapLevelFires[] = {
3, 0, 107, 95 , 1, 58, 140 , 2, 28, 38 ,
3, 2, 240, 0 , 0, 237, 32 , 1, 200, 30 ,
2, 2, 250, 57 , 0, 250, 43 , 2, 200, 70 ,
2, 1, 82, 59 , 2, 84, 16 , 0, 10, 95 ,
2, 2, 237, 50 , 1, 215, 42 , 1, 210, 50 ,
3, 0, 40, 7 , 1, 75, 6 , 2, 100, 10 ,
3, 0, 58, 61 , 1, 85, 80 , 2, 111, 63 ,
3, 0, 260, 65 , 1, 228, 0 , 2, 259, 15 ,
2, 0, 81, 38 , 2, 58, 38 , 2, 30, 20 ,
3, 0, 259, 49 , 1, 248, 76 , 2, 290, 65 ,
3, 2, 227, 66 , 0, 224, 98 , 1, 277, 30 ,
2, 0, 100, 10 , 2, 48, 76 , 2, 80, 80 ,
3, 0, 17, 2 , 1, 29, 49 , 2, 53, 28 ,
3, 0, 266, 42 , 1, 283, 99 , 2, 243, 108 ,
2, 0, 238, 19 , 2, 240, 92 , 2, 190, 40 ,
2, 0, 27, 0 , 1, 70, 40 , 0, 20, 130 ,
3, 0, 275, 65 , 1, 235, 8 , 2, 274, 6 ,
3, 0, 75, 45 , 1, 152, 105 , 2, 24, 68 ,
3, 0, 290, 25 , 1, 225, 63 , 2, 260, 110 ,
0, 1, 20, 10 , 1, 20, 10 , 1, 20, 10
};
const FIRE_SIZE = 10;
const FIRE_TYPE = 1;
const FIRE_XOFS = 2;
const FIRE_YOFS = 3;
const FIRE_ELEMENT_SIZE = 3;
int x;
int delta;
int nIdleSeconds;
int curYPos, destYPos;
int nLevel, nLevelNew, nLevelBest;
native static void SetNextLevel(int num);
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
ScreenJob Init(int oldlevel, int newlevel, int maxlevel)
{
Super.Init(fadein|fadeout);
nLevel = oldlevel - 1;
nLevelNew = newlevel - 1;
nLevelBest = min(maxlevel, 19) - 1;
curYPos = MapLevelOffsets[nLevel] + (200 * (nLevel / 2));
destYPos = MapLevelOffsets[nLevelNew] + (200 * (nLevelNew / 2));
if (curYPos < destYPos) delta = 2;
else if (curYPos > destYPos) delta = -2;
// Trim smoke in widescreen
/*
vec2_t mapwinxy1 = windowxy1, mapwinxy2 = windowxy2;
int32_t width = mapwinxy2.x - mapwinxy1.x + 1, height = mapwinxy2.y - mapwinxy1.y + 1;
if (3 * width > 4 * height)
{
mapwinxy1.x += (width - 4 * height / 3) / 2;
mapwinxy2.x -= (width - 4 * height / 3) / 2;
}
*/
return self;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
override bool OnEvent(InputEvent ev)
{
if (ev.type == InputEvent.Type_KeyDown)
{
int key = ev.KeyScan;
let binding = Bindings.GetBinding(key);
if (key == InputEvent.KEY_UPARROW || key == InputEvent.KEY_PAD_DPAD_UP || key == InputEvent.Key_kpad_8 || binding ~== "+move_forward")
{
if (curYPos == destYPos && nLevelNew <= nLevelBest)
{
nLevelNew++;
SetNextLevel(nLevelNew + 1);
destYPos = MapLevelOffsets[nLevelNew] + (200 * (nLevelNew / 2));
if (curYPos <= destYPos) delta = 2;
else delta = -2;
nIdleSeconds = 0;
}
return true;
}
if (key == InputEvent.KEY_DOWNARROW || key == InputEvent.KEY_PAD_DPAD_DOWN || key == InputEvent.Key_kpad_2 || binding ~== "+move_backward")
{
if (curYPos == destYPos && nLevelNew > 0)
{
nLevelNew--;
SetNextLevel(nLevelNew + 1);
destYPos = MapLevelOffsets[nLevelNew] + (200 * (nLevelNew / 2));
if (curYPos <= destYPos) delta = 2;
else delta = -2;
nIdleSeconds = 0;
}
return true;
}
if (!System.specialKeyEvent(ev)) jobstate = skipped;
return true;
}
return false;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
override void OnTick()
{
if (curYPos != destYPos)
{
// scroll the map every couple of ms
curYPos += delta;
if ((curYPos > destYPos && delta > 0) || (curYPos < destYPos && delta < 0))
curYPos = destYPos;
nIdleSeconds = 0;
}
else nIdleSeconds++;
if (nIdleSeconds > 300) jobstate = finished;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
override void Draw(double smoothratio)
{
int currentclock = int((ticks + smoothratio) * 120 / GameTicRate);
int tileY = curYPos;
// Draw the background screens
for (int i = 0; i < 10; i++)
{
let tex = String.Format("MapBG%02d", i+1);
Exhumed.DrawAbs(tex, x, tileY);
tileY -= 200;
}
// for each level - drawing the 'level completed' on-fire smoke markers
for (int i = 0; i < 20; i++)
{
int screenY = (i >> 1) * -200;
if (nLevelBest >= i) // check if the player has finished this level
{
for (int j = 0; j < MapLevelFires[i * FIRE_SIZE]; j++)
{
int nFireFrame = ((currentclock >> 4) & 3);
int elem = i * FIRE_SIZE + FIRE_ELEMENT_SIZE * j;
int nFireType = MapLevelFires[elem + FIRE_TYPE];
int x = MapLevelFires[elem + FIRE_XOFS];
int y = MapLevelFires[elem + FIRE_YOFS];
String nTile = String.Format("MAPFIRE_%d%d", nFireType+1, nFireFrame+1);
int smokeX = x + FireTilesX[nFireType*3 + nFireFrame];
int smokeY = y + FireTilesY[nFireType*3 + nFireFrame] + curYPos + screenY;
// Use rotatesprite to trim smoke in widescreen
Exhumed.DrawAbs(nTile, smokeX, smokeY);
// Todo: mask out the sides of the screen if the background is not widescreen.
}
}
int t = (((currentclock & 16) >> 4));
String nTile = String.Format("MapPlaque%d_%02d", t+1, i+1);
int nameX = mapPlaqueX[i];
int nameY = mapPlaqueY[i] + curYPos + screenY;
// Draw level name plaque
Exhumed.DrawAbs(nTile, nameX, nameY);
int shade = 96;
if (nLevelNew == i)
{
shade = (Raze.bsin(16 * currentclock) + 31) >> 8;
}
else if (nLevelBest >= i)
{
shade = 31;
}
int textY = nameY + MapPlaqueTextY[i];
int textX = nameX + MapPlaqueTextX[i];
nTile = String.Format("MapPlaqueText_%02d", i+1);
// draw the text, alternating between red and black
Exhumed.DrawAbs(nTile, textX, textY, shade);
}
}
}
//---------------------------------------------------------------------------
//
// cinema (this has been stripped off all game logic that was still in here)
//
//---------------------------------------------------------------------------
class Cinema : SkippableScreenJob
{
TextOverlay textov;
TextureID cinematile;
int currentCinemaPalette;
int cdtrack;
int palette;
bool done;
ScreenJob Init(String bgTexture, String text, int pal, int cdtrk)
{
Super.Init(fadein|fadeout);
cinematile = TexMan.CheckForTexture(bgTexture, TexMan.Type_Any);
textov = new("TextOverlay");
palette = Translation.MakeID(Translation_BasePalette, pal);
textov.Init(text, Font.CR_NATIVEPAL, palette);
cdtrack = cdtrk;
return self;
}
override void Start()
{
System.StopAllSounds();
if (cdtrack != -1)
{
Exhumed.playCDtrack(cdtrack+2, false);
}
}
override void OnTick()
{
if (done) jobstate = finished;
}
override void Draw(double smoothratio)
{
Screen.DrawTexture(cinematile, false, 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43, DTA_TranslationIndex, palette);
textov.DisplayText();
done = textov.ScrollText((ticks + smoothratio) * (120. / GameTicRate));
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class LastLevelCinema : ScreenJob
{
int var_24;
int var_28;
int ebp;
int phase;
int nextclock;
uint nStringTypeOn, nCharTypeOn;
int screencnt;
bool skiprequest;
BrokenLines screentext;
Font printFont;
TextureID tex;
ScreenJob Init()
{
Super.Init(fadein | fadeout);
var_24 = 16;
var_28 = 12;
nextclock = 4;
let p = StringTable.Localize("REQUIRED_CHARACTERS", false);
if (p == "REQUIRED_CHARACTERS") printFont = SmallFont2;
else printFont = ConFont;
return self;
}
native static TextureID DoStatic(int a, int b);
native static TextureID UndoStatic();
void Phase1()
{
if (var_24 >= 116)
{
if (var_28 < 192)
var_28 += 20;
}
else
{
var_24 += 20;
}
tex = DoStatic(var_28, var_24);
}
bool InitPhase2()
{
let label = StringTable.Localize(String.Format("$TXT_EX_LASTLEVEL%d", screencnt + 1));
screentext = printFont.BreakLines(label, 320);
if (screentext.Count() == 0) return false;
nStringTypeOn = 0;
nCharTypeOn = 0;
ebp = screentext.Count() * 4; // half height of the entire text
ebp = 81 - ebp; // offset from the screen's center.
tex = UndoStatic();
return true;
}
bool Phase3()
{
tex = DoStatic(var_28, var_24);
if (var_28 > 20)
{
var_28 -= 20;
return true;
}
if (var_24 > 20)
{
var_24 -= 20;
return true;
}
return false;
}
void DisplayPhase2()
{
int yy = ebp;
// for international content, use the generic 8x8 font. The original one is too small for expansion.
if (printFont == ConFont)
{
yy *= 2;
for (int i = 0; i < nStringTypeOn; i++, yy += 10) Screen.DrawText(ConFont, Font.CR_GREEN, 140, yy, screentext.StringAt(i), DTA_FullscreenScale, FSMode_Fit640x400);
Screen.DrawText(ConFont, Font.CR_GREEN, 140, yy, screentext.StringAt(nStringTypeOn), DTA_FullscreenScale, FSMode_Fit640x400, DTA_TextLen, nCharTypeOn);
}
else
{
for (int i = 0; i < nStringTypeOn; i++, yy += 8) Screen.DrawText(SmallFont2, Font.CR_UNTRANSLATED, 70, yy, screentext.StringAt(i), DTA_FullscreenScale, FSMode_Fit320x200);
Screen.DrawText(SmallFont2, Font.CR_UNTRANSLATED, 70, yy, screentext.StringAt(nStringTypeOn), DTA_FullscreenScale, FSMode_Fit320x200, DTA_TextLen, nCharTypeOn);
}
}
override bool OnEvent(InputEvent ev)
{
if (ev.type == InputEvent.Type_KeyDown && !System.specialKeyEvent(ev)) skiprequest = true;
return true;
}
override void Start()
{
Exhumed.PlayLocalSound(ExhumedSnd.kSound75, 0, false, CHANF_UI);
phase = 1;
}
override void OnTick()
{
switch (phase)
{
case 1:
Phase1();
if (skiprequest || ticks >= nextclock)
{
InitPhase2();
phase = 2;
skiprequest = false;
}
break;
case 2:
{
let text = screenText.StringAt(nStringTypeOn);
int chr;
[chr,nCharTypeOn] = text.GetNextCodePoint(nCharTypeOn);
if (chr == 0)
{
nCharTypeOn = 0;
nStringTypeOn++;
if (nStringTypeOn >= screentext.Count())
{
nextclock = (GameTicRate * (screentext.Count() + 2)) + ticks;
phase = 3;
}
}
else
{
nCharTypeOn++;
if (chr != 32) Exhumed.PlayLocalSound(ExhumedSnd.kSound71, 0, false, CHANF_UI);
}
if (skiprequest)
{
nextclock = (GameTicRate * (screentext.Count() + 2)) + ticks;
phase = 4;
}
break;
}
case 3:
if (ticks >= nextclock || skiprequest)
{
Exhumed.PlayLocalSound(ExhumedSnd.kSound75, 0, false, CHANF_UI);
phase = 4;
nextclock = ticks + 60;
skiprequest = false;
}
case 4:
if (ticks >= nextclock)
{
skiprequest |= !Phase3();
}
if (skiprequest)
{
// Go to the next text page.
if (screencnt != 2)
{
screencnt++;
nextclock = ticks + 60;
skiprequest = 0;
phase = 1;
}
else jobstate = finished;
}
if (skiprequest)
{
jobstate = finished;
}
}
}
override void Draw(double sm)
{
Screen.DrawTexture(tex, false, 0, 0, DTA_FullscreenEx, FSMode_ScaleToFit43);
if (phase == 2 || phase == 3) DisplayPhase2();
}
}
//---------------------------------------------------------------------------
//
// Credits roll
//
//---------------------------------------------------------------------------
class ExCredits : ScreenJob
{
Array<String> credits;
Array<String> pagelines;
int page;
int pagetime;
bool skiprequest;
ScreenJob Init()
{
Super.Init();
String text;
int lump = Wads.CheckNumForFullName("credits.txt");
if (lump > -1) text = Wads.ReadLump(lump);
text.Substitute("\r", "");
text.Split(credits, "\n\n");
return self;
}
override bool OnEvent(InputEvent ev)
{
if (ev.type == InputEvent.Type_KeyDown && !System.specialKeyEvent(ev)) skiprequest = true;
return true;
}
override void Start()
{
if (credits.Size() == 0)
{
jobstate = finished;
return;
}
Exhumed.playCDtrack(19, false);
pagetime = 0;
page = -1;
}
override void OnTick()
{
if (ticks >= pagetime || skiprequest)
{
page++;
pagelines.Clear();
if (page < credits.Size())
credits[page].Split(pagelines, "\n");
else
{
if (skiprequest || !musplaying.handle)
{
jobstate = finished;
return;
}
}
pagetime = ticks + 90; //
}
}
override void Draw(double smoothratio)
{
int y = 100 - ((10 * (pagelines.Size() - 1)) / 2);
int ptime = clamp((pagetime - ticks - smoothratio) * 1000 / GameTicRate, 0, 2000); // in milliseconds
int light;
if (ptime < 255) light = ptime;
else if (ptime > 2000 - 255) light = 2000 - ptime;
else light = 255;
let colr = Color(255, light, light, light);
for (int i = 0; i < pagelines.Size(); i++)
{
int nStringWidth = SmallFont.StringWidth(pagelines[i]);
Screen.DrawText(SmallFont, Font.CR_UNTRANSLATED, 160 - nStringWidth / 2, y, pagelines[i], DTA_FullscreenScale, FSMode_Fit320x200, DTA_Color, colr);
y += 10;
}
}
}
class ExhumedCutscenes
{
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void BuildIntro(ScreenJobRunner runner)
{
let logo = (gameinfo.gameType & GAMEFLAG_EXHUMED) ? "TileBMGLogo" : "TilePIELogo";
runner.Append(ImageScreen.CreateNamed(logo, ScreenJob.fadein | ScreenJob.fadeout));
runner.Append(new("LobotomyScreen").Init("LobotomyLogo", ScreenJob.fadein | ScreenJob.fadeout));
if (LMFDecoder.Identify("book.mov")) runner.Append(new("LMFPlayer").Init("book.mov"));
else runner.Append(MoviePlayerJob.Create("book.mov", 0));
runner.Append(new("MainTitle").Init());
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void BuildMap(ScreenJobRunner runner, MapRecord frommap, SummaryInfo info, MapRecord tomap)
{
// This is only defined for the regular levels.
int frommapnum = frommap == null? 1 : frommap.levelNumber;
if (fromMapnum < 1 || fromMapNum > 20 || tomap == null || tomap.levelNumber < 1 || tomap.levelNumber > 20) return;
// hijack the super secret info in the summary info to convey the max. map because we won't need that field for its real purpose.
runner.Append(new("MapScreen").Init(fromMapNum, toMap.levelNumber, info.supersecrets));
}
//---------------------------------------------------------------------------
//
// This removes all the insanity the original setup had with these.
// Simplicity rules!
//
//---------------------------------------------------------------------------
static void BuildCinemaBefore5(ScreenJobRunner runner)
{
runner.Append(new("Cinema").Init("TileCinema5", "$TXT_EX_CINEMA2", 3, 2));
}
static void BuildCinemaAfter10(ScreenJobRunner runner)
{
runner.Append(new("Cinema").Init("TileCinema10", "$TXT_EX_CINEMA4", 5, 3));
}
static void BuildCinemaBefore11(ScreenJobRunner runner)
{
runner.Append(new("Cinema").Init("TileCinema11", "$TXT_EX_CINEMA3", 1, 4));
}
static void BuildCinemaAfter15(ScreenJobRunner runner)
{
runner.Append(new("Cinema").Init("TileCinema15", "$TXT_EX_CINEMA6", 7, 6));
}
static void BuildCinemaBefore20(ScreenJobRunner runner)
{
runner.Append(new("LastLevelCinema").Init());
}
static void BuildCinemaAfter20(ScreenJobRunner runner)
{
runner.Append(new("Cinema").Init("TileCinema20", "$TXT_EX_CINEMA8", 6, 8));
runner.Append(new("ExCredits").Init());
}
static void BuildCinemaLose(ScreenJobRunner runner)
{
runner.Append(new("Cinema").Init("TileCinemaLose", "$TXT_EX_CINEMA7", 4, 7));
}
//---------------------------------------------------------------------------
//
// player died
//
//---------------------------------------------------------------------------
static void BuildGameOverScene(ScreenJobRunner runner)
{
System.StopMusic();
Exhumed.PlayLocalSound(ExhumedSnd.kSoundJonLaugh2, 0, false, CHANF_UI);
runner.Append(ImageScreen.CreateNamed("Gameover", ScreenJob.fadein | ScreenJob.fadeout, 0x7fffffff, Translation.MakeID(Translation_BasePalette, 16)));
}
}