private int fnISpeak(SwordObject cpt, int id, int cdt, int textNo, int spr, int f, int z, int x)
{
_speechClickDelay = 3;
if (((textNo & ~1) == 0x3f0012) && (cdt == 0) && (spr == 0))
{
cdt = SwordRes.GEOSTDLCDT; // workaround for missing animation when examining
spr = SwordRes.GEOSTDL; // the conductor on the train roof
}
cpt.logic = LOGIC_speech;
// first setup the talk animation
if (cdt != 0 && (spr == 0))
{ // if 'cdt' is non-zero but 'spr' is zero - 'cdt' is an anim table tag
var animTabData = _resMan.OpenFetchRes((uint)cdt);
var anim = new AnimSet(animTabData, Header.Size + cpt.dir * AnimSet.Size);
cpt.anim_resource = (int)_resMan.ReadUInt32(anim.cdt);
if (anim.cdt != 0)
cpt.resource = (int)_resMan.ReadUInt32(anim.spr);
_resMan.ResClose((uint)cdt);
}
else
{
cpt.anim_resource = cdt;
if (cdt != 0)
cpt.resource = spr;
}
cpt.anim_pc = 0; // start anim from first frame
if (cpt.anim_resource != 0)
{
if (cpt.resource == 0)
throw new InvalidOperationException($"ID {id}: Can't run anim with cdt={cdt}, spr={spr}");
FrameHeader frameHead = new FrameHeader(_resMan.FetchFrame(_resMan.OpenFetchRes((uint)cpt.resource), 0));
if (frameHead.offsetX != 0 && frameHead.offsetY != 0)
{ // is this a boxed mega?
cpt.status |= STAT_SHRINK;
cpt.anim_x = cpt.xcoord;
cpt.anim_y = cpt.ycoord;
}
else
cpt.status &= ~STAT_SHRINK;
_resMan.ResClose((uint)cpt.resource);
}
if (SystemVars.PlaySpeech != 0)
_speechRunning = _sound.StartSpeech((ushort)(textNo >> 16), (ushort)(textNo & 0xFFFF));
else
_speechRunning = false;
_speechFinished = false;
if (SystemVars.ShowText != 0 || (!_speechRunning))
{
_textRunning = true;
var text = _objMan.LockText((uint) textNo);
cpt.speech_time = GetTextLength(text) + 5;
uint textCptId = _textMan.LowTextManager(text, cpt.speech_width, (byte)cpt.speech_pen);
_objMan.UnlockText((uint) textNo);
SwordObject textCpt = _objMan.FetchObject(textCptId);
textCpt.screen = cpt.screen;
textCpt.target = (int)textCptId;
// the graphic is a property of Text, so we don't lock/unlock it.
ushort textSpriteWidth = _resMan.ReadUInt16(new FrameHeader(_textMan.GiveSpriteData((byte)textCpt.target)).width);
ushort textSpriteHeight = _resMan.ReadUInt16(new FrameHeader(_textMan.GiveSpriteData((byte)textCpt.target)).height);
cpt.text_id = (int)textCptId;
// now set text coords, above the player, usually
const int TEXT_MARGIN = 3; // distance kept from edges of screen
const int ABOVE_HEAD = 20; // distance kept above talking sprite
ushort textX, textY;
if (((id == GEORGE) || ((id == NICO) && (ScriptVars[(int)ScriptVariableNames.SCREEN] == 10))) && (cpt.anim_resource == 0))
{
// if George is doing Voice-Over text (centered at the bottom of the screen)
textX = (ushort)(ScriptVars[(int)ScriptVariableNames.SCROLL_OFFSET_X] + 128 + (640 / 2) - textSpriteWidth / 2);
textY = (ushort)(ScriptVars[(int)ScriptVariableNames.SCROLL_OFFSET_Y] + 128 + 400);
}
else
{
if ((id == GEORGE) && (ScriptVars[(int)ScriptVariableNames.SCREEN] == 79))
textX = (ushort)cpt.mouse_x2; // move it off george's head
else
textX = (ushort)((cpt.mouse_x1 + cpt.mouse_x2) / 2 - textSpriteWidth / 2);
textY = (ushort)(cpt.mouse_y1 - textSpriteHeight - ABOVE_HEAD);
}
// now ensure text is within visible screen
ushort textLeftMargin, textRightMargin, textTopMargin, textBottomMargin;
textLeftMargin = (ushort)(Screen.SCREEN_LEFT_EDGE + TEXT_MARGIN + ScriptVars[(int)ScriptVariableNames.SCROLL_OFFSET_X]);
textRightMargin = (ushort)(Screen.SCREEN_RIGHT_EDGE - TEXT_MARGIN + ScriptVars[(int)ScriptVariableNames.SCROLL_OFFSET_X] - textSpriteWidth);
textTopMargin = (ushort)(Screen.SCREEN_TOP_EDGE + TEXT_MARGIN + ScriptVars[(int)ScriptVariableNames.SCROLL_OFFSET_Y]);
textBottomMargin = (ushort)(Screen.SCREEN_BOTTOM_EDGE - TEXT_MARGIN + ScriptVars[(int)ScriptVariableNames.SCROLL_OFFSET_Y] - textSpriteHeight);
textCpt.anim_x = textCpt.xcoord = ScummHelper.Clip(textX, textLeftMargin, textRightMargin);
textCpt.anim_y = textCpt.ycoord = ScummHelper.Clip(textY, textTopMargin, textBottomMargin);
}
return SCRIPT_STOP;
}