diff options
86 files changed, 30051 insertions, 0 deletions
diff --git a/base/plugins.cpp b/base/plugins.cpp index dcd394495f..216c6ef1af 100644 --- a/base/plugins.cpp +++ b/base/plugins.cpp @@ -140,6 +140,9 @@ public: #if PLUGIN_ENABLED_STATIC(SWORD2) LINK_PLUGIN(SWORD2) #endif + #if PLUGIN_ENABLED_STATIC(TINSEL) + LINK_PLUGIN(TINSEL) + #endif #if PLUGIN_ENABLED_STATIC(TOUCHE) LINK_PLUGIN(TOUCHE) #endif @@ -100,6 +100,7 @@ add_engine saga "SAGA" yes add_engine sky "Beneath a Steel Sky" yes add_engine sword1 "Broken Sword 1" yes add_engine sword2 "Broken Sword 2" yes +add_engine tinsel "Tinsel" no add_engine touche "Touche: The Adventures of the Fifth Musketeer" yes _endian=unknown diff --git a/dists/msvc8/tinsel.vcproj b/dists/msvc8/tinsel.vcproj new file mode 100644 index 0000000000..48eb5bbbe5 --- /dev/null +++ b/dists/msvc8/tinsel.vcproj @@ -0,0 +1,502 @@ +<?xml version="1.0" encoding="windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8,00" + Name="tinsel" + ProjectGUID="{22AA7760-2C91-11DD-BD0B-0800200C9A66}" + RootNamespace="tinsel" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="tinsel_Debug" + IntermediateDirectory="tinsel_Debug" + ConfigurationType="4" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996" + Optimization="0" + AdditionalIncludeDirectories="../..;../../engines" + PreprocessorDefinitions="WIN32;_DEBUG;USE_ZLIB;USE_MAD;USE_VORBIS" + MinimalRebuild="true" + ExceptionHandling="1" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + BufferSecurityCheck="true" + EnableFunctionLevelLinking="true" + ForceConformanceInForLoopScope="true" + UsePrecompiledHeader="0" + WarningLevel="4" + WarnAsError="false" + SuppressStartupBanner="false" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/tinsel.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="tinsel_Release" + IntermediateDirectory="tinsel_Release" + ConfigurationType="4" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996" + Optimization="3" + InlineFunctionExpansion="2" + OmitFramePointers="true" + AdditionalIncludeDirectories="../../;../../engines" + PreprocessorDefinitions="WIN32;NDEBUG;USE_ZLIB;USE_MAD;USE_VORBIS" + StringPooling="true" + ExceptionHandling="1" + RuntimeLibrary="0" + BufferSecurityCheck="false" + EnableFunctionLevelLinking="false" + ForceConformanceInForLoopScope="true" + UsePrecompiledHeader="0" + WarningLevel="4" + WarnAsError="true" + DebugInformationFormat="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/tinsel.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="libcoro" + > + <File + RelativePath="..\..\engines\tinsel\libcoro\coro.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\libcoro\coro.h" + > + </File> + </Filter> + <File + RelativePath="..\..\engines\tinsel\actors.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\actors.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\anim.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\anim.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\background.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\background.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\bg.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cliprect.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cliprect.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\config.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\config.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cursor.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cursor.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\debugger.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\debugger.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\detection.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\dos_hand.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\dw.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\dwtypes.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\effect.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\events.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\events.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\faders.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\faders.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\fileio.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\film.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\font.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\font.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\graphics.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\graphics.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\handle.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\heapmem.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\heapmem.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\inventory.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\inventory.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\mareels.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\move.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\move.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\multiobj.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\multiobj.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\music.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\music.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\object.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\object.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\palette.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\palette.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pcode.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pcode.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pdisplay.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pid.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\play.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\polygons.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\polygons.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\rince.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\rince.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\saveload.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\savescn.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\savescn.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scene.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scene.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sched.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sched.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scn.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scn.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scroll.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scroll.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\serializer.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sound.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sound.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\strres.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\strres.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\text.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\text.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\timers.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\timers.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinlib.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinlib.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinsel.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinsel.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\token.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\token.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/dists/msvc9/tinsel.vcproj b/dists/msvc9/tinsel.vcproj new file mode 100644 index 0000000000..aca1a82ab8 --- /dev/null +++ b/dists/msvc9/tinsel.vcproj @@ -0,0 +1,503 @@ +<?xml version="1.0" encoding="windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="9,00" + Name="tinsel" + ProjectGUID="{22AA7760-2C91-11DD-BD0B-0800200C9A66}" + RootNamespace="tinsel" + Keyword="Win32Proj" + TargetFrameworkVersion="131072" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="tinsel_Debug" + IntermediateDirectory="tinsel_Debug" + ConfigurationType="4" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996" + Optimization="0" + AdditionalIncludeDirectories="../..;../../engines" + PreprocessorDefinitions="WIN32;_DEBUG;USE_ZLIB;USE_MAD;USE_VORBIS" + MinimalRebuild="true" + ExceptionHandling="1" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + BufferSecurityCheck="true" + EnableFunctionLevelLinking="true" + ForceConformanceInForLoopScope="true" + UsePrecompiledHeader="0" + WarningLevel="4" + WarnAsError="false" + SuppressStartupBanner="false" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/tinsel.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="tinsel_Release" + IntermediateDirectory="tinsel_Release" + ConfigurationType="4" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996" + Optimization="3" + InlineFunctionExpansion="2" + OmitFramePointers="true" + AdditionalIncludeDirectories="../../;../../engines" + PreprocessorDefinitions="WIN32;NDEBUG;USE_ZLIB;USE_MAD;USE_VORBIS" + StringPooling="true" + ExceptionHandling="1" + RuntimeLibrary="0" + BufferSecurityCheck="false" + EnableFunctionLevelLinking="false" + ForceConformanceInForLoopScope="true" + UsePrecompiledHeader="0" + WarningLevel="4" + WarnAsError="true" + DebugInformationFormat="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/tinsel.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="libcoro" + > + <File + RelativePath="..\..\engines\tinsel\libcoro\coro.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\libcoro\coro.h" + > + </File> + </Filter> + <File + RelativePath="..\..\engines\tinsel\actors.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\actors.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\anim.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\anim.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\background.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\background.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\bg.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cliprect.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cliprect.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\config.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\config.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cursor.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\cursor.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\debugger.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\debugger.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\detection.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\dos_hand.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\dw.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\dwtypes.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\effect.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\events.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\events.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\faders.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\faders.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\fileio.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\film.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\font.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\font.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\graphics.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\graphics.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\handle.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\heapmem.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\heapmem.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\inventory.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\inventory.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\mareels.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\move.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\move.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\multiobj.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\multiobj.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\music.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\music.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\object.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\object.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\palette.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\palette.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pcode.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pcode.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pdisplay.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\pid.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\play.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\polygons.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\polygons.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\rince.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\rince.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\saveload.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\savescn.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\savescn.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scene.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scene.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sched.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sched.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scn.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scn.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scroll.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\scroll.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\serializer.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sound.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\sound.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\strres.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\strres.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\text.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\text.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\timers.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\timers.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinlib.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinlib.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinsel.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\tinsel.h" + > + </File> + <File + RelativePath="..\..\engines\tinsel\token.cpp" + > + </File> + <File + RelativePath="..\..\engines\tinsel\token.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/engines/engines.mk b/engines/engines.mk index cfb8e69f3e..4dba913173 100644 --- a/engines/engines.mk +++ b/engines/engines.mk @@ -97,6 +97,11 @@ DEFINES += -DENABLE_SWORD2=$(ENABLE_SWORD2) MODULES += engines/sword2 endif +ifdef ENABLE_TINSEL +DEFINES += -DENABLE_TINSEL=$(ENABLE_TINSEL) +MODULES += engines/tinsel +endif + ifdef ENABLE_TOUCHE DEFINES += -DENABLE_TOUCHE=$(ENABLE_TOUCHE) MODULES += engines/touche diff --git a/engines/tinsel/actors.cpp b/engines/tinsel/actors.cpp new file mode 100644 index 0000000000..e0689f1374 --- /dev/null +++ b/engines/tinsel/actors.cpp @@ -0,0 +1,896 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Handles things to do with actors, delegates much moving actor stuff. + */ + +#include "tinsel/actors.h" +#include "tinsel/events.h" +#include "tinsel/film.h" // for FREEL +#include "tinsel/handle.h" +#include "tinsel/inventory.h" // INV_NOICON +#include "tinsel/move.h" +#include "tinsel/multiobj.h" +#include "tinsel/object.h" // for POBJECT +#include "tinsel/pcode.h" +#include "tinsel/pid.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/serializer.h" +#include "tinsel/token.h" + +#include "common/util.h" + +namespace Tinsel { + + +//----------------- LOCAL DEFINES -------------------- + + +#include "common/pack-start.h" // START STRUCT PACKING + +/** actor struct - one per actor */ +struct ACTOR_STRUC { + int32 masking; //!< type of actor masking + SCNHANDLE hActorId; //!< handle actor ID string index + SCNHANDLE hActorCode; //!< handle to actor script +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int LeadActorId = 0; // The lead actor + +static int NumActors = 0; // The total number of actors in the game + +struct ACTORINFO { + bool alive; // TRUE == alive + bool hidden; // TRUE == hidden + bool completed; // TRUE == script played out + + int x, y, z; + + int32 mtype; // DEFAULT(b'ground), MASK, ALWAYS + SCNHANDLE actorCode; // The actor's script + + const FREEL *presReel; // the present reel + int presRnum; // the present reel number + SCNHANDLE presFilm; // the film that reel belongs to + OBJECT *presObj; // reference for position information + int presX, presY; + + bool tagged; // actor tagged? + SCNHANDLE hTag; // handle to tag text + int tType; // e.g. TAG_Q1TO3 + + bool escOn; + int escEv; + + COLORREF tColour; // Text colour + + SCNHANDLE playFilm; // revert to this after talks + SCNHANDLE talkFilm; // this be deleted in the future! + SCNHANDLE latestFilm; // the last film ordered + bool talking; + + int steps; + +}; + +static ACTORINFO *actorInfo = 0; + +static COLORREF defaultColour = 0; // Text colour + +static bool bActorsOn = false; + +static int ti = 0; + +/** + * Called once at start-up time, and again at restart time. + * Registers the total number of actors in the game. + * @param num Chunk Id + */ +void RegisterActors(int num) { + if (actorInfo == NULL) { + // Store the total number of actors in the game + NumActors = num; + + // Check we can save so many + assert(NumActors <= MAX_SAVED_ALIVES); + + // Allocate RAM for actorInfo + // FIXME: For now, we always allocate MAX_SAVED_ALIVES blocks, + // as this makes the save/load code simpler + actorInfo = (ACTORINFO *)calloc(MAX_SAVED_ALIVES, sizeof(ACTORINFO)); + + // make sure memory allocated + if (actorInfo == NULL) { + error("Cannot allocate memory for actors"); + } + } else { + // Check the total number of actors is still the same + assert(num == NumActors); + + memset(actorInfo, 0, MAX_SAVED_ALIVES * sizeof(ACTORINFO)); + } + + // All actors start off alive. + while (num--) + actorInfo[num].alive = true; +} + +void FreeActors() { + if (actorInfo) { + free(actorInfo); + actorInfo = NULL; + } +} + +/** + * Called from dec_lead(), i.e. normally once at start of master script. + * @param leadID Lead Id + */ +void setleadid(int leadID) { + LeadActorId = leadID; + actorInfo[leadID-1].mtype = ACT_MASK; +} + +/** + * No comment. + */ +int LeadId(void) { + return LeadActorId; +} + +struct ATP_INIT { + int id; // Actor number + USER_EVENT event; // Event + BUTEVENT bev; // Causal mouse event +}; + +/** + * Runs actor's glitter code. + */ +static void ActorTinselProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + PINT_CONTEXT pic; + CORO_END_CONTEXT(_ctx); + + // get the stuff copied to process when it was created + ATP_INIT *atp = (ATP_INIT *)ProcessGetParamsSelf(); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(AllowDclick, atp->bev); // May kill us if single click + + // Run the Glitter code + assert(actorInfo[atp->id - 1].actorCode); // no code to run + + _ctx->pic = InitInterpretContext(GS_ACTOR, actorInfo[atp->id - 1].actorCode, atp->event, NOPOLY, atp->id, NULL); + CORO_INVOKE_1(Interpret, _ctx->pic); + + // If it gets here, actor's code has run to completion + actorInfo[atp->id - 1].completed = true; + + CORO_END_CODE; +} + + +//--------------------------------------------------------------------------- + +struct RATP_INIT { + PINT_CONTEXT pic; + int id; // Actor number +}; + +static void ActorRestoredProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + PINT_CONTEXT pic; + CORO_END_CONTEXT(_ctx); + + // get the stuff copied to process when it was created + RATP_INIT *r = (RATP_INIT *)ProcessGetParamsSelf(); + + CORO_BEGIN_CODE(_ctx); + + _ctx->pic = RestoreInterpretContext(r->pic); + CORO_INVOKE_1(Interpret, _ctx->pic); + + // If it gets here, actor's code has run to completion + actorInfo[r->id - 1].completed = true; + + CORO_END_CODE; +} + +void RestoreActorProcess(int id, PINT_CONTEXT pic) { + RATP_INIT r = { pic, id }; + + CoroutineInstall(PID_TCODE, ActorRestoredProcess, &r, sizeof(r)); +} + +/** + * Starts up process to runs actor's glitter code. + * @param ano Actor Id + * @param event Event structure + * @param be ButEvent + */ +void actorEvent(int ano, USER_EVENT event, BUTEVENT be) { + ATP_INIT atp; + + // Only if there is Glitter code associated with this actor. + if (actorInfo[ano - 1].actorCode) { + atp.id = ano; + atp.event = event; + atp.bev = be; + CoroutineInstall(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp)); + } +} + +/** + * Called at the start of each scene for each actor with a code block. + * @param as Actor structure + * @param bRunScript Flag for whether to run actor's script for the scene + */ +void StartActor(const ACTOR_STRUC *as, bool bRunScript) { + SCNHANDLE hActorId = FROM_LE_32(as->hActorId); + + // Zero-out many things + actorInfo[hActorId - 1].hidden = false; + actorInfo[hActorId - 1].completed = false; + actorInfo[hActorId - 1].x = 0; + actorInfo[hActorId - 1].y = 0; + actorInfo[hActorId - 1].presReel = NULL; + actorInfo[hActorId - 1].presFilm = 0; + actorInfo[hActorId - 1].presObj = NULL; + + // Store current scene's parameters for this actor + actorInfo[hActorId - 1].mtype = FROM_LE_32(as->masking); + actorInfo[hActorId - 1].actorCode = FROM_LE_32(as->hActorCode); + + // Run actor's script for this scene + if (bRunScript) { + if (bActorsOn) + actorInfo[hActorId - 1].alive = true; + + if (actorInfo[hActorId - 1].alive && FROM_LE_32(as->hActorCode)) + actorEvent(hActorId, STARTUP, BE_NONE); + } +} + +/** + * Called at the start of each scene. Start each actor with a code block. + * @param ah Scene handle + * @param numActors Number of actors + * @param bRunScript Flag for whether to run actor scene scripts + */ +void StartActors(SCNHANDLE ah, int numActors, bool bRunScript) { + int i; + + // Only actors with code blocks got (x, y) re-initialised, so... + for (i = 0; i < NumActors; i++) { + actorInfo[i].x = actorInfo[i].y = 0; + actorInfo[i].mtype = 0; + } + + const ACTOR_STRUC *as = (const ACTOR_STRUC *)LockMem(ah); + for (i = 0; i < numActors; i++, as++) { + StartActor(as, bRunScript); + } +} + +/** + * Called between scenes, zeroises all actors. + */ +void DropActors(void) { + for (int i = 0; i < NumActors; i++) { + actorInfo[i].actorCode = 0; // No script + actorInfo[i].presReel = NULL; // No reel running + actorInfo[i].presFilm = 0; // ditto + actorInfo[i].presObj = NULL; // No object + actorInfo[i].x = 0; // No position + actorInfo[i].y = 0; // ditto + + actorInfo[i].talkFilm = 0; + actorInfo[i].latestFilm = 0; + actorInfo[i].playFilm = 0; + actorInfo[i].talking = false; + } +} + +/** + * Kill actors. + * @param ano Actor Id + */ +void DisableActor(int ano) { + PMACTOR pActor; + + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].alive = false; // Record as dead + actorInfo[ano - 1].x = actorInfo[ano - 1].y = 0; + + // Kill off moving actor properly + pActor = GetMover(ano); + if (pActor) + KillMActor(pActor); +} + +/** + * Enable actors. + * @param ano Actor Id + */ +void EnableActor(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + // Re-incarnate only if it's dead, or it's script ran to completion + if (!actorInfo[ano - 1].alive || actorInfo[ano - 1].completed) { + actorInfo[ano - 1].alive = true; + actorInfo[ano - 1].hidden = false; + actorInfo[ano - 1].completed = false; + + // Re-run actor's script for this scene + if (actorInfo[ano-1].actorCode) + actorEvent(ano, STARTUP, BE_NONE); + } +} + +/** + * Returns the aliveness (to coin a word) of the actor. + * @param ano Actor Id + */ +bool actorAlive(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].alive; +} + +/** + * Define an actor as being tagged. + * @param ano Actor Id + * @param tagtext Scene handle + * @param tp tType + */ +void Tag_Actor(int ano, SCNHANDLE tagtext, int tp) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano-1].tagged = true; + actorInfo[ano-1].hTag = tagtext; + actorInfo[ano-1].tType = tp; +} + +/** + * Undefine an actor as being tagged. + * @param ano Actor Id + * @param tagtext Scene handle + * @param tp tType + */ +void UnTagActor(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano-1].tagged = false; +} + +/** + * Redefine an actor as being tagged. + * @param ano Actor Id + * @param tagtext Scene handle + * @param tp tType + */ +void ReTagActor(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + if (actorInfo[ano-1].hTag) + actorInfo[ano-1].tagged = true; +} + +/** + * Returns a tagged actor's tag type. e.g. TAG_Q1TO3 + * @param ano Actor Id + */ +int TagType(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano-1].tType; +} + +/** + * Returns handle to tagged actor's tag text + * @param ano Actor Id + */ +SCNHANDLE GetActorTag(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].hTag; +} + +/** + * Called from TagProcess, FirstTaggedActor() resets the index, then + * NextTagged Actor is repeatedly called until the caller gets fed up + * or there are no more tagged actors to look at. + */ +void FirstTaggedActor(void) { + ti = 0; +} + +/** + * Called from TagProcess, FirstTaggedActor() resets the index, then + * NextTagged Actor is repeatedly called until the caller gets fed up + * or there are no more tagged actors to look at. + */ +int NextTaggedActor(void) { + PMACTOR pActor; + bool hid; + + do { + if (actorInfo[ti].tagged) { + pActor = GetMover(ti+1); + if (pActor) + hid = getMActorHideState(pActor); + else + hid = actorInfo[ti].hidden; + + if (!hid) { + return ++ti; + } + } + } while (++ti < NumActors); + + return 0; +} + +/** + * Returns the masking type of the actor. + * @param ano Actor Id + */ +int32 actorMaskType(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].mtype; +} + +/** + * Store/Return the currently stored co-ordinates of the actor. + * Delegate the task for moving actors. + * @param ano Actor Id + * @param x X position + * @param y Y position + */ +void storeActorPos(int ano, int x, int y) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].x = x; + actorInfo[ano - 1].y = y; +} + +void storeActorSteps(int ano, int steps) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].steps = steps; +} + +int getActorSteps(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].steps; +} + +void storeActorZpos(int ano, int z) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].z = z; +} + + +void GetActorPos(int ano, int *x, int *y) { + PMACTOR pActor; + + assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor + + pActor = GetMover(ano); + + if (pActor) + GetMActorPosition(pActor, x, y); + else { + *x = actorInfo[ano - 1].x; + *y = actorInfo[ano - 1].y; + } +} + +/** + * Returns the position of the mid-top of the actor. + * Delegate the task for moving actors. + * @param ano Actor Id + * @param x Output x + * @param y Output y + */ +void GetActorMidTop(int ano, int *x, int *y) { + // Not used in JAPAN version + PMACTOR pActor; + + assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor + + pActor = GetMover(ano); + + if (pActor) + GetMActorMidTopPosition(pActor, x, y); + else if (actorInfo[ano - 1].presObj) { + *x = (MultiLeftmost(actorInfo[ano - 1].presObj) + + MultiRightmost(actorInfo[ano - 1].presObj)) / 2; + *y = MultiHighest(actorInfo[ano - 1].presObj); + } else + GetActorPos(ano, x, y); // The best we can do! +} + +/** + * Return the appropriate co-ordinate of the actor. + * @param ano Actor Id + */ +int GetActorLeft(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + if (!actorInfo[ano - 1].presObj) + return 0; + + return MultiLeftmost(actorInfo[ano - 1].presObj); +} + +/** + * Return the appropriate co-ordinate of the actor. + * @param ano Actor Id + */ +int GetActorRight(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + if (!actorInfo[ano - 1].presObj) + return 0; + + return MultiRightmost(actorInfo[ano - 1].presObj); +} + +/** + * Return the appropriate co-ordinate of the actor. + * @param ano Actor Id + */ +int GetActorTop(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + if (!actorInfo[ano - 1].presObj) + return 0; + + return MultiHighest(actorInfo[ano - 1].presObj); +} + +/** + * Return the appropriate co-ordinate of the actor. + */ +int GetActorBottom(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + if (!actorInfo[ano - 1].presObj) + return 0; + + return MultiLowest(actorInfo[ano - 1].presObj); +} + +/** + * Set actor hidden status to true. + * For a moving actor, actually hide it. + * @param ano Actor Id + */ +void HideActor(int ano) { + PMACTOR pActor; + + assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor + + // Get moving actor involved + pActor = GetMover(ano); + + if (pActor) + hideMActor(pActor, 0); + else + actorInfo[ano - 1].hidden = true; +} + +/** + * Hide an actor if it's a moving actor. + * @param ano Actor Id + * @param sf sf + */ +bool HideMovingActor(int ano, int sf) { + PMACTOR pActor; + + assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor + + // Get moving actor involved + pActor = GetMover(ano); + + if (pActor) { + hideMActor(pActor, sf); + return true; + } else { + if (actorInfo[ano - 1].presObj != NULL) + MultiHideObject(actorInfo[ano - 1].presObj); // Hidee object + return false; + } +} + +/** + * Unhide an actor if it's a moving actor. + * @param ano Actor Id + */ +void unHideMovingActor(int ano) { + PMACTOR pActor; + + assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor + + // Get moving actor involved + pActor = GetMover(ano); + + assert(pActor); // not a moving actor + + unhideMActor(pActor); +} + +/** + * Called after a moving actor had been replaced by an splay(). + * Moves the actor to where the splay() left it, and continues the + * actor's walk (if any) from the new co-ordinates. + */ +void restoreMovement(int ano) { + PMACTOR pActor; + + assert(ano > 0 && ano <= NumActors); // illegal actor number + + // Get moving actor involved + pActor = GetMover(ano); + + assert(pActor); // not a moving actor + + if (pActor->objx == actorInfo[ano - 1].x && pActor->objy == actorInfo[ano - 1].y) + return; + + pActor->objx = actorInfo[ano - 1].x; + pActor->objy = actorInfo[ano - 1].y; + + if (pActor->actorObj) + SSetActorDest(pActor); +} + +/** + * More properly should be called: + * 'store_actor_reel_and/or_film_and/or_object()' + */ +void storeActorReel(int ano, const FREEL *reel, SCNHANDLE film, OBJECT *pobj, int reelnum, int x, int y) { + PMACTOR pActor; + + assert(ano > 0 && ano <= NumActors); // illegal actor number + + pActor = GetMover(ano); + + // Only store the reel and film for a moving actor if NOT called from MActorProcess() + // (MActorProcess() calls with reel=film=NULL, pobj not NULL) + if (!pActor + || !(reel == NULL && film == 0 && pobj != NULL)) { + actorInfo[ano - 1].presReel = reel; // Store reel + actorInfo[ano - 1].presRnum = reelnum; // Store reel number + actorInfo[ano - 1].presFilm = film; // Store film + actorInfo[ano - 1].presX = x; + actorInfo[ano - 1].presY = y; + } + + // Only store the object for a moving actor if called from MActorProcess() + if (!pActor) { + actorInfo[ano - 1].presObj = pobj; // Store object + } else if (reel == NULL && film == 0 && pobj != NULL) { + actorInfo[ano - 1].presObj = pobj; // Store object + } +} + +/** + * Return the present reel/film of the actor. + */ +const FREEL *actorReel(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].presReel; // the present reel +} + +/***************************************************************************/ + +void setActorPlayFilm(int ano, SCNHANDLE film) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].playFilm = film; +} + +SCNHANDLE getActorPlayFilm(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].playFilm; +} + +void setActorTalkFilm(int ano, SCNHANDLE film) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].talkFilm = film; +} + +SCNHANDLE getActorTalkFilm(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].talkFilm; +} + +void setActorTalking(int ano, bool tf) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].talking = tf;; +} + +bool isActorTalking(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].talking; +} + +void setActorLatestFilm(int ano, SCNHANDLE film) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].latestFilm = film; + actorInfo[ano - 1].steps = 0; +} + +SCNHANDLE getActorLatestFilm(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].latestFilm; +} + +/***************************************************************************/ + +void updateActorEsc(int ano, bool escOn, int escEvent) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + actorInfo[ano - 1].escOn = escOn; + actorInfo[ano - 1].escEv = escEvent; +} + +bool actorEsc(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].escOn; +} + +int actorEev(int ano) { + assert(ano > 0 && ano <= NumActors); // illegal actor number + + return actorInfo[ano - 1].escEv; +} + +/** + * Guess what these do. + */ +int AsetZPos(OBJECT *pObj, int y, int32 z) { + int zPos; + + z += z ? -1 : 0; + + zPos = y + (z << 10); + MultiSetZPosition(pObj, zPos); + return zPos; +} + +/** + * Guess what these do. + */ +void MAsetZPos(PMACTOR pActor, int y, int32 zFactor) { + if (!pActor->aHidden) + AsetZPos(pActor->actorObj, y, zFactor); +} + +/** + * Stores actor's attributes. + * Currently only the speech colours. + */ +void storeActorAttr(int ano, int r1, int g1, int b1) { + assert((ano > 0 && ano <= NumActors) || ano == -1); // illegal actor number + + if (r1 > MAX_INTENSITY) r1 = MAX_INTENSITY; // } Ensure + if (g1 > MAX_INTENSITY) g1 = MAX_INTENSITY; // } within limits + if (b1 > MAX_INTENSITY) b1 = MAX_INTENSITY; // } + + if (ano == -1) + defaultColour = RGB(r1, g1, b1); + else + actorInfo[ano - 1].tColour = RGB(r1, g1, b1); +} + +/** + * Get the actor's stored speech colour. + * @param ano Actor Id + */ +COLORREF getActorTcol(int ano) { + // Not used in JAPAN version + assert(ano > 0 && ano <= NumActors); // illegal actor number + + if (actorInfo[ano - 1].tColour) + return actorInfo[ano - 1].tColour; + else + return defaultColour; +} + +/** + * Store relevant information pertaining to currently existing actors. + */ +int SaveActors(PSAVED_ACTOR sActorInfo) { + int i, j; + + for (i = 0, j = 0; i < NumActors; i++) { + if (actorInfo[i].presObj != NULL) { + assert(j < MAX_SAVED_ACTORS); // Saving too many actors + +// sActorInfo[j].hidden = actorInfo[i].hidden; + sActorInfo[j].bAlive = actorInfo[i].alive; +// sActorInfo[j].x = (short)actorInfo[i].x; +// sActorInfo[j].y = (short)actorInfo[i].y; + sActorInfo[j].z = (short)actorInfo[i].z; +// sActorInfo[j].presReel = actorInfo[i].presReel; + sActorInfo[j].presRnum = (short)actorInfo[i].presRnum; + sActorInfo[j].presFilm = actorInfo[i].presFilm; + sActorInfo[j].presX = (short)actorInfo[i].presX; + sActorInfo[j].presY = (short)actorInfo[i].presY; + sActorInfo[j].actorID = (short)(i+1); + j++; + } + } + + return j; +} + +void setactorson(void) { + bActorsOn = true; +} + +void ActorsLife(int ano, bool bAlive) { + assert((ano > 0 && ano <= NumActors) || ano == -1); // illegal actor number + + actorInfo[ano-1].alive = bAlive; +} + + +void syncAllActorsAlive(Serializer &s) { + for (int i = 0; i < MAX_SAVED_ALIVES; i++) { + s.syncAsByte(actorInfo[i].alive); + s.syncAsByte(actorInfo[i].tagged); + s.syncAsByte(actorInfo[i].tType); + s.syncAsUint32LE(actorInfo[i].hTag); + } +} + + +} // end of namespace Tinsel diff --git a/engines/tinsel/actors.h b/engines/tinsel/actors.h new file mode 100644 index 0000000000..7707dd636d --- /dev/null +++ b/engines/tinsel/actors.h @@ -0,0 +1,126 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Prototypes of actor functions + */ + +#ifndef TINSEL_ACTOR_H // prevent multiple includes +#define TINSEL_ACTOR_H + + +#include "tinsel/dw.h" // for SCNHANDLE +#include "tinsel/events.h" // for USER_EVENT +#include "tinsel/palette.h" // for COLORREF + +namespace Tinsel { + +struct FREEL; +struct INT_CONTEXT; +struct MACTOR; +struct OBJECT; + + +/*----------------------------------------------------------------------*/ + +void RegisterActors(int num); +void FreeActors(void); +void setleadid(int rid); +int LeadId(void); +void StartActors(SCNHANDLE ah, int numActors, bool bRunScript); +void DropActors(void); // No actor reels running +void DisableActor(int actor); +void EnableActor(int actor); +void Tag_Actor(int ano, SCNHANDLE tagtext, int tp); +void UnTagActor(int ano); +void ReTagActor(int ano); +int TagType(int ano); +bool actorAlive(int ano); +int32 actorMaskType(int ano); +void GetActorPos(int ano, int *x, int *y); +void SetActorPos(int ano, int x, int y); +void GetActorMidTop(int ano, int *x, int *y); +int GetActorLeft(int ano); +int GetActorRight(int ano); +int GetActorTop(int ano); +int GetActorBottom(int ano); +void HideActor(int ano); +bool HideMovingActor(int id, int sf); +void unHideMovingActor(int id); +void restoreMovement(int id); +void storeActorReel(int ano, const FREEL *reel, SCNHANDLE film, OBJECT *pobj, int reelnum, int x, int y); +const FREEL *actorReel(int ano); +SCNHANDLE actorFilm(int ano); + +void setActorPlayFilm(int ano, SCNHANDLE film); +SCNHANDLE getActorPlayFilm(int ano); +void setActorTalkFilm(int ano, SCNHANDLE film); +SCNHANDLE getActorTalkFilm(int ano); +void setActorTalking(int ano, bool tf); +bool isActorTalking(int ano); +void setActorLatestFilm(int ano, SCNHANDLE film); +SCNHANDLE getActorLatestFilm(int ano); + +void updateActorEsc(int ano, bool escOn, int escEv); +bool actorEsc(int ano); +int actorEev(int ano); +void storeActorPos(int ano, int x, int y); +void storeActorSteps(int ano, int steps); +int getActorSteps(int ano); +void storeActorZpos(int ano, int z); +SCNHANDLE GetActorTag(int ano); +void FirstTaggedActor(void); +int NextTaggedActor(void); +int AsetZPos(OBJECT *pObj, int y, int32 zFactor); +void MAsetZPos(MACTOR *pActor, int y, int32 zFactor); +void actorEvent(int ano, USER_EVENT event, BUTEVENT be); + +void storeActorAttr(int ano, int r1, int g1, int b1); +COLORREF getActorTcol(int ano); + +void setactorson(void); + +void ActorsLife(int id, bool bAlive); + +/*----------------------------------------------------------------------*/ + +struct SAVED_ACTOR { + short actorID; + short z; + bool bAlive; + SCNHANDLE presFilm; //!< the film that reel belongs to + short presRnum; //!< the present reel number + short presX, presY; +}; +typedef SAVED_ACTOR *PSAVED_ACTOR; + +int SaveActors(PSAVED_ACTOR sActorInfo); + + +void RestoreActorProcess(int id, INT_CONTEXT *pic); + + +/*----------------------------------------------------------------------*/ + +} // end of namespace Tinsel + +#endif /* TINSEL_ACTOR_H */ diff --git a/engines/tinsel/anim.cpp b/engines/tinsel/anim.cpp new file mode 100644 index 0000000000..266c39bca8 --- /dev/null +++ b/engines/tinsel/anim.cpp @@ -0,0 +1,404 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * This file contains utilities to handle object animation. + */ + +#include "tinsel/anim.h" +#include "tinsel/handle.h" +#include "tinsel/multiobj.h" // multi-part object defintions etc. +#include "tinsel/object.h" +#include "tinsel/sched.h" + +#include "common/util.h" + +namespace Tinsel { + +/** Animation script commands */ +enum { + ANI_END = 0, //!< end of animation script + ANI_JUMP = 1, //!< animation script jump + ANI_HFLIP = 2, //!< flip animated object horizontally + ANI_VFLIP = 3, //!< flip animated object vertically + ANI_HVFLIP = 4, //!< flip animated object in both directions + ANI_ADJUSTX = 5, //!< adjust animated object x animation point + ANI_ADJUSTY = 6, //!< adjust animated object y animation point + ANI_ADJUSTXY = 7, //!< adjust animated object x & y animation points + ANI_NOSLEEP = 8, //!< do not sleep for this frame + ANI_CALL = 9, //!< call routine + ANI_HIDE = 10 //!< hide animated object +}; + +/** animation script command possibilities */ +union ANI_SCRIPT { + int32 op; //!< treat as an opcode or operand + uint32 hFrame; //!< treat as a animation frame handle +}; + +/** + * Advance to next frame routine. + * @param pAnim Animation data structure + */ +SCRIPTSTATE DoNextFrame(PANIM pAnim) { + // get a pointer to the script + const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript); + + while (1) { // repeat until a real image + + switch ((int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)) { + case ANI_END: // end of animation script + + // move to next opcode + pAnim->scriptIndex++; + + // indicate script has finished + return ScriptFinished; + + case ANI_JUMP: // do animation jump + + // move to jump address + pAnim->scriptIndex++; + + // jump to new frame position + pAnim->scriptIndex += (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + + // go fetch a real image + break; + + case ANI_HFLIP: // flip animated object horizontally + + // next opcode + pAnim->scriptIndex++; + + MultiHorizontalFlip(pAnim->pObject); + + // go fetch a real image + break; + + case ANI_VFLIP: // flip animated object vertically + + // next opcode + pAnim->scriptIndex++; + + MultiVerticalFlip(pAnim->pObject); + + // go fetch a real image + break; + + case ANI_HVFLIP: // flip animated object in both directions + + // next opcode + pAnim->scriptIndex++; + + MultiHorizontalFlip(pAnim->pObject); + MultiVerticalFlip(pAnim->pObject); + + // go fetch a real image + break; + + case ANI_ADJUSTX: // adjust animated object x animation point + + // move to x adjustment operand + pAnim->scriptIndex++; + + MultiAdjustXY(pAnim->pObject, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op), 0); + + // next opcode + pAnim->scriptIndex++; + + // go fetch a real image + break; + + case ANI_ADJUSTY: // adjust animated object y animation point + + // move to y adjustment operand + pAnim->scriptIndex++; + + MultiAdjustXY(pAnim->pObject, 0, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)); + + // next opcode + pAnim->scriptIndex++; + + // go fetch a real image + break; + + case ANI_ADJUSTXY: // adjust animated object x & y animation points + { + int x, y; + + // move to x adjustment operand + pAnim->scriptIndex++; + x = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + + // move to y adjustment operand + pAnim->scriptIndex++; + y = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + + MultiAdjustXY(pAnim->pObject, x, y); + + // next opcode + pAnim->scriptIndex++; + + // go fetch a real image + break; + } + + case ANI_NOSLEEP: // do not sleep for this frame + + // next opcode + pAnim->scriptIndex++; + + // indicate not to sleep + return ScriptNoSleep; + + case ANI_CALL: // call routine + + // move to function address + pAnim->scriptIndex++; + + // make function call + + // REMOVED BUGGY CODE + // pFunc is a function pointer that's part of a union and is assumed to be 32-bits. + // There is no known place where a function pointer is stored inside the animation + // scripts, something which wouldn't have worked anyway. Having played through the + // entire game, there hasn't been any occurence of this case, so just error out here + // in case we missed something (highly unlikely though) + error("ANI_CALL opcode encountered! Please report this error to the ScummVM team"); + //(*pAni[pAnim->scriptIndex].pFunc)(pAnim); + + // next opcode + pAnim->scriptIndex++; + + // go fetch a real image + break; + + case ANI_HIDE: // hide animated object + + MultiHideObject(pAnim->pObject); + + // next opcode + pAnim->scriptIndex++; + + // dont skip a sleep + return ScriptSleep; + + default: // must be an actual animation frame handle + + // set objects new animation frame + pAnim->pObject->hShape = FROM_LE_32(pAni[pAnim->scriptIndex].hFrame); + + // re-shape the object + MultiReshape(pAnim->pObject); + + // next opcode + pAnim->scriptIndex++; + + // dont skip a sleep + return ScriptSleep; + } + } +} + +/** + * Init a ANIM structure for single stepping through a animation script. + * @param pAnim Animation data structure + * @param pAniObj Object to animate + * @param hNewScript Script of multipart frames + * @param aniSpeed Sets speed of animation in frames + */ +void InitStepAnimScript(PANIM pAnim, OBJECT *pAniObj, SCNHANDLE hNewScript, int aniSpeed) { + OBJECT *pObj; // multi-object list iterator + + pAnim->aniDelta = 1; // will animate on next call to NextAnimRate + pAnim->pObject = pAniObj; // set object to animate + pAnim->hScript = hNewScript; // set animation script + pAnim->scriptIndex = 0; // start of script + pAnim->aniRate = aniSpeed; // set speed of animation + + // reset flip flags for the object - let the script do the flipping + for (pObj = pAniObj; pObj != NULL; pObj = pObj->pSlave) { + AnimateObjectFlags(pObj, pObj->flags & ~(DMA_FLIPH | DMA_FLIPV), + pObj->hImg); + } +} + +/** + * Execute the next command in a animation script. + * @param pAnim Animation data structure + */ +SCRIPTSTATE StepAnimScript(PANIM pAnim) { + SCRIPTSTATE state; + + if (--pAnim->aniDelta == 0) { + // re-init animation delta counter + pAnim->aniDelta = pAnim->aniRate; + + // move to next frame + while ((state = DoNextFrame(pAnim)) == ScriptNoSleep) + ; + + return state; + } + + // indicate calling task should sleep + return ScriptSleep; +} + +/** + * Skip the specified number of frames. + * @param pAnim Animation data structure + * @param numFrames Number of frames to skip + */ +void SkipFrames(PANIM pAnim, int numFrames) { + // get a pointer to the script + const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript); + + if (numFrames <= 0) + // do nothing + return; + + while (1) { // repeat until a real image + + switch ((int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)) { + case ANI_END: // end of animation script + // going off the end is probably a error + error("SkipFrames(): formally 'assert(0)!'"); + break; + + case ANI_JUMP: // do animation jump + + // move to jump address + pAnim->scriptIndex++; + + // jump to new frame position + pAnim->scriptIndex += (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + break; + + case ANI_HFLIP: // flip animated object horizontally + + // next opcode + pAnim->scriptIndex++; + + MultiHorizontalFlip(pAnim->pObject); + break; + + case ANI_VFLIP: // flip animated object vertically + + // next opcode + pAnim->scriptIndex++; + + MultiVerticalFlip(pAnim->pObject); + break; + + case ANI_HVFLIP: // flip animated object in both directions + + // next opcode + pAnim->scriptIndex++; + + MultiHorizontalFlip(pAnim->pObject); + MultiVerticalFlip(pAnim->pObject); + break; + + case ANI_ADJUSTX: // adjust animated object x animation point + + // move to x adjustment operand + pAnim->scriptIndex++; + + MultiAdjustXY(pAnim->pObject, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op), 0); + + // next opcode + pAnim->scriptIndex++; + break; + + case ANI_ADJUSTY: // adjust animated object y animation point + + // move to y adjustment operand + pAnim->scriptIndex++; + + MultiAdjustXY(pAnim->pObject, 0, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)); + + // next opcode + pAnim->scriptIndex++; + break; + + case ANI_ADJUSTXY: // adjust animated object x & y animation points + { + int x, y; + + // move to x adjustment operand + pAnim->scriptIndex++; + x = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + + // move to y adjustment operand + pAnim->scriptIndex++; + y = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op); + + MultiAdjustXY(pAnim->pObject, x, y); + + // next opcode + pAnim->scriptIndex++; + + break; + } + + case ANI_NOSLEEP: // do not sleep for this frame + + // next opcode + pAnim->scriptIndex++; + break; + + case ANI_CALL: // call routine + + // skip function address + pAnim->scriptIndex += 2; + break; + + case ANI_HIDE: // hide animated object + + // next opcode + pAnim->scriptIndex++; + break; + + default: // must be an actual animation frame handle + + // one less frame + if (numFrames-- > 0) { + // next opcode + pAnim->scriptIndex++; + } else { + // set objects new animation frame + pAnim->pObject->hShape = FROM_LE_32(pAni[pAnim->scriptIndex].hFrame); + + // re-shape the object + MultiReshape(pAnim->pObject); + + // we have skipped to the correct place + return; + } + break; + } + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/anim.h b/engines/tinsel/anim.h new file mode 100644 index 0000000000..a6ac574350 --- /dev/null +++ b/engines/tinsel/anim.h @@ -0,0 +1,72 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Object animation definitions + */ + +#ifndef TINSEL_ANIM_H // prevent multiple includes +#define TINSEL_ANIM_H + +#include "tinsel/dw.h" // for SCNHANDLE + +namespace Tinsel { + +struct OBJECT; + +/** animation structure */ +struct ANIM { + int aniRate; //!< animation speed + int aniDelta; //!< animation speed delta counter + OBJECT *pObject; //!< object to animate (assumed to be multi-part) + uint32 hScript; //!< animation script handle + int scriptIndex; //!< current position in animation script +}; +typedef ANIM *PANIM; + + +/*----------------------------------------------------------------------*\ +|* Anim Function Prototypes *| +\*----------------------------------------------------------------------*/ + +/** states for DoNextFrame */ +enum SCRIPTSTATE {ScriptFinished, ScriptNoSleep, ScriptSleep}; + +SCRIPTSTATE DoNextFrame( // Execute the next animation frame of a animation script + ANIM *pAnim); // animation data structure + +void InitStepAnimScript( // Init a ANIM struct for single stepping through a animation script + ANIM *pAnim, // animation data structure + OBJECT *pAniObj, // object to animate + SCNHANDLE hNewScript, // handle to script of multipart frames + int aniSpeed); // sets speed of animation in frames + +SCRIPTSTATE StepAnimScript( // Execute the next command in a animation script + ANIM *pAnim); // animation data structure + +void SkipFrames( // Skip the specified number of frames + ANIM *pAnim, // animation data structure + int numFrames); // number of frames to skip + +} // end of namespace Tinsel + +#endif // TINSEL_ANIM_H diff --git a/engines/tinsel/background.cpp b/engines/tinsel/background.cpp new file mode 100644 index 0000000000..05d36e9412 --- /dev/null +++ b/engines/tinsel/background.cpp @@ -0,0 +1,240 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Background handling code. + */ + +#include "tinsel/background.h" +#include "tinsel/cliprect.h" // object clip rect defs +#include "tinsel/graphics.h" +#include "tinsel/sched.h" // process sheduler defs +#include "tinsel/object.h" +#include "tinsel/pid.h" // process identifiers +#include "tinsel/tinsel.h" + +namespace Tinsel { + +// screen clipping rectangle +Common::Rect rcScreen(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + +// current background +BACKGND *pCurBgnd = NULL; + +// scroll flag - when set scrolling and velocity additions are paused +bool bNoScroll; + +/** + * Called to initialise a background. + * @param pBgnd Pointer to data struct for current background + */ + +void InitBackground(BACKGND *pBgnd) { + int i; // playfield counter + PPLAYFIELD pPlayfield; // pointer to current playfield + + // set current background + pCurBgnd = pBgnd; + + // init background sky colour + SetBgndColour(pBgnd->rgbSkyColour); + + // start of playfield array + pPlayfield = pBgnd->fieldArray; + + // for each background playfield + for (i = 0; i < pBgnd->numPlayfields; i++, pPlayfield++) { + // init playfield pos + pPlayfield->fieldX = intToFrac(pBgnd->ptInitWorld.x); + pPlayfield->fieldY = intToFrac(pBgnd->ptInitWorld.y); + + // no scrolling + pPlayfield->fieldXvel = intToFrac(0); + pPlayfield->fieldYvel = intToFrac(0); + + // clear playfield display list + pPlayfield->pDispList = NULL; + + // clear playfield moved flag + pPlayfield->bMoved = false; + } +} + +/** + * Sets the xy position of the specified playfield in the current background. + * @param which Which playfield + * @param newXpos New x position + * @param newYpos New y position + */ + +void PlayfieldSetPos(int which, int newXpos, int newYpos) { + PPLAYFIELD pPlayfield; // pointer to relavent playfield + + // make sure there is a background + assert(pCurBgnd != NULL); + + // make sure the playfield number is in range + assert(which >= 0 && which < pCurBgnd->numPlayfields); + + // get playfield pointer + pPlayfield = pCurBgnd->fieldArray + which; + + // set new integer position + pPlayfield->fieldX = intToFrac(newXpos); + pPlayfield->fieldY = intToFrac(newYpos); + + // set moved flag + pPlayfield->bMoved = true; +} + +/** + * Returns the xy position of the specified playfield in the current background. + * @param which Which playfield + * @param pXpos Returns current x position + * @param pYpos Returns current y position + */ + +void PlayfieldGetPos(int which, int *pXpos, int *pYpos) { + PPLAYFIELD pPlayfield; // pointer to relavent playfield + + // make sure there is a background + assert(pCurBgnd != NULL); + + // make sure the playfield number is in range + assert(which >= 0 && which < pCurBgnd->numPlayfields); + + // get playfield pointer + pPlayfield = pCurBgnd->fieldArray + which; + + // get current integer position + *pXpos = fracToInt(pPlayfield->fieldX); + *pYpos = fracToInt(pPlayfield->fieldY); +} + +/** + * Returns the display list for the specified playfield. + * @param which Which playfield + */ + +OBJECT *GetPlayfieldList(int which) { + PPLAYFIELD pPlayfield; // pointer to relavent playfield + + // make sure there is a background + assert(pCurBgnd != NULL); + + // make sure the playfield number is in range + assert(which >= 0 && which < pCurBgnd->numPlayfields); + + // get playfield pointer + pPlayfield = pCurBgnd->fieldArray + which; + + // return the display list pointer for this playfield + return (OBJECT *)&pPlayfield->pDispList; +} + +/** + * Draws all the playfield object lists for the current background. + * The playfield velocity is added to the playfield position in order + * to scroll each playfield before it is drawn. + */ + +void DrawBackgnd(void) { + int i; // playfield counter + PPLAYFIELD pPlay; // playfield pointer + int prevX, prevY; // save interger part of position + Common::Point ptWin; // window top left + + if (pCurBgnd == NULL) + return; // no current background + + // scroll each background playfield + for (i = 0; i < pCurBgnd->numPlayfields; i++) { + // get pointer to correct playfield + pPlay = pCurBgnd->fieldArray + i; + + // save integer part of position + prevX = fracToInt(pPlay->fieldX); + prevY = fracToInt(pPlay->fieldY); + + if (!bNoScroll) { + // update scrolling + pPlay->fieldX += pPlay->fieldXvel; + pPlay->fieldY += pPlay->fieldYvel; + + // convert fixed point window pos to a int + ptWin.x = fracToInt(pPlay->fieldX); + ptWin.y = fracToInt(pPlay->fieldY); + + // set the moved flag if the playfield has moved + if (prevX != ptWin.x || prevY != ptWin.y) + pPlay->bMoved = true; + } + + // sort the display list for this background - just in case somebody has changed object Z positions + SortObjectList((OBJECT *)&pPlay->pDispList); + + // generate clipping rects for all objects that have moved etc. + FindMovingObjects((OBJECT *)&pPlay->pDispList, &ptWin, + &pPlay->rcClip, bNoScroll, pPlay->bMoved); + + // clear playfield moved flag + pPlay->bMoved = false; + } + + // merge the clipping rectangles + MergeClipRect(); + + // redraw all playfields within the clipping rectangles + const RectList &clipRects = GetClipRects(); + for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) { + // clear the clip rectangle on the virtual screen + // for each background playfield + for (i = 0; i < pCurBgnd->numPlayfields; i++) { + Common::Rect rcPlayClip; // clip rect for this playfield + + // get pointer to correct playfield + pPlay = pCurBgnd->fieldArray + i; + + // convert fixed point window pos to a int + ptWin.x = fracToInt(pPlay->fieldX); + ptWin.y = fracToInt(pPlay->fieldY); + + if (IntersectRectangle(rcPlayClip, pPlay->rcClip, *r)) + // redraw all objects within this clipping rect + UpdateClipRect((OBJECT *)&pPlay->pDispList, + &ptWin, &rcPlayClip); + } + } + + // transfer any new palettes to the video DAC + PalettesToVideoDAC(); + + // update the screen within the clipping rectangles + for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) { + UpdateScreenRect(*r); + } + + // delete all the clipping rectangles + ResetClipRect(); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/background.h b/engines/tinsel/background.h new file mode 100644 index 0000000000..7409fd0785 --- /dev/null +++ b/engines/tinsel/background.h @@ -0,0 +1,165 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Data structures used for handling backgrounds + */ + +#ifndef TINSEL_BACKGND_H // prevent multiple includes +#define TINSEL_BACKGND_H + +#include "tinsel/dw.h" // for SCNHANDLE +#include "tinsel/palette.h" // palette definitions +#include "common/frac.h" +#include "common/rect.h" + +namespace Tinsel { + +struct OBJECT; + + +/** Scrolling padding. Needed because scroll process does not normally run on every frame */ +enum { + SCROLLX_PAD = 64, + SCROLLY_PAD = 64 +}; + +/** When module BLK_INFO list is this long, switch from a binary to linear search */ +#define LINEAR_SEARCH 5 + + +/** structure of each individual background block */ +struct BLOCK { + short blkWidth; //!< block width + short blkHeight; //!< block height + SCNHANDLE hBlkBits; //!< block bitmap handle +}; +typedef BLOCK *PBLOCK; + + +/** structure to define position of blocks, which block and which palette */ +struct BLK_INFO { + uint16 wBlkX; //!< x position of this block + uint16 wBlkY; //!< y position of this block + uint16 wBlkZ; //!< z position of this block + uint8 byBlkFlags; //!< block flags used for drawing object associated with this block + uint8 byBlkPal; //!< which palette - index into "blkPals" for this block + int32 blkIndex; //!< which block - index into "blocks" +}; +typedef BLK_INFO *PBLK_INFO; + + +/** background module structure - a module is a container for blocks */ +struct MODULE { + int modWidth; //!< width of module + int modHeight; //!< height of module + int numBlocks; //!< number of blocks in this module + BLOCK *blocks; //!< pointer to array of all blocks used by this module + uint32 *blkPals; //!< pointer to array of all palette handles used by the blocks in this module + BLK_INFO *blkInfo; //!< pointer to array of which block goes where + //!< NOTE: This array must be sorted on x position +}; +typedef MODULE *PMODULE; + + +/** + * background module node structure - links a playfields modules together + * and specifies each module position. It is done this way so that modules + * are position independent and can be reused within a playfield + */ +struct MOD_NODE { + MOD_NODE *pNext; //!< next module node + MODULE *pModule; //!< pointer to actual module definition + char *onDispList; //!< pointer to modules (block on object list) flags - should alloc 1 byte per block + Common::Point ptModPos; //!< module world start position +}; +typedef MOD_NODE *PMOD_NODE; + + +/** background playfield structure - a playfield is a container for modules */ +struct PLAYFIELD { + MOD_NODE *pModNode; //!< head of module node chain for this playfield + OBJECT *pDispList; //!< object display list for this playfield + frac_t fieldX; //!< current world x position of playfield + frac_t fieldY; //!< current world y position of playfield + frac_t fieldXvel; //!< current x velocity of playfield + frac_t fieldYvel; //!< current y velocity of playfield + Common::Rect rcClip; //!< clip rectangle for this playfield + bool bMoved; //!< set when playfield has moved +}; +typedef PLAYFIELD *PPLAYFIELD; + +/** multi-playfield background structure - a backgnd is a container of playfields */ +struct BACKGND { + COLORREF rgbSkyColour; //!< background sky colour + Common::Point ptInitWorld; //!< initial world position + Common::Rect rcScrollLimits; //!< scroll limits + int refreshRate; //!< background update process refresh rate + frac_t *pXscrollTable; //!< pointer to x direction scroll table for this background + frac_t *pYscrollTable; //!< pointer to y direction scroll table for this background + int numPlayfields; //!< number of playfields for this background + PLAYFIELD *fieldArray; //!< pointer to array of all playfields for this background + bool bAutoErase; //!< when set - screen is cleared before anything is plotted (unused) +}; + + +/** screen clipping rect */ +extern Common::Rect rcScreen; + +/** scroll flag - when set scrolling and velocity additions are paused */ +extern bool bNoScroll; + + +/*----------------------------------------------------------------------*\ +|* Background Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void InitBackground( // called to initialise a background + BACKGND *pBgnd); // pointer to data struct for current background + +void StopBgndScrolling(void); // Stops all background playfields from scrolling + +void PlayfieldSetPos( // Sets the xy position of the specified playfield in the current background + int which, // which playfield + int newXpos, // new x position + int newYpos); // new y position + +void PlayfieldGetPos( // Returns the xy position of the specified playfield in the current background + int which, // which playfield + int *pXpos, // returns current x position + int *pYpos); // returns current y position + +OBJECT *GetPlayfieldList( // Returns the display list for the specified playfield + int which); // which playfield + +void KillPlayfieldList( // Kills all the objects on the display list for the specified playfield + int which); // which playfield + +void DrawBackgnd(void); // Draws all playfields for the current background + +void RedrawBackgnd(void); // Completely redraws all the playfield object lists for the current background + +SCNHANDLE BackPal(void); + +} // end of namespace Tinsel + +#endif // TINSEL_BACKGND_H diff --git a/engines/tinsel/bg.cpp b/engines/tinsel/bg.cpp new file mode 100644 index 0000000000..cf7e18dc08 --- /dev/null +++ b/engines/tinsel/bg.cpp @@ -0,0 +1,189 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Plays the background film of a scene. + */ + +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/dw.h" +#include "tinsel/faders.h" +#include "tinsel/film.h" +#include "tinsel/font.h" +#include "tinsel/handle.h" +#include "tinsel/multiobj.h" +#include "tinsel/object.h" +#include "tinsel/pcode.h" // CONTROL_STARTOFF +#include "tinsel/pid.h" +#include "tinsel/sched.h" +#include "tinsel/timers.h" // For ONE_SECOND constant +#include "tinsel/tinlib.h" // For control() + +#include "common/util.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +static SCNHANDLE BackPalette = 0; // Background's palette +static OBJECT *pBG = 0; // The main picture's object. +static int BGspeed = 0; +static SCNHANDLE BgroundHandle = 0; // Current scene handle - stored in case of Save_Scene() +static bool DoFadeIn = false; +static ANIM thisAnim; // used by BGmainProcess() + +/** + * BackPal + */ +SCNHANDLE BackPal(void) { + return BackPalette; +} + +/** + * SetDoFadeIn +*/ +void SetDoFadeIn(bool tf) { + DoFadeIn = tf; +} + +/** + * Called before scene change. + */ +void DropBackground(void) { + pBG = NULL; // No background + BackPalette = 0; // No background palette +} + +/** + * Return the width of the current background. + */ +int BackgroundWidth(void) { + assert(pBG); + return MultiRightmost(pBG) + 1; +} + +/** + * Return the height of the current background. + */ +int BackgroundHeight(void) { + assert(pBG); + return MultiLowest(pBG) + 1; +} + +/** + * Run main animation that comprises the scene background. + */ +static void BGmainProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + const FREEL *pfr; + const MULTI_INIT *pmi; + + // get the stuff copied to process when it was created + pfr = (const FREEL *)ProcessGetParamsSelf(); + + if (pBG == NULL) { + /*** At start of scene ***/ + + // Get the MULTI_INIT structure + pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfr->mobj)); + + // Initialise and insert the object, and initialise its script. + pBG = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pBG); + InitStepAnimScript(&thisAnim, pBG, FROM_LE_32(pfr->script), BGspeed); + + if (DoFadeIn) { + FadeInFast(NULL); + DoFadeIn = false; + } + + while (StepAnimScript(&thisAnim) != ScriptFinished) + CORO_SLEEP(1); + + error("Background animation has finished!"); + } else { + // New background during scene + + // Just re-initialise the script. + InitStepAnimScript(&thisAnim, pBG, FROM_LE_32(pfr->script), BGspeed); + StepAnimScript(&thisAnim); + } + + CORO_END_CODE; +} + +/** + * setBackPal() + */ +void setBackPal(SCNHANDLE hPal) { + BackPalette = hPal; + + fettleFontPal(BackPalette); + CreateTranslucentPalette(BackPalette); +} + +void ChangePalette(SCNHANDLE hPal) { + SwapPalette(FindPalette(BackPalette), hPal); + + setBackPal(hPal); +} + +/** + * Given the scene background film, extracts the palette handle for + * everything else's use, then starts a display process for each reel + * in the film. + * @param bfilm Scene background film + */ +void startupBackground(SCNHANDLE bfilm) { + const FILM *pfilm; + PIMAGE pim; + + BgroundHandle = bfilm; // Save handle in case of Save_Scene() + + pim = GetImageFromFilm(bfilm, 0, NULL, NULL, &pfilm); + setBackPal(FROM_LE_32(pim->hImgPal)); + + // Extract the film speed + BGspeed = ONE_SECOND / FROM_LE_32(pfilm->frate); + + if (pBG == NULL) + control(CONTROL_STARTOFF); // New feature - start scene with control off + + // Start display process for each reel in the film + assert(FROM_LE_32(pfilm->numreels) == 1); // Multi-reeled backgrounds withdrawn + CoroutineInstall(PID_REEL, BGmainProcess, &pfilm->reels[0], sizeof(FREEL)); +} + +/** + * Return the current scene handle. + */ +SCNHANDLE GetBgroundHandle(void) { + return BgroundHandle; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/cliprect.cpp b/engines/tinsel/cliprect.cpp new file mode 100644 index 0000000000..632a84f723 --- /dev/null +++ b/engines/tinsel/cliprect.cpp @@ -0,0 +1,312 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * This file contains the clipping rectangle code. + */ + +#include "tinsel/cliprect.h" // object clip rect defs +#include "tinsel/graphics.h" // normal object drawing +#include "tinsel/object.h" +#include "tinsel/palette.h" + +namespace Tinsel { + +/** list of all clip rectangles */ +static RectList s_rectList; + +/** + * Resets the clipping rectangle allocator. + */ +void ResetClipRect(void) { + s_rectList.clear(); +} + +/** + * Allocate a clipping rectangle from the free list. + * @param pClip clip rectangle dimensions to allocate + */ +void AddClipRect(const Common::Rect &pClip) { + s_rectList.push_back(pClip); +} + +const RectList &GetClipRects() { + return s_rectList; +} + +/** + * Creates the intersection of two rectangles. + * Returns True if there is a intersection. + * @param pDest Pointer to destination rectangle that is to receive the intersection + * @param pSrc1 Pointer to a source rectangle + * @param pSrc2 Pointer to a source rectangle + */ +bool IntersectRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) { + pDest.left = (pSrc1.left > pSrc2.left) ? pSrc1.left : pSrc2.left; + pDest.top = (pSrc1.top > pSrc2.top) ? pSrc1.top : pSrc2.top; + pDest.right = (pSrc1.right < pSrc2.right) ? pSrc1.right : pSrc2.right; + pDest.bottom = (pSrc1.bottom < pSrc2.bottom) ? pSrc1.bottom : pSrc2.bottom; + + return (pDest.right > pDest.left && pDest.bottom > pDest.top); +} + +/** + * Creates the union of two rectangles. + * Returns True if there is a union. + * @param pDest destination rectangle that is to receive the new union + * @param pSrc1 a source rectangle + * @param pSrc2 a source rectangle + */ +bool UnionRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) { + pDest.left = (pSrc1.left < pSrc2.left) ? pSrc1.left : pSrc2.left; + pDest.top = (pSrc1.top < pSrc2.top) ? pSrc1.top : pSrc2.top; + pDest.right = (pSrc1.right > pSrc2.right) ? pSrc1.right : pSrc2.right; + pDest.bottom = (pSrc1.bottom > pSrc2.bottom) ? pSrc1.bottom : pSrc2.bottom; + + return (pDest.right > pDest.left && pDest.bottom > pDest.top); +} + +/** + * Check if the two rectangles are next to each other. + * @param pSrc1 a source rectangle + * @param pSrc2 a source rectangle + */ +static bool LooseIntersectRectangle(const Common::Rect &pSrc1, const Common::Rect &pSrc2) { + Common::Rect pDest; + + pDest.left = (pSrc1.left > pSrc2.left) ? pSrc1.left : pSrc2.left; + pDest.top = (pSrc1.top > pSrc2.top) ? pSrc1.top : pSrc2.top; + pDest.right = (pSrc1.right < pSrc2.right) ? pSrc1.right : pSrc2.right; + pDest.bottom = (pSrc1.bottom < pSrc2.bottom) ? pSrc1.bottom : pSrc2.bottom; + + return (pDest.right >= pDest.left && pDest.bottom >= pDest.top); +} + +/** + * Adds velocities and creates clipping rectangles for all the + * objects that have moved on the specified object list. + * @param pObjList Playfield display list to draw + * @param pWin Playfield window top left position + * @param pClip Playfield clipping rectangle + * @param bNoVelocity When reset, objects pos is updated with velocity + * @param bScrolled) When set, playfield has scrolled + */ +void FindMovingObjects(OBJECT *pObjList, Common::Point *pWin, Common::Rect *pClip, bool bNoVelocity, bool bScrolled) { + OBJECT *pObj; // object list traversal pointer + + for (pObj = pObjList->pNext; pObj != NULL; pObj = pObj->pNext) { + if (!bNoVelocity) { + // we want to add velocities to objects position + + if (bScrolled) { + // this playfield has scrolled + + // indicate change + pObj->flags |= DMA_CHANGED; + } + } + + if ((pObj->flags & DMA_CHANGED) || // object changed + HasPalMoved(pObj->pPal)) { // or palette moved + // object has changed in some way + + Common::Rect rcClip; // objects clipped bounding rectangle + Common::Rect rcObj; // objects bounding rectangle + + // calc intersection of objects previous bounding rectangle + // NOTE: previous position is in screen co-ords + if (IntersectRectangle(rcClip, pObj->rcPrev, *pClip)) { + // previous position is within clipping rect + AddClipRect(rcClip); + } + + // calc objects current bounding rectangle + if (pObj->flags & DMA_ABS) { + // object position is absolute + rcObj.left = fracToInt(pObj->xPos); + rcObj.top = fracToInt(pObj->yPos); + } else { + // object position is relative to window + rcObj.left = fracToInt(pObj->xPos) - pWin->x; + rcObj.top = fracToInt(pObj->yPos) - pWin->y; + } + rcObj.right = rcObj.left + pObj->width; + rcObj.bottom = rcObj.top + pObj->height; + + // calc intersection of object with clipping rect + if (IntersectRectangle(rcClip, rcObj, *pClip)) { + // current position is within clipping rect + AddClipRect(rcClip); + + // update previous position + pObj->rcPrev = rcClip; + } else { + // clear previous position + pObj->rcPrev = Common::Rect(); + } + + // clear changed flag + pObj->flags &= ~DMA_CHANGED; + } + } +} + +/** + * Merges any clipping rectangles that overlap to try and reduce + * the total number of clip rectangles. + */ +void MergeClipRect(void) { + if (s_rectList.size() > 1) { + RectList::iterator rOuter, rInner; + + for (rOuter = s_rectList.begin(); rOuter != s_rectList.end(); ++rOuter) { + rInner = rOuter; + while (++rInner != s_rectList.end()) { + + if (LooseIntersectRectangle(*rOuter, *rInner)) { + // these two rectangles overlap or + // are next to each other - merge them + + UnionRectangle(*rOuter, *rOuter, *rInner); + + // remove the inner rect from the list + s_rectList.erase(rInner); + + // move back to beginning of list + rInner = rOuter; + } + } + } + } +} + +/** + * Redraws all objects within this clipping rectangle. + * @param pObjList Object list to draw + * @param pWin Window top left position + * @param pClip Pointer to clip rectangle + */ +void UpdateClipRect(OBJECT *pObjList, Common::Point *pWin, Common::Rect *pClip) { + int x, y, right, bottom; // object corners + int hclip, vclip; // total size of object clipping + DRAWOBJECT currentObj; // filled in to draw the current object in list + OBJECT *pObj; // object list iterator + + // Initialise the fields of the drawing object to empty + memset(¤tObj, 0, sizeof(DRAWOBJECT)); + + for (pObj = pObjList->pNext; pObj != NULL; pObj = pObj->pNext) { + if (pObj->flags & DMA_ABS) { + // object position is absolute + x = fracToInt(pObj->xPos); + y = fracToInt(pObj->yPos); + } else { + // object position is relative to window + x = fracToInt(pObj->xPos) - pWin->x; + y = fracToInt(pObj->yPos) - pWin->y; + } + + // calc object right + right = x + pObj->width; + if (right < 0) + // totally clipped if negative + continue; + + // calc object bottom + bottom = y + pObj->height; + if (bottom < 0) + // totally clipped if negative + continue; + + // bottom clip = low right y - clip low right y + currentObj.botClip = bottom - pClip->bottom; + if (currentObj.botClip < 0) { + // negative - object is not clipped + currentObj.botClip = 0; + } + + // right clip = low right x - clip low right x + currentObj.rightClip = right - pClip->right; + if (currentObj.rightClip < 0) { + // negative - object is not clipped + currentObj.rightClip = 0; + } + + // top clip = clip top left y - top left y + currentObj.topClip = pClip->top - y; + if (currentObj.topClip < 0) { + // negative - object is not clipped + currentObj.topClip = 0; + } else { // clipped - adjust start position to top of clip rect + y = pClip->top; + } + + // left clip = clip top left x - top left x + currentObj.leftClip = pClip->left - x; + if (currentObj.leftClip < 0) { + // negative - object is not clipped + currentObj.leftClip = 0; + } + else + // NOTE: This else statement is disabled in tinsel v1 + { // clipped - adjust start position to left of clip rect + x = pClip->left; + } + + // calc object total horizontal clipping + hclip = currentObj.leftClip + currentObj.rightClip; + + // calc object total vertical clipping + vclip = currentObj.topClip + currentObj.botClip; + + if (hclip + vclip != 0) { + // object is clipped in some way + + if (pObj->width <= hclip) + // object totally clipped horizontally - ignore + continue; + + if (pObj->height <= vclip) + // object totally clipped vertically - ignore + continue; + + // set clip bit in objects flags + currentObj.flags = pObj->flags | DMA_CLIP; + } else { // object is not clipped - copy flags + currentObj.flags = pObj->flags; + } + + // copy objects properties to local object + currentObj.width = pObj->width; + currentObj.height = pObj->height; + currentObj.xPos = (short) x; + currentObj.yPos = (short) y; + currentObj.pPal = pObj->pPal; + currentObj.constant = pObj->constant; + currentObj.hBits = pObj->hBits; + + // draw the object + DrawObject(¤tObj); + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/cliprect.h b/engines/tinsel/cliprect.h new file mode 100644 index 0000000000..96b4b3314e --- /dev/null +++ b/engines/tinsel/cliprect.h @@ -0,0 +1,76 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Clipping rectangle defines + */ + +#ifndef TINSEL_CLIPRECT_H // prevent multiple includes +#define TINSEL_CLIPRECT_H + +#include "common/list.h" +#include "common/rect.h" + +namespace Tinsel { + +struct OBJECT; + +typedef Common::List<Common::Rect> RectList; + +/*----------------------------------------------------------------------*\ +|* Clip Rect Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void ResetClipRect(void); // Resets the clipping rectangle allocator + +void AddClipRect( // Allocate a clipping rectangle from the free list + const Common::Rect &pClip); // clip rectangle dimensions to allocate + +const RectList &GetClipRects(); + +bool IntersectRectangle( // Creates the intersection of two rectangles + Common::Rect &pDest, // pointer to destination rectangle that is to receive the intersection + const Common::Rect &pSrc1, // pointer to a source rectangle + const Common::Rect &pSrc2); // pointer to a source rectangle + +bool UnionRectangle( // Creates the union of two rectangles + Common::Rect &pDest, // destination rectangle that is to receive the new union + const Common::Rect &pSrc1, // a source rectangle + const Common::Rect &pSrc2); // a source rectangle + +void FindMovingObjects( // Creates clipping rectangles for all the objects that have moved on the specified object list + OBJECT *pObjList, // playfield display list to draw + Common::Point *pWin, // playfield window top left position + Common::Rect *pClip, // playfield clipping rectangle + bool bVelocity, // when set, objects pos is updated with velocity + bool bScrolled); // when set, playfield has scrolled + +void MergeClipRect(void); // Merges any clipping rectangles that overlap + +void UpdateClipRect( // Redraws all objects within this clipping rectangle + OBJECT *pObjList, // object list to draw + Common::Point *pWin, // window top left position + Common::Rect *pClip); // pointer to clip rectangle + +} // end of namespace Tinsel + +#endif // TINSEL_CLIPRECT_H diff --git a/engines/tinsel/config.cpp b/engines/tinsel/config.cpp new file mode 100644 index 0000000000..4c143f1b8d --- /dev/null +++ b/engines/tinsel/config.cpp @@ -0,0 +1,125 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * This file contains configuration functionality + */ + +//#define USE_3FLAGS 1 + +#include "tinsel/config.h" +#include "tinsel/dw.h" +#include "tinsel/sound.h" +#include "tinsel/music.h" + +#include "common/file.h" +#include "common/config-manager.h" + +#include "sound/mixer.h" + +namespace Tinsel { + +//----------------- GLOBAL GLOBAL DATA -------------------- + +int dclickSpeed = DOUBLE_CLICK_TIME; +int volMidi = MAXMIDIVOL; +int volSound = MAXSAMPVOL; +int volVoice = MAXSAMPVOL; +int speedText = DEFTEXTSPEED; +int bSubtitles = false; +int bSwapButtons = 0; +LANGUAGE language = TXT_ENGLISH; +int bAmerica = 0; + + +// Shouldn't really be here, but time is short... +bool bNoBlocking; + +/** + * WriteConfig() + */ + +void WriteConfig(void) { + ConfMan.setInt("dclick_speed", dclickSpeed); + ConfMan.setInt("music_volume", (volMidi * Audio::Mixer::kMaxChannelVolume) / MAXMIDIVOL); + ConfMan.setInt("sfx_volume", (volSound * Audio::Mixer::kMaxChannelVolume) / MAXSAMPVOL); + ConfMan.setInt("speech_volume", (volVoice * Audio::Mixer::kMaxChannelVolume) / MAXSAMPVOL); + ConfMan.setInt("talkspeed", (speedText * 255) / 100); + ConfMan.setBool("subtitles", bSubtitles); + //ConfMan.setBool("swap_buttons", bSwapButtons ? 1 : 0); + //ConfigData.language = language; // not necessary, as language has been set in the launcher + //ConfigData.bAmerica = bAmerica; // EN_USA / EN_GRB +} + +/*---------------------------------------------------------------------*\ +| ReadConfig() | +|-----------------------------------------------------------------------| +| +\*---------------------------------------------------------------------*/ +void ReadConfig(void) { + if (ConfMan.hasKey("dclick_speed")) + dclickSpeed = ConfMan.getInt("dclick_speed"); + + volMidi = (ConfMan.getInt("music_volume") * MAXMIDIVOL) / Audio::Mixer::kMaxChannelVolume; + volSound = (ConfMan.getInt("sfx_volume") * MAXSAMPVOL) / Audio::Mixer::kMaxChannelVolume; + volVoice = (ConfMan.getInt("speech_volume") * MAXSAMPVOL) / Audio::Mixer::kMaxChannelVolume; + + if (ConfMan.hasKey("talkspeed")) + speedText = (ConfMan.getInt("talkspeed") * 100) / 255; + if (ConfMan.hasKey("subtitles")) + bSubtitles = ConfMan.getBool("subtitles"); + + // FIXME: If JAPAN is set, subtitles are forced OFF in the original engine + + //bSwapButtons = ConfMan.getBool("swap_buttons") == 1 ? true : false; + //ConfigData.language = language; // not necessary, as language has been set in the launcher + //ConfigData.bAmerica = bAmerica; // EN_USA / EN_GRB + +// The flags here control how many country flags are displayed in one of the option dialogs. +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) + language = ConfigData.language; + #ifdef USE_3FLAGS + if (language == TXT_ENGLISH || language == TXT_ITALIAN) { + language = TXT_GERMAN; + bSubtitles = true; + } + #endif + #ifdef USE_4FLAGS + if (language == TXT_ENGLISH) { + language = TXT_GERMAN; + bSubtitles = true; + } + #endif +#else + language = TXT_ENGLISH; +#endif +} + +bool isJapanMode() { +#ifdef JAPAN + return true; +#else + return false; +#endif +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/config.h b/engines/tinsel/config.h new file mode 100644 index 0000000000..73cc411cb6 --- /dev/null +++ b/engines/tinsel/config.h @@ -0,0 +1,72 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_CONFIG_H +#define TINSEL_CONFIG_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +// None of these defined -> 1 language, in ENGLISH.TXT +//#define USE_5FLAGS 1 // All 5 flags +//#define USE_4FLAGS 1 // French, German, Italian, Spanish +//#define USE_3FLAGS 1 // French, German, Spanish + +// The Hebrew version appears to the software as being English +// but it needs to have subtitles on... +//#define HEBREW 1 + +//#define JAPAN 1 + + +// double click timer initial value +#define DOUBLE_CLICK_TIME 6 // 6 @ 18Hz = .33 sec + +#define DEFTEXTSPEED 0 + + +extern int dclickSpeed; +extern int volMidi; +extern int volSound; +extern int volVoice; +extern int speedText; +extern int bSubtitles; +extern int bSwapButtons; +extern LANGUAGE language; +extern int bAmerica; + +void WriteConfig(void); +void ReadConfig(void); + +extern bool isJapanMode(); + + +// Shouldn't really be here, but time is short... +extern bool bNoBlocking; + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/coroutine.h b/engines/tinsel/coroutine.h new file mode 100644 index 0000000000..4a8997fe00 --- /dev/null +++ b/engines/tinsel/coroutine.h @@ -0,0 +1,124 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_COROUTINE_H +#define TINSEL_COROUTINE_H + +#include "common/scummsys.h" + +namespace Tinsel { + +// The following is loosely based on <http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html>, +// Proper credit to Simon Tatham shall be given. + +/* + + * `ccr' macros for re-entrant coroutines. + + */ + +struct CoroBaseContext { + int _line; + int _sleep; + CoroBaseContext *_subctx; + CoroBaseContext() : _line(0), _sleep(0), _subctx(0) {} + ~CoroBaseContext() { delete _subctx; } +}; + +typedef CoroBaseContext *CoroContext; + + +/** + * Wrapper class which holds a pointer to a pointer to a CoroBaseContext. + * The interesting part is the destructor, which kills the context being held, + * but ONLY if the _sleep val of that context is zero. + */ +struct CoroContextHolder { + CoroContext &_ctx; + CoroContextHolder(CoroContext &ctx) : _ctx(ctx) {} + ~CoroContextHolder() { if (_ctx && _ctx->_sleep == 0) { delete _ctx; _ctx = 0; } } +}; + +#define CORO_PARAM CoroContext &coroParam + + +#define CORO_BEGIN_CONTEXT struct CoroContextTag : CoroBaseContext { int DUMMY +#define CORO_END_CONTEXT(x) } *x = (CoroContextTag *)coroParam + +#define CORO_BEGIN_CODE(x) \ + if (!x) {coroParam = x = new CoroContextTag();}\ + assert(coroParam);\ + assert(coroParam->_sleep >= 0);\ + coroParam->_sleep = 0;\ + CoroContextHolder tmpHolder(coroParam);\ + switch(coroParam->_line) { case 0:; + +#define CORO_END_CODE \ + } + +#define CORO_SLEEP(delay) \ + do {\ + coroParam->_line=__LINE__;\ + coroParam->_sleep=delay;\ + return; case __LINE__:;\ + } while (0) + +// Stop the currently running coroutine +#define CORO_KILL_SELF() do { coroParam->_sleep = -1; return; } while(0) + +//#define CORO_ABORT() do { delete (ctx); ctx = 0; } while (0) + +#define CORO_INVOKE_ARGS(subCoro, ARGS) \ + do {\ + coroParam->_line=__LINE__;\ + coroParam->_subctx = 0;\ + do {\ + subCoro ARGS;\ + if (!coroParam->_subctx) break;\ + coroParam->_sleep = coroParam->_subctx->_sleep;\ + return; case __LINE__:;\ + } while(1);\ + } while (0) + +#define CORO_SUBCTX coroParam->_subctx + +#define CORO_INVOKE_0(subCoroutine) \ + CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX)) +#define CORO_INVOKE_1(subCoroutine, a0) \ + CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0)) +#define CORO_INVOKE_2(subCoroutine, a0,a1) \ + CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1)) +#define CORO_INVOKE_3(subCoroutine, a0,a1,a2) \ + CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1,a2)) +#define CORO_INVOKE_4(subCoroutine, a0,a1,a2,a3) \ + CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1,a2,a3)) +#define CORO_INVOKE_5(subCoroutine, a0,a1,a2,a3,a4) \ + CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1,a2,a3,a4)) + + + +} // end of namespace Tinsel + +#endif // TINSEL_COROUTINE_H diff --git a/engines/tinsel/cursor.cpp b/engines/tinsel/cursor.cpp new file mode 100644 index 0000000000..087730c165 --- /dev/null +++ b/engines/tinsel/cursor.cpp @@ -0,0 +1,647 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Cursor and cursor trails. + */ + +#include "tinsel/cursor.h" + +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" // For EventsManager class +#include "tinsel/film.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/multiobj.h" // multi-part object defintions etc. +#include "tinsel/object.h" +#include "tinsel/pid.h" +#include "tinsel/sched.h" +#include "tinsel/timers.h" // For ONE_SECOND constant +#include "tinsel/tinlib.h" // resetidletime() +#include "tinsel/tinsel.h" // For engine access + + +namespace Tinsel { + +//----------------- LOCAL DEFINES -------------------- + +#define ITERATION_BASE FRAC_ONE +#define ITER_ACCELLERATION (10L << (FRAC_BITS - 4)) + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static OBJECT *McurObj = 0; // Main cursor object +static OBJECT *AcurObj = 0; // Auxiliary cursor object + +static ANIM McurAnim = {0,0,0,0,0}; // Main cursor animation structure +static ANIM AcurAnim = {0,0,0,0,0}; // Auxiliary cursor animation structure + +static bool bHiddenCursor = false; // Set when cursor is hidden +static bool bTempNoTrailers = false; // Set when cursor trails are hidden + +static bool bFrozenCursor = false; // Set when cursor position is frozen + +static frac_t IterationSize = 0; + +static SCNHANDLE CursorHandle = 0; // Handle to cursor reel data + +static int numTrails = 0; +static int nextTrail = 0; + +static bool bWhoa = false; // Set by DropCursor() at the end of a scene + // - causes cursor processes to do nothing + // Reset when main cursor has re-initialised + +static bool restart = false; // When main cursor has been bWhoa-ed, it waits + // for this to be set to true. + +static short ACoX = 0, ACoY = 0; // Auxillary cursor image's animation offsets + + + +#define MAX_TRAILERS 10 + +static struct { + + ANIM trailAnim; // Animation structure + OBJECT *trailObj; // This trailer's object + +} ntrailData [MAX_TRAILERS]; + +static int lastCursorX = 0, lastCursorY = 0; + + +//----------------- FORWARD REFERENCES -------------------- + +static void MoveCursor(void); + +/** + * Initialise and insert a cursor trail object, set its Z-pos, and hide + * it. Also initialise its animation script. + */ +static void InitCurTrailObj(int i, int x, int y) { + const FREEL *pfr; // pointer to reel + PIMAGE pim; // pointer to image + const MULTI_INIT *pmi; // MULTI_INIT structure + + const FILM *pfilm; + + if (!numTrails) + return; + + // Get rid of old object + if (ntrailData[i].trailObj != NULL) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); + + pim = GetImageFromFilm(CursorHandle, i+1, &pfr, &pmi, &pfilm);// Get pointer to image + assert(BackPal()); // No background palette + pim->hImgPal = TO_LE_32(BackPal()); + + // Initialise and insert the object, set its Z-pos, and hide it + ntrailData[i].trailObj = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); + MultiSetZPosition(ntrailData[i].trailObj, Z_CURSORTRAIL); + MultiSetAniXY(ntrailData[i].trailObj, x, y); + + // Initialise the animation script + InitStepAnimScript(&ntrailData[i].trailAnim, ntrailData[i].trailObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + StepAnimScript(&ntrailData[i].trailAnim); +} + +/** + * Get the cursor position from the mouse driver. + */ +static bool GetDriverPosition(int *x, int *y) { + Common::Point ptMouse = _vm->getMousePosition(); + *x = ptMouse.x; + *y = ptMouse.y; + + return(*x >= 0 && *x <= SCREEN_WIDTH-1 && + *y >= 0 && *y <= SCREEN_HEIGHT-1); +} + +/** + * Move the cursor relative to current position. + */ +void AdjustCursorXY(int deltaX, int deltaY) { + int x, y; + + if (deltaX || deltaY) { + if (GetDriverPosition(&x, &y)) + _vm->setMousePosition(Common::Point(x + deltaX, y + deltaY)); + } + MoveCursor(); +} + +/** + * Move the cursor to an absolute position. + */ +void SetCursorXY(int newx, int newy) { + int x, y; + int Loffset, Toffset; // Screen offset + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + newx -= Loffset; + newy -= Toffset; + + if (GetDriverPosition(&x, &y)) + _vm->setMousePosition(Common::Point(newx, newy)); + MoveCursor(); +} + +/** + * Move the cursor to a screen position. + */ +void SetCursorScreenXY(int newx, int newy) { + int x, y; + + if (GetDriverPosition(&x, &y)) + _vm->setMousePosition(Common::Point(newx, newy)); + MoveCursor(); +} + +/** + * Called by the world and his brother. + * Returns the cursor's animation position in (x,y). + * Returns false if there is no cursor object. + */ +bool GetCursorXYNoWait(int *x, int *y, bool absolute) { + if (McurObj == NULL) { + *x = *y = 0; + return false; + } + + GetAniPosition(McurObj, x, y); + + if (absolute) { + int Loffset, Toffset; // Screen offset + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + *x += Loffset; + *y += Toffset; + } + + return true; +} + +/** + * Called by the world and his brother. + * Returns the cursor's animation position. + * If called while there is no cursor object, the calling process ends + * up waiting until there is. + */ +void GetCursorXY(int *x, int *y, bool absolute) { + //while (McurObj == NULL) + // ProcessSleepSelf(); + assert(McurObj); + GetCursorXYNoWait(x, y, absolute); +} + +/** + * Re-initialise the main cursor to use the main cursor reel. + * Called from TINLIB.C to restore cursor after hiding it. + * Called from INVENTRY.C to restore cursor after customising it. + */ +void RestoreMainCursor(void) { + const FILM *pfilm; + + if (McurObj != NULL) { + pfilm = (const FILM *)LockMem(CursorHandle); + + InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfilm->reels->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + StepAnimScript(&McurAnim); + } + bHiddenCursor = false; + bFrozenCursor = false; +} + +/** + * Called from INVENTRY.C to customise the main cursor. + */ +void SetTempCursor(SCNHANDLE pScript) { + if (McurObj != NULL) + InitStepAnimScript(&McurAnim, McurObj, pScript, 2); +} + +/** + * Hide the cursor. + */ +void DwHideCursor(void) { + int i; + + bHiddenCursor = true; + + if (McurObj) + MultiHideObject(McurObj); + if (AcurObj) + MultiHideObject(AcurObj); + + for (i = 0; i < numTrails; i++) { + if (ntrailData[i].trailObj != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); + ntrailData[i].trailObj = NULL; + } + } +} + +/** + * Unhide the cursor. + */ +void UnHideCursor(void) { + bHiddenCursor = false; +} + +/** + * Freeze the cursor. + */ +void FreezeCursor(void) { + bFrozenCursor = true; +} + +/** + * HideCursorTrails + */ +void HideCursorTrails(void) { + int i; + + bTempNoTrailers = true; + + for (i = 0; i < numTrails; i++) { + if (ntrailData[i].trailObj != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); + ntrailData[i].trailObj = NULL; + } + } +} + +/** + * UnHideCursorTrails + */ +void UnHideCursorTrails(void) { + bTempNoTrailers = false; +} + +/** + * Get pointer to image from a film reel. And the rest. + */ +IMAGE *GetImageFromReel(const FREEL *pfr, const MULTI_INIT **ppmi) { + const MULTI_INIT *pmi; + const FRAME *pFrame; + + pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfr->mobj)); + if (ppmi) + *ppmi = pmi; + + pFrame = (const FRAME *)LockMem(FROM_LE_32(pmi->hMulFrame)); + + // get pointer to image + return (IMAGE *)LockMem(READ_LE_UINT32(pFrame)); +} + +/** + * Get pointer to image from a film. And the rest. + */ +IMAGE *GetImageFromFilm(SCNHANDLE hFilm, int reel, const FREEL **ppfr, const MULTI_INIT **ppmi, const FILM **ppfilm) { + const FILM *pfilm; + const FREEL *pfr; + + pfilm = (const FILM *)LockMem(hFilm); + if (ppfilm) + *ppfilm = pfilm; + + pfr = &pfilm->reels[reel]; + if (ppfr) + *ppfr = pfr; + + return GetImageFromReel(pfr, ppmi); +} + +/** + * Delete auxillary cursor. Restore animation offsets in the image. + */ +void DelAuxCursor(void) { + if (AcurObj != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), AcurObj); + AcurObj = NULL; + } +} + +/** + * Set auxillary cursor. + * Save animation offsets from the image if required. + */ +void SetAuxCursor(SCNHANDLE hFilm) { + PIMAGE pim; // Pointer to auxillary cursor's image + const FREEL *pfr; + const MULTI_INIT *pmi; + const FILM *pfilm; + int x, y; // Cursor position + + DelAuxCursor(); // Get rid of previous + + GetCursorXY(&x, &y, false); // Note: also waits for cursor to appear + + pim = GetImageFromFilm(hFilm, 0, &pfr, &pmi, &pfilm);// Get pointer to image + assert(BackPal()); // no background palette + pim->hImgPal = TO_LE_32(BackPal()); // Poke in the background palette + + ACoX = (short)(FROM_LE_16(pim->imgWidth)/2 - FROM_LE_16(pim->anioffX)); + ACoY = (short)(FROM_LE_16(pim->imgHeight)/2 - FROM_LE_16(pim->anioffY)); + + // Initialise and insert the auxillary cursor object + AcurObj = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), AcurObj); + + // Initialise the animation and set its position + InitStepAnimScript(&AcurAnim, AcurObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + MultiSetAniXY(AcurObj, x - ACoX, y - ACoY); + MultiSetZPosition(AcurObj, Z_ACURSOR); + + if (bHiddenCursor) + MultiHideObject(AcurObj); +} + +/** + * MoveCursor + */ +static void MoveCursor(void) { + int startX, startY; + Common::Point ptMouse; + frac_t newX, newY; + unsigned dir; + + // get cursors start animation position + GetCursorXYNoWait(&startX, &startY, false); + + // get mouse drivers current position + ptMouse = _vm->getMousePosition(); + + // convert to fixed point + newX = intToFrac(ptMouse.x); + newY = intToFrac(ptMouse.y); + + // modify mouse driver position depending on cursor keys + if ((dir = _vm->getKeyDirection()) != 0) { + if (dir & MSK_LEFT) + newX -= IterationSize; + + if (dir & MSK_RIGHT) + newX += IterationSize; + + if (dir & MSK_UP) + newY -= IterationSize; + + if (dir & MSK_DOWN) + newY += IterationSize; + + IterationSize += ITER_ACCELLERATION; + + // set new mouse driver position + _vm->setMousePosition(Common::Point(fracToInt(newX), fracToInt(newY))); + } else + + IterationSize = ITERATION_BASE; + + // get new mouse driver position - could have been modified + ptMouse = _vm->getMousePosition(); + + if (lastCursorX != ptMouse.x || lastCursorY != ptMouse.y) { + resetUserEventTime(); + + if (!bTempNoTrailers && !bHiddenCursor) { + InitCurTrailObj(nextTrail++, lastCursorX, lastCursorY); + if (nextTrail == numTrails) + nextTrail = 0; + } + } + + // adjust cursor to new mouse position + if (McurObj) + MultiSetAniXY(McurObj, ptMouse.x, ptMouse.y); + if (AcurObj != NULL) + MultiSetAniXY(AcurObj, ptMouse.x - ACoX, ptMouse.y - ACoY); + + if (InventoryActive() && McurObj) { + // Notify the inventory + Xmovement(ptMouse.x - startX); + Ymovement(ptMouse.y - startY); + } + + lastCursorX = ptMouse.x; + lastCursorY = ptMouse.y; +} + +/** + * Initialise cursor object. + */ +static void InitCurObj(void) { + const FILM *pfilm; + const FREEL *pfr; + const MULTI_INIT *pmi; + PIMAGE pim; + + pim = GetImageFromFilm(CursorHandle, 0, &pfr, &pmi, &pfilm);// Get pointer to image + assert(BackPal()); // no background palette + pim->hImgPal = TO_LE_32(BackPal()); +//--- + + AcurObj = NULL; // No auxillary cursor + + McurObj = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), McurObj); + + InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); +} + +/** + * Initialise the cursor position. + */ +static void InitCurPos(void) { + Common::Point ptMouse = _vm->getMousePosition(); + lastCursorX = ptMouse.x; + lastCursorY = ptMouse.y; + + MultiSetZPosition(McurObj, Z_CURSOR); + MoveCursor(); + MultiHideObject(McurObj); + + IterationSize = ITERATION_BASE; +} + +/** + * CursorStoppedCheck + */ +static void CursorStoppedCheck(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // If scene is closing down + if (bWhoa) { + // ...wait for next scene start-up + while (!restart) + CORO_SLEEP(1); + + // Re-initialise + InitCurObj(); + InitCurPos(); + InventoryIconCursor(); // May be holding something + + // Re-start the cursor trails + restart = false; // set all bits + bWhoa = false; + } + CORO_END_CODE; +} + +/** + * The main cursor process. + */ +void CursorProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + while (!CursorHandle || !BackPal()) + CORO_SLEEP(1); + + InitCurObj(); + InitCurPos(); + InventoryIconCursor(); // May be holding something + + bWhoa = false; + restart = false; + + while (1) { + // allow rescheduling + CORO_SLEEP(1); + + // Stop/start between scenes + CORO_INVOKE_0(CursorStoppedCheck); + + // Step the animation script(s) + StepAnimScript(&McurAnim); + if (AcurObj != NULL) + StepAnimScript(&AcurAnim); + for (int i = 0; i < numTrails; i++) { + if (ntrailData[i].trailObj != NULL) { + if (StepAnimScript(&ntrailData[i].trailAnim) == ScriptFinished) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); + ntrailData[i].trailObj = NULL; + } + } + } + + // Move the cursor as appropriate + if (!bFrozenCursor) + MoveCursor(); + + // If the cursor should be hidden... + if (bHiddenCursor) { + // ...hide the cursor object(s) + MultiHideObject(McurObj); + if (AcurObj) + MultiHideObject(AcurObj); + + for (int i = 0; i < numTrails; i++) { + if (ntrailData[i].trailObj != NULL) + MultiHideObject(ntrailData[i].trailObj); + } + + // Wait 'til cursor is again required. + while (bHiddenCursor) { + CORO_SLEEP(1); + + // Stop/start between scenes + CORO_INVOKE_0(CursorStoppedCheck); + } + } + } + CORO_END_CODE; +} + +/** + * Called from dec_cursor() Glitter function. + * Register the handle to cursor reel data. + */ +void DwInitCursor(SCNHANDLE bfilm) { + const FILM *pfilm; + + CursorHandle = bfilm; + + pfilm = (const FILM *)LockMem(CursorHandle); + numTrails = FROM_LE_32(pfilm->numreels) - 1; + + assert(numTrails <= MAX_TRAILERS); +} + +/** + * DropCursor is called when a scene is closing down. + */ +void DropCursor(void) { + AcurObj = NULL; // No auxillary cursor + McurObj = NULL; // No cursor object (imminently deleted elsewhere) + bHiddenCursor = false; // Not hidden in next scene + bTempNoTrailers = false; // Trailers not hidden in next scene + bWhoa = true; // Suspend cursor processes + + for (int i = 0; i < numTrails; i++) { + if (ntrailData[i].trailObj != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj); + ntrailData[i].trailObj = NULL; + } + } +} + +/** + * RestartCursor is called when a new scene is starting up. + */ +void RestartCursor(void) { + restart = true; // Get the main cursor to re-initialise +} + +/** + * Called when restarting the game, ensures correct re-start with NULL + * pointers etc. + */ +void RebootCursor(void) { + McurObj = AcurObj = NULL; + for (int i = 0; i < MAX_TRAILERS; i++) + ntrailData[i].trailObj = NULL; + + bHiddenCursor = bTempNoTrailers = bFrozenCursor = false; + + CursorHandle = 0; + + bWhoa = false; + restart = false; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/cursor.h b/engines/tinsel/cursor.h new file mode 100644 index 0000000000..15349dda26 --- /dev/null +++ b/engines/tinsel/cursor.h @@ -0,0 +1,56 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Clipping rectangle defines + */ + +#ifndef TINSEL_CURSOR_H // prevent multiple includes +#define TINSEL_CURSOR_H + +#include "tinsel/dw.h" // for SCNHANDLE + +namespace Tinsel { + +void AdjustCursorXY(int deltaX, int deltaY); +void SetCursorXY(int x, int y); +void SetCursorScreenXY(int newx, int newy); +void GetCursorXY(int *x, int *y, bool absolute); +bool GetCursorXYNoWait(int *x, int *y, bool absolute); + +void RestoreMainCursor(void); +void SetTempCursor(SCNHANDLE pScript); +void DwHideCursor(void); +void UnHideCursor(void); +void FreezeCursor(void); +void HideCursorTrails(void); +void UnHideCursorTrails(void); +void DelAuxCursor(void); +void SetAuxCursor(SCNHANDLE hFilm); +void DwInitCursor(SCNHANDLE bfilm); +void DropCursor(void); +void RestartCursor(void); +void RebootCursor(void); + +} // end of namespace Tinsel + +#endif // TINSEL_CURSOR_H diff --git a/engines/tinsel/debugger.cpp b/engines/tinsel/debugger.cpp new file mode 100644 index 0000000000..dc37e6a9a1 --- /dev/null +++ b/engines/tinsel/debugger.cpp @@ -0,0 +1,162 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#include "tinsel/tinsel.h" +#include "tinsel/debugger.h" +#include "tinsel/inventory.h" +#include "tinsel/pcode.h" +#include "tinsel/scene.h" +#include "tinsel/sound.h" +#include "tinsel/music.h" +#include "tinsel/font.h" +#include "tinsel/strres.h" + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// In PDISPLAY.CPP +extern void TogglePathDisplay(void); +// In tinsel.cpp +extern void SetNewScene(SCNHANDLE scene, int entrance, int transition); +// In scene.cpp +extern SCNHANDLE GetSceneHandle(void); + +//----------------- SUPPORT FUNCTIONS --------------------- + +//static +int strToInt(const char *s) { + if (!*s) + // No string at all + return 0; + else if (toupper(s[strlen(s) - 1]) != 'H') + // Standard decimal string + return atoi(s); + + // Hexadecimal string + uint tmp; + sscanf(s, "%xh", &tmp); + return (int)tmp; +} + +//----------------- CONSOLE CLASS --------------------- + +Console::Console() : GUI::Debugger() { + DCmd_Register("item", WRAP_METHOD(Console, cmd_item)); + DCmd_Register("scene", WRAP_METHOD(Console, cmd_scene)); + DCmd_Register("music", WRAP_METHOD(Console, cmd_music)); + DCmd_Register("sound", WRAP_METHOD(Console, cmd_sound)); + DCmd_Register("string", WRAP_METHOD(Console, cmd_string)); +} + +Console::~Console() { +} + +bool Console::cmd_item(int argc, const char **argv) { + if (argc < 2) { + DebugPrintf("%s item_number\n", argv[0]); + DebugPrintf("Sets the currently active 'held' item\n"); + return true; + } + + HoldItem(INV_NOICON); + HoldItem(strToInt(argv[1])); + return false; +} + +bool Console::cmd_scene(int argc, const char **argv) { + if (argc < 1 || argc > 3) { + DebugPrintf("%s [scene_number [entry number]]\n", argv[0]); + DebugPrintf("If no parameters are given, prints the current scene.\n"); + DebugPrintf("Otherwise changes to the specified scene number. Entry number defaults to 1 if none provided\n"); + return true; + } + + if (argc == 1) { + DebugPrintf("Current scene is %d\n", GetSceneHandle() >> SCNHANDLE_SHIFT); + return true; + } + + uint32 sceneNumber = (uint32)strToInt(argv[1]) << SCNHANDLE_SHIFT; + int entryNumber = (argc >= 3) ? strToInt(argv[2]) : 1; + + SetNewScene(sceneNumber, entryNumber, TRANS_CUT); + return false; +} + +bool Console::cmd_music(int argc, const char **argv) { + if (argc < 2) { + DebugPrintf("%s track_number or %s -offset\n", argv[0], argv[0]); + DebugPrintf("Plays the MIDI track number provided, or the offset inside midi.dat\n"); + DebugPrintf("A positive number signifies a track number, whereas a negative signifies an offset\n"); + return true; + } + + int param = strToInt(argv[1]); + if (param == 0) { + DebugPrintf("Track number/offset can't be 0!\n", argv[0]); + } else if (param > 0) { + // Track provided + PlayMidiSequence(GetTrackOffset(param - 1), false); + } else if (param < 0) { + // Offset provided + param = param * -1; + PlayMidiSequence(param, false); + } + return true; +} + +bool Console::cmd_sound(int argc, const char **argv) { + if (argc < 2) { + DebugPrintf("%s id\n", argv[0]); + DebugPrintf("Plays the sound with the given ID\n"); + return true; + } + + int id = strToInt(argv[1]); + if (_vm->_sound->sampleExists(id)) + _vm->_sound->playSample(id, Audio::Mixer::kSpeechSoundType); + else + DebugPrintf("Sample %d does not exist!\n", id); + + return true; +} + +bool Console::cmd_string(int argc, const char **argv) { + if (argc < 2) { + DebugPrintf("%s id\n", argv[0]); + DebugPrintf("Prints the string with the given ID\n"); + return true; + } + + char tmp[TBUFSZ]; + int id = strToInt(argv[1]); + LoadStringRes(id, tmp, TBUFSZ); + DebugPrintf("%s\n", tmp); + + return true; +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/debugger.h b/engines/tinsel/debugger.h new file mode 100644 index 0000000000..219bc71224 --- /dev/null +++ b/engines/tinsel/debugger.h @@ -0,0 +1,49 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_DEBUGGER_H +#define TINSEL_DEBUGGER_H + +#include "gui/debugger.h" + +namespace Tinsel { + +class TinselEngine; + +class Console: public GUI::Debugger { +protected: + bool cmd_item(int argc, const char **argv); + bool cmd_scene(int argc, const char **argv); + bool cmd_music(int argc, const char **argv); + bool cmd_sound(int argc, const char **argv); + bool cmd_string(int argc, const char **argv); +public: + Console(); + virtual ~Console(void); +}; + +} // End of namespace Tinsel + +#endif diff --git a/engines/tinsel/detection.cpp b/engines/tinsel/detection.cpp new file mode 100644 index 0000000000..a638dde2c5 --- /dev/null +++ b/engines/tinsel/detection.cpp @@ -0,0 +1,278 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#include "base/plugins.h" + +#include "common/advancedDetector.h" +#include "common/file.h" + +#include "tinsel/tinsel.h" + + +namespace Tinsel { + +struct TinselGameDescription { + Common::ADGameDescription desc; + + int gameID; + int gameType; + uint32 features; + uint16 version; +}; + +uint32 TinselEngine::getGameID() const { + return _gameDescription->gameID; +} + +uint32 TinselEngine::getFeatures() const { + return _gameDescription->features; +} + +Common::Language TinselEngine::getLanguage() const { + return _gameDescription->desc.language; +} + +Common::Platform TinselEngine::getPlatform() const { + return _gameDescription->desc.platform; +} + +uint16 TinselEngine::getVersion() const { + return _gameDescription->version; +} + +} + +static const PlainGameDescriptor tinselGames[] = { + {"tinsel", "Tinsel engine game"}, + {"dw", "Discworld"}, + {"dw2", "Discworld 2: Mortality Bytes!"}, + {0, 0} +}; + + +namespace Tinsel { + +static const TinselGameDescription gameDescriptions[] = { + + // Note: versions with *.gra files use tinsel v1 (28/2/1995), whereas + // versions with *.scn files tinsel v2 (7/5/1995) + // Update: this is not entirely true, there were some versions released + // with *.gra files and used tinsel v2 + + { + { // This version has *.gra files but uses tinsel v2 + "dw", + "Floppy", + AD_ENTRY1s("dw.gra", "c8808ccd988d603dd35dff42013ae7fd", 781656), + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_DW1, + 0, + GF_FLOPPY, + TINSEL_V2, + }, + + { // English CD v1. This version has *.gra files but uses tinsel v2 + { + "dw", + "CD", + { + {"dw.gra", 0, "c8808ccd988d603dd35dff42013ae7fd", 781656}, + {"english.smp", 0, NULL, -1}, + {NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_DW1, + 0, + GF_CD, + TINSEL_V2, + }, + + { // English CD v2 + { + "dw", + "CD", + { + {"dw.scn", 0, "70955425870c7720d6eebed903b2ef41", 776188}, + {"english.smp", 0, NULL, -1}, + {NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_DW1, + 0, + GF_CD | GF_SCNFILES, + TINSEL_V2, + }, + +#if 0 + { // English Saturn CD + { + "dw", + "CD", + { + {"dw.scn", 0, "6803f293c88758057cc685b9437f7637", 382248}, + {"english.smp", 0, NULL, -1}, + {NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_DW1, + 0, + GF_CD, + TINSEL_V2, + }, +#endif + + { // Demo from http://www.adventure-treff.de/specials/dl_demos.php + { + "dw", + "Demo", + AD_ENTRY1s("dw.gra", "ce1b57761ba705221bcf70955b827b97", 441192), + //AD_ENTRY1s("dw.scn", "ccd72f02183d0e96b6e7d8df9492cda8", 23308), + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_DEMO + }, + GID_DW1, + 0, + GF_DEMO, + TINSEL_V1, + }, + + { // German CD re-release "Neon Edition" + // Note: This release has ENGLISH.TXT (with german content) instead of GERMAN.TXT + { + "dw", + "CD", + AD_ENTRY1s("dw.scn", "6182c7986eaec893c62fb6ea13a9f225", 774556), + Common::DE_DEU, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GID_DW1, + 0, + GF_CD | GF_SCNFILES, + TINSEL_V2, + }, + + { AD_TABLE_END_MARKER, 0, 0, 0, 0 } +}; + +/** + * The fallback game descriptor used by the Tinsel engine's fallbackDetector. + * Contents of this struct are to be overwritten by the fallbackDetector. + */ +static TinselGameDescription g_fallbackDesc = { + { + "", + "", + AD_ENTRY1(0, 0), // This should always be AD_ENTRY1(0, 0) in the fallback descriptor + Common::UNK_LANG, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + 0, + 0, + 0, + 0, +}; + +} // End of namespace Tinsel + +static const Common::ADParams detectionParams = { + // Pointer to ADGameDescription or its superset structure + (const byte *)Tinsel::gameDescriptions, + // Size of that superset structure + sizeof(Tinsel::TinselGameDescription), + // Number of bytes to compute MD5 sum for + 5000, + // List of all engine targets + tinselGames, + // Structure for autoupgrading obsolete targets + 0, + // Name of single gameid (optional) + "tinsel", + // List of files for file-based fallback detection (optional) + 0, + // Flags + 0 +}; + +class TinselMetaEngine : public Common::AdvancedMetaEngine { +public: + TinselMetaEngine() : Common::AdvancedMetaEngine(detectionParams) {} + + virtual const char *getName() const { + return "Tinsel Engine"; + } + + virtual const char *getCopyright() const { + return "Tinsel Engine"; + } + + virtual bool createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const; + + const Common::ADGameDescription *fallbackDetect(const FSList *fslist) const; + +}; + +bool TinselMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const { + const Tinsel::TinselGameDescription *gd = (const Tinsel::TinselGameDescription *)desc; + if (gd) { + *engine = new Tinsel::TinselEngine(syst, gd); + } + return gd != 0; +} + +const Common::ADGameDescription *TinselMetaEngine::fallbackDetect(const FSList *fslist) const { + // Set the default values for the fallback descriptor's ADGameDescription part. + Tinsel::g_fallbackDesc.desc.language = Common::UNK_LANG; + Tinsel::g_fallbackDesc.desc.platform = Common::kPlatformPC; + Tinsel::g_fallbackDesc.desc.flags = Common::ADGF_NO_FLAGS; + + // Set default values for the fallback descriptor's TinselGameDescription part. + Tinsel::g_fallbackDesc.gameID = 0; + Tinsel::g_fallbackDesc.features = 0; + Tinsel::g_fallbackDesc.version = 0; + + //return (const Common::ADGameDescription *)&Tinsel::g_fallbackDesc; + return NULL; +} + +#if PLUGIN_ENABLED_DYNAMIC(TINSEL) + REGISTER_PLUGIN_DYNAMIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine); +#else + REGISTER_PLUGIN_STATIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine); +#endif diff --git a/engines/tinsel/dw.h b/engines/tinsel/dw.h new file mode 100644 index 0000000000..d14dd43fa2 --- /dev/null +++ b/engines/tinsel/dw.h @@ -0,0 +1,119 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_DW_H +#define TINSEL_DW_H + +#include "common/scummsys.h" +#include "common/endian.h" + +namespace Tinsel { + +/** scene handle data type */ +typedef uint32 SCNHANDLE; + +/** polygon handle */ +typedef int HPOLYGON; + + +#define EOS_CHAR '\0' // string terminator +#define LF_CHAR '\x0a' // line feed + +// file names +#define SAMPLE_FILE "english.smp" // all samples +#define SAMPLE_INDEX "english.idx" // sample index filename +#define MIDI_FILE "midi.dat" // all MIDI sequences +#define INDEX_FILENAME "index" // name of index file + +#define SCNHANDLE_SHIFT 23 // amount to shift scene handles by +#define NO_SCNHANDLES 300 // number of memory handles for scenes +#define MASTER_SCNHANDLE (0 << SCNHANDLE_SHIFT) // master scene memory handle + +// the minimum value a integer number can have +#define MIN_INT (1 << (8*sizeof(int) - 1)) +#define MIN_INT16 (-32767) + +// the maximum value a integer number can have +#define MAX_INT (~MIN_INT) + +// TODO: v1->v2 scene files +#ifdef FILE_SPLIT +// each scene is split into 2 files +#define INV_OBJ_SCNHANDLE (2 << SCNHANDLE_SHIFT) // inventory object handle (if there are inventory objects) +#else +#define INV_OBJ_SCNHANDLE (1 << SCNHANDLE_SHIFT) // inventory object handle (if there are inventory objects) +#endif + + +#define FIELD_WORLD 0 +#define FIELD_STATUS 1 + + + + +// We don't set the Z position for print and talk text +// i.e. it gets a Z position of 0 + +#define Z_INV_BRECT 10 // Inventory background rectangle +#define Z_INV_MFRAME 15 // Inventory window frame +#define Z_INV_HTEXT 15 // Inventory heading text +#define Z_INV_ICONS 16 // Icons in inventory +#define Z_INV_ITEXT 995 // Icon text + +#define Z_INV_RFRAME 22 // Re-sizing frame + +#define Z_CURSOR 1000 // Cursor +#define Z_CURSORTRAIL 999 // Cursor trails +#define Z_ACURSOR 990 // Auxillary cursor + +#define Z_TAG_TEXT 995 // In front of auxillary cursor + +#define Z_MDGROOVE 20 +#define Z_MDSLIDER 21 + +#define Z_TOPPLAY 100 + +#define Z_TOPW_TEXT Z_TAG_TEXT + +// Started a collection of assorted maximum numbers here: +#define MAX_MOVERS 6 // Moving actors using path system +#define MAX_SAVED_ACTORS 32 // Saved 'Normal' actors +#define MAX_SAVED_ALIVES 512 // Saves actors'lives + +// Legal non-existant entrance number for LoadScene() +#define NO_ENTRY_NUM (-3458) // Magic unlikely number + + +#define SAMPLETIMEOUT (15*ONE_SECOND) + +// Language for the resource strings +enum LANGUAGE { + TXT_ENGLISH, TXT_FRENCH, TXT_GERMAN, + TXT_ITALIAN, TXT_SPANISH +}; + +} // end of namespace Tinsel + +#endif // TINSEL_DW_H diff --git a/engines/tinsel/effect.cpp b/engines/tinsel/effect.cpp new file mode 100644 index 0000000000..825825ccec --- /dev/null +++ b/engines/tinsel/effect.cpp @@ -0,0 +1,134 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +// Handles effect polygons. +// +// EffectPolyProcess() monitors triggering of effect code (i.e. a moving +// actor entering an effect polygon). +// EffectProcess() runs the appropriate effect code. +// +// NOTE: Currently will only run one effect process at a time, i.e. +// effect polygons will not currently nest. It won't be very difficult +// to fix this if required. + +#include "tinsel/actors.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/pid.h" +#include "tinsel/pcode.h" // LEAD_ACTOR +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" + + +namespace Tinsel { + +struct EP_INIT { + HPOLYGON hEpoly; + PMACTOR pActor; + int index; +}; + +/** + * Runs an effect polygon's Glitter code with ENTER event, waits for the + * actor to leave that polygon. Then runs the polygon's Glitter code + * with LEAVE event. + */ +static void EffectProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + EP_INIT *to = (EP_INIT *)ProcessGetParamsSelf(); // get the stuff copied to process when it was created + + CORO_BEGIN_CODE(_ctx); + + int x, y; // Lead actor position + + // Run effect poly enter script + effRunPolyTinselCode(to->hEpoly, ENTER, to->pActor->actorID); + + do { + CORO_SLEEP(1); + GetMActorPosition(to->pActor, &x, &y); + } while (InPolygon(x, y, EFFECT) == to->hEpoly); + + // Run effect poly leave script + effRunPolyTinselCode(to->hEpoly, LEAVE, to->pActor->actorID); + + SetMAinEffectPoly(to->index, false); + + CORO_END_CODE; +} + +/** + * If the actor was not already in an effect polygon, checks to see if + * it has just entered one. If it has, a process is started up to run + * the polygon's Glitter code. + */ +static void FettleEffectPolys(int x, int y, int index, PMACTOR pActor) { + HPOLYGON hPoly; + EP_INIT epi; + + // If just entered an effect polygon, the effect should be triggered. + if (!IsMAinEffectPoly(index)) { + hPoly = InPolygon(x, y, EFFECT); + if (hPoly != NOPOLY) { + //Just entered effect polygon + SetMAinEffectPoly(index, true); + + epi.hEpoly = hPoly; + epi.pActor = pActor; + epi.index = index; + CoroutineInstall(PID_TCODE, EffectProcess, &epi, sizeof(epi)); + } + } +} + +/** + * Just calls FettleEffectPolys() every clock tick. + */ +void EffectPolyProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + while (1) { + for (int i = 0; i < MAX_MOVERS; i++) { + PMACTOR pActor = GetLiveMover(i); + if (pActor != NULL) { + int x, y; + GetMActorPosition(pActor, &x, &y); + FettleEffectPolys(x, y, i, pActor); + } + } + + CORO_SLEEP(1); // allow re-scheduling + } + CORO_END_CODE; +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/events.cpp b/engines/tinsel/events.cpp new file mode 100644 index 0000000000..8064bdd3f3 --- /dev/null +++ b/engines/tinsel/events.cpp @@ -0,0 +1,439 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Main purpose is to process user events. + * Also provides a couple of utility functions. + */ + +#include "tinsel/actors.h" +#include "tinsel/config.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/handle.h" // For LockMem() +#include "tinsel/inventory.h" +#include "tinsel/move.h" // For walking lead actor +#include "tinsel/pcode.h" // For Interpret() +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" // For walking lead actor +#include "tinsel/sched.h" +#include "tinsel/scroll.h" // For DontScrollCursor() +#include "tinsel/timers.h" // DwGetCurrentTime() +#include "tinsel/tinlib.h" // For control() +#include "tinsel/token.h" + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in PDISPLAY.C +extern int GetTaggedActor(void); +extern HPOLYGON GetTaggedPoly(void); + + +//----------------- EXTERNAL GLOBAL DATA --------------------- + +extern bool bEnableF1; + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int userEvents = 0; // Whenever a button or a key comes in +static uint32 lastUserEvent = 0; // Time it hapenned +static int butEvents = 0; // Single or double, left or right. Or escape key. +static int escEvents = 0; // Escape key + + +static int eCount = 0; + +/** + * Gets called before each schedule, only 1 user action per schedule + * is allowed. + */ +void ResetEcount(void) { + eCount = 0; +} + + +void IncUserEvents(void) { + userEvents++; + lastUserEvent = DwGetCurrentTime(); +} + +/** + * If this is a single click, wait to check it's not the first half of a + * double click. + * If this is a double click, the process from the waiting single click + * gets killed. + */ +void AllowDclick(CORO_PARAM, BUTEVENT be) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + if (be == BE_SLEFT) { + GetToken(TOKEN_LEFT_BUT); + CORO_SLEEP(dclickSpeed+1); + FreeToken(TOKEN_LEFT_BUT); + + // Prevent activation of 2 events on the same tick + if (++eCount != 1) + CORO_KILL_SELF(); + + break; + + } else if (be == BE_DLEFT) { + GetToken(TOKEN_LEFT_BUT); + FreeToken(TOKEN_LEFT_BUT); + } + CORO_END_CODE; +} + +/** + * Take control from player, if the player has it. + * Return TRUE if control taken, FALSE if not. + */ + +bool GetControl(int param) { + if (TestToken(TOKEN_CONTROL)) { + control(param); + return true; + } else + return false; +} + +struct TP_INIT { + HPOLYGON hPoly; // Polygon + USER_EVENT event; // Trigerring event + BUTEVENT bev; // To allow for double clicks + bool take_control; // Set if control should be taken + // while code is running. + int actor; +}; + +/** + * Runs glitter code associated with a polygon. + */ +static void PolyTinselProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + PINT_CONTEXT pic; + bool took_control; // Set if this function takes control + CORO_END_CONTEXT(_ctx); + + TP_INIT *to = (TP_INIT *)ProcessGetParamsSelf(); // get the stuff copied to process when it was created + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(AllowDclick, to->bev); // May kill us if single click + + // Control may have gone off during AllowDclick() + if (!TestToken(TOKEN_CONTROL) + && (to->event == WALKTO || to->event == ACTION || to->event == LOOK)) + CORO_KILL_SELF(); + + // Take control, if requested + if (to->take_control) + _ctx->took_control = GetControl(CONTROL_OFF); + else + _ctx->took_control = false; + + // Hide conversation if appropriate + if (to->event == CONVERSE) + convHide(true); + + // Run the code + _ctx->pic = InitInterpretContext(GS_POLYGON, getPolyScript(to->hPoly), to->event, to->hPoly, to->actor, NULL); + CORO_INVOKE_1(Interpret, _ctx->pic); + + // Free control if we took it + if (_ctx->took_control) + control(CONTROL_ON); + + // Restore conv window if applicable + if (to->event == CONVERSE) + convHide(false); + + CORO_END_CODE; +} + +/** + * Runs glitter code associated with a polygon. + */ +void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc) { + TP_INIT to = { hPoly, event, be, tc, 0 }; + + CoroutineInstall(PID_TCODE, PolyTinselProcess, &to, sizeof(to)); +} + +void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor) { + TP_INIT to = { hPoly, event, BE_NONE, false, actor }; + + CoroutineInstall(PID_TCODE, PolyTinselProcess, &to, sizeof(to)); +} + +//----------------------------------------------------------------------- + +struct WP_INIT { + int x; // } Where to walk to + int y; // } +}; + +/** + * Perform a walk directly initiated by a click. + */ +static void WalkProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + PMACTOR pActor; + CORO_END_CONTEXT(_ctx); + + WP_INIT *to = (WP_INIT *)ProcessGetParamsSelf(); // get the co-ordinates - copied to process when it was created + + CORO_BEGIN_CODE(_ctx); + + _ctx->pActor = GetMover(LEAD_ACTOR); + if (_ctx->pActor->MActorState == NORM_MACTOR) { + assert(_ctx->pActor->hCpath != NOPOLY); // Lead actor is not in a path + + GetToken(TOKEN_LEAD); + SetActorDest(_ctx->pActor, to->x, to->y, false, 0); + DontScrollCursor(); + + while (MAmoving(_ctx->pActor)) + CORO_SLEEP(1); + + FreeToken(TOKEN_LEAD); + } + + CORO_END_CODE; +} + +void walkto(int x, int y) { + WP_INIT to = { x, y }; + + CoroutineInstall(PID_TCODE, WalkProcess, &to, sizeof(to)); +} + +/** + * Run appropriate actor or polygon glitter code. + * If none, and it's a WALKTO event, do a walk. + */ +static void User_Event(USER_EVENT uEvent, BUTEVENT be) { + int actor; + int aniX, aniY; + HPOLYGON hPoly; + + // Prevent activation of 2 events on the same tick + if (++eCount != 1) + return; + + if ((actor = GetTaggedActor()) != 0) + actorEvent(actor, uEvent, be); + else if ((hPoly = GetTaggedPoly()) != NOPOLY) + RunPolyTinselCode(hPoly, uEvent, be, false); + else { + GetCursorXY(&aniX, &aniY, true); + + // There could be a poly involved which has no tag. + if ((hPoly = InPolygon(aniX, aniY, TAG)) != NOPOLY + || (hPoly = InPolygon(aniX, aniY, EXIT)) != NOPOLY) { + RunPolyTinselCode(hPoly, uEvent, be, false); + } else if (uEvent == WALKTO) + walkto(aniX, aniY); + } +} + + +/** + * ProcessButEvent + */ +void ProcessButEvent(BUTEVENT be) { + IncUserEvents(); + + if (bSwapButtons) { + switch (be) { + case BE_SLEFT: + be = BE_SRIGHT; + break; + case BE_DLEFT: + be = BE_DRIGHT; + break; + case BE_SRIGHT: + be = BE_SLEFT; + break; + case BE_DRIGHT: + be = BE_DLEFT; + break; + case BE_LDSTART: + be = BE_RDSTART; + break; + case BE_LDEND: + be = BE_RDEND; + break; + case BE_RDSTART: + be = BE_LDSTART; + break; + case BE_RDEND: + be = BE_LDEND; + break; + default: + break; + } + } + +// if (be == BE_SLEFT || be == BE_DLEFT || be == BE_SRIGHT || be == BE_DRIGHT) + if (be == BE_SLEFT || be == BE_SRIGHT) + butEvents++; + + if (!TestToken(TOKEN_CONTROL) && be != BE_LDEND) + return; + + if (InventoryActive()) { + ButtonToInventory(be); + } else { + switch (be) { + case BE_SLEFT: + User_Event(WALKTO, BE_SLEFT); + break; + + case BE_DLEFT: + User_Event(ACTION, BE_DLEFT); + break; + + case BE_SRIGHT: + User_Event(LOOK, BE_SRIGHT); + break; + + default: + break; + } + } +} + +/** + * ProcessKeyEvent + */ + +void ProcessKeyEvent(KEYEVENT ke) { + // This stuff to allow F1 key during startup. + if (bEnableF1 && ke == OPTION_KEY) + control(CONTROL_ON); + else + IncUserEvents(); + + if (ke == ESC_KEY) { + escEvents++; + butEvents++; // Yes, I do mean this + } + + // FIXME: This comparison is weird - I added (BUTEVENT) cast for now to suppress warning + if (!TestToken(TOKEN_CONTROL) && (BUTEVENT)ke != BE_LDEND) + return; + + switch (ke) { + case QUIT_KEY: + PopUpConf(QUIT); + break; + + case OPTION_KEY: + PopUpConf(OPTION); + break; + + case SAVE_KEY: + PopUpConf(SAVE); + break; + + case LOAD_KEY: + PopUpConf(LOAD); + break; + + case WALKTO_KEY: + if (InventoryActive()) + ButtonToInventory(BE_SLEFT); + else + User_Event(WALKTO, BE_NONE); + break; + + case ACTION_KEY: + if (InventoryActive()) + ButtonToInventory(BE_DLEFT); + else + User_Event(ACTION, BE_NONE); + break; + + case LOOK_KEY: + if (InventoryActive()) + ButtonToInventory(BE_SRIGHT); + else + User_Event(LOOK, BE_NONE); + break; + + case ESC_KEY: + case PGUP_KEY: + case PGDN_KEY: + case HOME_KEY: + case END_KEY: + if (InventoryActive()) + KeyToInventory(ke); + break; + + default: + break; + } +} + +/** + * For ESCapable Glitter sequences + */ + +int GetEscEvents(void) { + return escEvents; +} + +/** + * For cutting short talk()s etc. + */ + +int GetLeftEvents(void) { + return butEvents; +} + +/** + * For waitkey() Glitter function + */ + +int getUserEvents(void) { + return userEvents; +} + +uint32 getUserEventTime(void) { + return DwGetCurrentTime() - lastUserEvent; +} + +void resetUserEventTime(void) { + lastUserEvent = DwGetCurrentTime(); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/events.h b/engines/tinsel/events.h new file mode 100644 index 0000000000..bc49d68717 --- /dev/null +++ b/engines/tinsel/events.h @@ -0,0 +1,84 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * User events processing and utility functions + */ + +#ifndef TINSEL_EVENTS_H +#define TINSEL_EVENTS_H + +#include "tinsel/dw.h" +#include "tinsel/coroutine.h" + +namespace Tinsel { + +enum BUTEVENT { + BE_NONE, BE_SLEFT, BE_DLEFT, BE_SRIGHT, BE_DRIGHT, + BE_LDSTART, BE_LDEND, BE_RDSTART, BE_RDEND, + BE_UNKNOWN +}; + + +enum KEYEVENT { + ESC_KEY, QUIT_KEY, SAVE_KEY, LOAD_KEY, OPTION_KEY, + PGUP_KEY, PGDN_KEY, HOME_KEY, END_KEY, + WALKTO_KEY, ACTION_KEY, LOOK_KEY, + NOEVENT_KEY +}; + + +/** + * Reasons for running Glitter code. + * Do not re-order these as equivalent CONSTs are defined in the master + * scene Glitter source file for testing against the event() library function. + */ +enum USER_EVENT { + POINTED, WALKTO, ACTION, LOOK, + ENTER, LEAVE, STARTUP, CONVERSE, + UNPOINT, PUTDOWN, + NOEVENT +}; + + +void AllowDclick(CORO_PARAM, BUTEVENT be); +bool GetControl(int param); + +void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc); +void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor); + +void ProcessButEvent(BUTEVENT be); +void ProcessKeyEvent(KEYEVENT ke); + + +int GetEscEvents(void); +int GetLeftEvents(void); +int getUserEvents(void); + +uint32 getUserEventTime(void); +void resetUserEventTime(void); + +void ResetEcount(void); + +} // end of namespace Tinsel + +#endif /* TINSEL_EVENTS_H */ diff --git a/engines/tinsel/faders.cpp b/engines/tinsel/faders.cpp new file mode 100644 index 0000000000..9ac742800f --- /dev/null +++ b/engines/tinsel/faders.cpp @@ -0,0 +1,175 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Palette Fader and Flasher processes. + */ + +#include "tinsel/pid.h" // list of all process IDs +#include "tinsel/sched.h" // scheduler defs +#include "tinsel/faders.h" // fader defs +#include "tinsel/handle.h" +#include "tinsel/palette.h" // Palette Manager defs + +namespace Tinsel { + +/** structure used by the "FadeProcess" process */ +struct FADE { + const long *pColourMultTable; // list of fixed point colour multipliers - terminated with negative entry + PPALQ pPalQ; // palette queue entry to fade +}; + +// fixed point fade multiplier tables +//const long fadeout[] = {0xf000, 0xd000, 0xb000, 0x9000, 0x7000, 0x5000, 0x3000, 0x1000, 0, -1}; +const long fadeout[] = {0xd000, 0xa000, 0x7000, 0x4000, 0x1000, 0, -1}; +//const long fadein[] = {0, 0x1000, 0x3000, 0x5000, 0x7000, 0x9000, 0xb000, 0xd000, 0x10000L, -1}; +const long fadein[] = {0, 0x1000, 0x4000, 0x7000, 0xa000, 0xd000, 0x10000L, -1}; + +/** + * Scale 'colour' by the fixed point colour multiplier 'colourMult' + * @param colour Colour to scale + * @param colourMult Fixed point multiplier + */ +static COLORREF ScaleColour(COLORREF colour, uint32 colourMult) { + // apply multiplier to RGB components + uint32 red = ((GetRValue(colour) * colourMult) << 8) >> 24; + uint32 green = ((GetGValue(colour) * colourMult) << 8) >> 24; + uint32 blue = ((GetBValue(colour) * colourMult) << 8) >> 24; + + // return new colour + return RGB(red, green, blue); +} + +/** + * Applies the fixed point multiplier 'mult' to all colours in + * 'pOrig' to produce 'pNew'. Each colour in the palette will be + * multiplied by 'mult'. + * @param pNew Pointer to new palette + * @param pOrig Pointer to original palette + * @param numColours Number of colours in the above palettes + * @param mult Fixed point multiplier + */ +static void FadePalette(COLORREF *pNew, COLORREF *pOrig, int numColours, uint32 mult) { + for (int i = 0; i < numColours; i++, pNew++, pOrig++) { + // apply multiplier to RGB components + *pNew = ScaleColour(*pOrig, mult); + } +} + +/** + * Process to fade one palette. + * A pointer to a 'FADE' structure must be passed to this process when + * it is created. + */ +static void FadeProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + COLORREF fadeRGB[MAX_COLOURS]; // local copy of palette + const long *pColMult; // pointer to colour multiplier table + PALETTE *pPalette; // pointer to palette + CORO_END_CONTEXT(_ctx); + + // get the fade data structure - copied to process when it was created + FADE *pFade = (FADE *)ProcessGetParamsSelf(); + + CORO_BEGIN_CODE(_ctx); + + // get pointer to palette - reduce pointer indirection a bit + _ctx->pPalette = (PALETTE *)LockMem(pFade->pPalQ->hPal); + + for (_ctx->pColMult = pFade->pColourMultTable; *_ctx->pColMult >= 0; _ctx->pColMult++) { + // go through all multipliers in table - until a negative entry + + // fade palette using next multiplier + FadePalette(_ctx->fadeRGB, _ctx->pPalette->palRGB, + FROM_LE_32(_ctx->pPalette->numColours), (uint32) *_ctx->pColMult); + + // send new palette to video DAC + UpdateDACqueue(pFade->pPalQ->posInDAC, FROM_LE_32(_ctx->pPalette->numColours), _ctx->fadeRGB); + + // allow time for video DAC to be updated + CORO_SLEEP(1); + } + + CORO_END_CODE; +} + +/** + * Generic palette fader/unfader. Creates a 'FadeProcess' process + * for each palette that is to fade. + * @param multTable Fixed point colour multiplier table + * @param noFadeTable List of palettes not to fade + */ +static void Fader(const long multTable[], SCNHANDLE noFadeTable[]) { + PPALQ pPal; // palette manager iterator + + // create a process for each palette in the palette queue + for (pPal = GetNextPalette(NULL); pPal != NULL; pPal = GetNextPalette(pPal)) { + bool bFade = true; + // assume we want to fade this palette + + // is palette in the list of palettes not to fade + if (noFadeTable != NULL) { + // there is a list of palettes not to fade + for (int i = 0; noFadeTable[i] != 0; i++) { + if (pPal->hPal == noFadeTable[i]) { + // palette is in the list - dont fade it + bFade = false; + + // leave loop prematurely + break; + } + } + } + + if (bFade) { + FADE fade; + + // fill in FADE struct + fade.pColourMultTable = multTable; + fade.pPalQ = pPal; + + // create a fader process for this palette + CoroutineInstall(PID_FADER, FadeProcess, (void *)&fade, sizeof(FADE)); + } + } +} + +/** + * Fades a list of palettes down to black. + * @param noFadeTable A NULL terminated list of palettes not to fade. + */ +void FadeOutFast(SCNHANDLE noFadeTable[]) { + // call generic fader + Fader(fadeout, noFadeTable); +} + +/** + * Fades a list of palettes from black to their current colours. + * @param noFadeTable A NULL terminated list of palettes not to fade. + */ +void FadeInFast(SCNHANDLE noFadeTable[]) { + // call generic fader + Fader(fadein, noFadeTable); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/faders.h b/engines/tinsel/faders.h new file mode 100644 index 0000000000..1e9336fae8 --- /dev/null +++ b/engines/tinsel/faders.h @@ -0,0 +1,55 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Data structures used by the fader and flasher processes + */ + +#ifndef TINSEL_FADERS_H // prevent multiple includes +#define TINSEL_FADERS_H + +#include "tinsel/dw.h" // for SCNHANDLE + +namespace Tinsel { + +enum { + /** + * Number of iterations in a fade out. + * Must match which FadeOut() is in use. + */ + COUNTOUT_COUNT = 6 +}; + +/*----------------------------------------------------------------------*\ +|* Fader Function Prototypes *| +\*----------------------------------------------------------------------*/ + +// usefull palette faders - they all need a list of palettes that +// should not be faded. This parameter can be +// NULL - fade all palettes. + +void FadeOutFast(SCNHANDLE noFadeTable[]); +void FadeInFast(SCNHANDLE noFadeTable[]); + +} // end of namespace Tinsel + +#endif // TINSEL_FADERS_H diff --git a/engines/tinsel/film.h b/engines/tinsel/film.h new file mode 100644 index 0000000000..c8bf4604bc --- /dev/null +++ b/engines/tinsel/film.h @@ -0,0 +1,50 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_FILM_H // prevent multiple includes +#define TINSEL_FILM_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +#include "common/pack-start.h" // START STRUCT PACKING + +struct FREEL { + SCNHANDLE mobj; + SCNHANDLE script; +} PACKED_STRUCT; + +struct FILM { + int32 frate; + int32 numreels; + FREEL reels[1]; +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/font.cpp b/engines/tinsel/font.cpp new file mode 100644 index 0000000000..335ba031fe --- /dev/null +++ b/engines/tinsel/font.cpp @@ -0,0 +1,96 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + */ + +#include "tinsel/dw.h" +#include "tinsel/font.h" +#include "tinsel/handle.h" +#include "tinsel/object.h" +#include "tinsel/text.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +static char tBuffer[TBUFSZ]; + +static SCNHANDLE hTagFont = 0, hTalkFont = 0; + + +/** + * Return address of tBuffer + */ +char *tBufferAddr() { + return tBuffer; +} + +/** + * Return hTagFont handle. + */ +SCNHANDLE hTagFontHandle() { + return hTagFont; +} + +/** + * Return hTalkFont handle. + */ +SCNHANDLE hTalkFontHandle() { + return hTalkFont; +} + +/** + * Called from dec_tagfont() Glitter function. Store the tag font handle. + */ +void TagFontHandle(SCNHANDLE hf) { + hTagFont = hf; // Store the font handle +} + +/** + * Called from dec_talkfont() Glitter function. + * Store the talk font handle. + */ +void TalkFontHandle(SCNHANDLE hf) { + hTalkFont = hf; // Store the font handle +} + +/** + * Poke the background palette into character 0's images. + */ +void fettleFontPal(SCNHANDLE fontPal) { + const FONT *pFont; + PIMAGE pImg; + + assert(fontPal); + assert(hTagFont); // Tag font not declared + assert(hTalkFont); // Talk font not declared + + pFont = (const FONT *)LockMem(hTagFont); + pImg = (PIMAGE)LockMem(FROM_LE_32(pFont->fontInit.hObjImg)); // get image for char 0 + pImg->hImgPal = TO_LE_32(fontPal); + + pFont = (const FONT *)LockMem(hTalkFont); + pImg = (PIMAGE)LockMem(FROM_LE_32(pFont->fontInit.hObjImg)); // get image for char 0 + pImg->hImgPal = TO_LE_32(fontPal); +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/font.h b/engines/tinsel/font.h new file mode 100644 index 0000000000..b75c36191c --- /dev/null +++ b/engines/tinsel/font.h @@ -0,0 +1,48 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_FONT_H // prevent multiple includes +#define TINSEL_FONT_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +// A temporary buffer for extracting text into is defined in font.c +// Accessed using tBufferAddr(), this is how big it is: +#define TBUFSZ 512 + + +char *tBufferAddr(void); +SCNHANDLE hTagFontHandle(void); +SCNHANDLE hTalkFontHandle(void); + +void TagFontHandle(SCNHANDLE hf); +void TalkFontHandle(SCNHANDLE hf); +void fettleFontPal(SCNHANDLE fontPal); + +} // end of namespace Tinsel + +#endif // TINSEL_FONT_H diff --git a/engines/tinsel/graphics.cpp b/engines/tinsel/graphics.cpp new file mode 100644 index 0000000000..751b8929ef --- /dev/null +++ b/engines/tinsel/graphics.cpp @@ -0,0 +1,437 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Low level graphics interface. + */ + +#include "tinsel/graphics.h" +#include "tinsel/handle.h" // LockMem() +#include "tinsel/object.h" +#include "tinsel/palette.h" +#include "tinsel/tinsel.h" + +namespace Tinsel { + +//----------------- LOCAL DEFINES -------------------- + +// Defines used in graphic drawing +#define CHARPTR_OFFSET 16 +#define CHAR_WIDTH 4 +#define CHAR_HEIGHT 4 + +extern uint8 transPalette[MAX_COLOURS]; + +//----------------- SUPPORT FUNCTIONS --------------------- + +/** + * Straight rendering with transparency support + */ + +static void WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) { + // Set up the offset between destination blocks + int rightClip = applyClipping ? pObj->rightClip : 0; + Common::Rect boxBounds; + + if (applyClipping) { + // Adjust the height down to skip any bottom clipping + pObj->height -= pObj->botClip; + + // Make adjustment for the top clipping row + srcP += sizeof(uint16) * ((pObj->width + 3) >> 2) * (pObj->topClip >> 2); + pObj->height -= pObj->topClip; + pObj->topClip %= 4; + } + + // Vertical loop + while (pObj->height > 0) { + // Get the start of the next line output + uint8 *tempDest = destP; + + // Get the line width, and figure out which row range within the 4 row high blocks + // will be displayed if clipping is to be taken into account + int width = pObj->width; + + if (!applyClipping) { + // No clipping, so so set box bounding area for drawing full 4x4 pixel blocks + boxBounds.top = 0; + boxBounds.bottom = 3; + boxBounds.left = 0; + } else { + // Handle any possible clipping at the top of the char block. + // We already handled topClip partially at the beginning of this function. + // Hence the only non-zero values it can assume at this point are 1,2,3, + // and that only during the very first iteration (i.e. when the top char + // block is drawn only partially). In particular, we set topClip to zero, + // as all following blocks are not to be top clipped. + boxBounds.top = pObj->topClip; + pObj->topClip = 0; + + boxBounds.bottom = MIN(boxBounds.top + pObj->height - 1, 3); + + // Handle any possible clipping at the start of the line + boxBounds.left = pObj->leftClip; + if (boxBounds.left >= 4) { + srcP += sizeof(uint16) * (boxBounds.left >> 2); + width -= boxBounds.left & 0xfffc; + boxBounds.left %= 4; + } + + width -= boxBounds.left; + } + + // Horizontal loop + while (width > rightClip) { + boxBounds.right = MIN(boxBounds.left + width - rightClip - 1, 3); + assert(boxBounds.bottom >= boxBounds.top); + assert(boxBounds.right >= boxBounds.left); + + int16 indexVal = READ_LE_UINT16(srcP); + srcP += sizeof(uint16); + + if (indexVal >= 0) { + // Draw a 4x4 block based on the opcode as in index into the block list + const uint8 *p = (uint8 *)pObj->charBase + (indexVal << 4); + p += boxBounds.top * sizeof(uint32); + for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp, p += sizeof(uint32)) { + Common::copy(p + boxBounds.left, p + boxBounds.right + 1, tempDest + (SCREEN_WIDTH * (yp - boxBounds.top))); + } + + } else { + // Draw a 4x4 block with transparency support + indexVal &= 0x7fff; + + // If index is zero, then skip drawing the block completely + if (indexVal > 0) { + // Use the index along with the object's translation offset + const uint8 *p = (uint8 *)pObj->charBase + ((pObj->transOffset + indexVal) << 4); + + // Loop through each pixel - only draw a pixel if it's non-zero + p += boxBounds.top * sizeof(uint32); + for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp) { + p += boxBounds.left; + for (int xp = boxBounds.left; xp <= boxBounds.right; ++xp, ++p) { + if (*p) + *(tempDest + SCREEN_WIDTH * (yp - boxBounds.top) + (xp - boxBounds.left)) = *p; + } + p += 3 - boxBounds.right; + } + } + } + + tempDest += boxBounds.right - boxBounds.left + 1; + width -= 3 - boxBounds.left + 1; + + // None of the remaining horizontal blocks should be left clipped + boxBounds.left = 0; + } + + // If there is any width remaining, there must be a right edge clipping + if (width >= 0) + srcP += sizeof(uint16) * ((width + 3) >> 2); + + // Move to next line line + pObj->height -= boxBounds.bottom - boxBounds.top + 1; + destP += (boxBounds.bottom - boxBounds.top + 1) * SCREEN_WIDTH; + } +} + +/** + * Fill the destination area with a constant colour + */ + +static void WrtConst(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) { + if (applyClipping) { + pObj->height -= pObj->topClip + pObj->botClip; + pObj->width -= pObj->leftClip + pObj->rightClip; + + if (pObj->width <= 0) + return; + } + + // Loop through any remaining lines + while (pObj->height > 0) { + Common::set_to(destP, destP + pObj->width, pObj->constant); + + --pObj->height; + destP += SCREEN_WIDTH; + } +} + +/** + * Translates the destination surface within the object's bounds using the transparency + * lookup table from transpal.cpp (the contents of which have been moved into palette.cpp) + */ + +static void WrtTrans(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) { + if (applyClipping) { + pObj->height -= pObj->topClip + pObj->botClip; + pObj->width -= pObj->leftClip + pObj->rightClip; + + if (pObj->width <= 0) + return; + } + + // Set up the offset between destination lines + int lineOffset = SCREEN_WIDTH - pObj->width; + + // Loop through any remaining lines + while (pObj->height > 0) { + for (int i = 0; i < pObj->width; ++i, ++destP) + *destP = transPalette[*destP]; + + --pObj->height; + destP += lineOffset; + } +} + + +#if 0 +// This commented out code is the untested original WrtNonZero/ClpWrtNonZero combo method +// from the v1 source. It may be needed to be included later on to support v1 gfx files + +/** + * Straight rendering with transparency support + * Possibly only used in the Discworld Demo + */ + +static void DemoWrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) { + // FIXME: If this method is used for the demo, it still needs to be made Endian safe + + // Set up the offset between destination lines + pObj->lineoffset = SCREEN_WIDTH - pObj->width - (applyClipping ? pObj->leftClip - pObj->rightClip : 0); + + // Top clipped line handling + while (applyClipping && (pObj->topClip > 0)) { + // Loop through discarding the data for the line + int width = pObj->width; + while (width > 0) { + int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP); + srcP += sizeof(uint32); + + if (opcodeOrLen >= 0) { + // Dump the data + srcP += ((opcodeOrLen + 3) / 4) * 4; + width -= opcodeOrLen; + } else { + // Dump the run-length opcode + width -= -opcodeOrLen; + } + } + + --pObj->height; + --pObj->topClip; + } + + // Loop for the required number of rows + while (pObj->height > 0) { + + int width = pObj->width; + + // Handling for left edge clipping - this basically involves dumping data until we reach + // the part of the line to be displayed + int clipLeft = pObj->leftClip; + while (applyClipping && (clipLeft > 0)) { + int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP); + srcP += sizeof(uint32); + + if (opcodeOrLen >= 0) { + // Copy a specified number of bytes + // Make adjustments for past the clipping width + int remainder = 4 - (opcodeOrLen % 4); + srcP += MIN(clipLeft, opcodeOrLen); + opcodeOrLen -= MIN(clipLeft, opcodeOrLen); + clipLeft -= MIN(clipLeft, opcodeOrLen); + width -= opcodeOrLen; + + + // Handle any right edge clipping (if run length covers entire width) + if (width < pObj->rightClip) { + remainder += (pObj->rightClip - width); + opcodeOrLen -= (pObj->rightClip - width); + } + + if (opcodeOrLen > 0) + Common::copy(srcP, srcP + opcodeOrLen, destP); + + } else { + // Output a run length number of bytes + // Get data for byte value and run length + opcodeOrLen = -opcodeOrLen; + int runLength = opcodeOrLen & 0xff; + uint8 colourVal = (opcodeOrLen >> 8) & 0xff; + + // Make adjustments for past the clipping width + runLength -= MIN(clipLeft, runLength); + clipLeft -= MIN(clipLeft, runLength); + width -= runLength; + + // Handle any right edge clipping (if run length covers entire width) + if (width < pObj->rightClip) + runLength -= (pObj->rightClip - width); + + if (runLength > 0) { + // Displayable part starts partway through the slice + if (colourVal != 0) + Common::set_to(destP, destP + runLength, colourVal); + destP += runLength; + } + } + + if (width < pObj->rightClip) + width = 0; + } + + // Handling for the visible part of the line + int endWidth = applyClipping ? pObj->rightClip : 0; + while (width > endWidth) { + int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP); + srcP += sizeof(uint32); + + if (opcodeOrLen >= 0) { + // Copy the specified number of bytes + int remainder = 4 - (opcodeOrLen % 4); + + if (width < endWidth) { + // Shorten run length by right clipping + remainder += (pObj->rightClip - width); + opcodeOrLen -= (pObj->rightClip - width); + } + + Common::copy(srcP, srcP + opcodeOrLen, destP); + srcP += opcodeOrLen + remainder; + destP += opcodeOrLen; + width -= opcodeOrLen; + + } else { + // Handle a given run length + opcodeOrLen = -opcodeOrLen; + int runLength = opcodeOrLen & 0xff; + uint8 colourVal = (opcodeOrLen >> 8) & 0xff; + + if (width < endWidth) + // Shorten run length by right clipping + runLength -= (pObj->rightClip - width); + + // Only set pixels if colourVal non-zero (0 signifies transparency) + if (colourVal != 0) + // Fill out a run length of a specified colour + Common::set_to(destP, destP + runLength, colourVal); + + destP += runLength; + width -= runLength; + } + } + + // If right edge clipping is being applied, then width may still be non-zero - in + // that case all remaining line data until the end of the line must be ignored + while (width > 0) { + int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP); + srcP += sizeof(uint32); + + if (opcodeOrLen >= 0) { + // Dump the data + srcP += ((opcodeOrLen + 3) / 4) * 4; + width -= opcodeOrLen; + } else { + // Dump the run-length opcode + width -= -opcodeOrLen; + } + } + + --pObj->height; + destP += pObj->lineoffset; + } +} +#endif + +//----------------- MAIN FUNCTIONS --------------------- + +/** + * Clears both the screen surface buffer and screen to the specified value + */ +void ClearScreen(uint32 val) { + uint32 *pDest = (uint32 *)_vm->screen().getData(); + Common::set_to(pDest, (uint32 *)((byte *)pDest + SCREEN_WIDTH * SCREEN_HEIGHT), val); + _vm->screen().update(); +} + +/** + * Updates the screen surface within the following rectangle + */ +void UpdateScreenRect(const Common::Rect &pClip) { + _vm->screen().updateRect(pClip); +} + +/** + * Draws the specified object onto the screen surface buffer + */ +void DrawObject(DRAWOBJECT *pObj) { + uint8 *srcPtr = NULL; + uint8 *destPtr; + + if ((pObj->width <= 0) || (pObj->height <= 0)) + // Empty image, so return immediately + return; + + // If writing constant data, don't bother locking the data pointer and reading src details + if ((pObj->flags & DMA_CONST) == 0) { + byte *p = (byte *)LockMem(pObj->hBits & 0xFF800000); + + srcPtr = p + (pObj->hBits & 0x7FFFFF); + pObj->charBase = (char *)p + READ_LE_UINT32(p + 0x10); + pObj->transOffset = READ_LE_UINT32(p + 0x14); + } + + // Get destination starting point + destPtr = _vm->screen().getBasePtr(pObj->xPos, pObj->yPos); + + // Handle various draw types + uint8 typeId = pObj->flags & 0xff; + switch (typeId) { + case 0x01: + case 0x08: + case 0x41: + case 0x48: + WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40); + break; + + case 0x04: + case 0x44: + // ClpWrtConst with/without clipping + WrtConst(pObj,destPtr, typeId == 0x44); + break; + + case 0x84: + case 0xC4: + // WrtTrans with/without clipping + WrtTrans(pObj, destPtr, typeId == 0xC4); + break; + + default: + // NoOp + error("Unknown drawing type %d", typeId); + break; + } +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/graphics.h b/engines/tinsel/graphics.h new file mode 100644 index 0000000000..e080e08f58 --- /dev/null +++ b/engines/tinsel/graphics.h @@ -0,0 +1,103 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Low level graphics interface. + */ + +#ifndef TINSEL_GRAPHICS_H // prevent multiple includes +#define TINSEL_GRAPHICS_H + +#include "tinsel/dw.h" + +#include "common/rect.h" +#include "common/system.h" +#include "graphics/surface.h" + +namespace Tinsel { + +struct PALQ; + + +#define SCREEN_WIDTH 320 // PC screen dimensions +#define SCREEN_HEIGHT 200 +#define SCRN_CENTRE_X ((SCREEN_WIDTH - 1) / 2) // screen centre x +#define SCRN_CENTRE_Y ((SCREEN_HEIGHT - 1) / 2) // screen centre y + +/** Class representing either a buffered surface or the physical screen. */ +class Surface : public Graphics::Surface { +private: + bool _isScreen; +public: + Surface(bool isScreen = false) { _isScreen = isScreen; } + Surface(int Width, int Height) { create(Width, Height, 1); _isScreen = false; } + + // Surface methods + byte *getData() { return (byte *)pixels; } + byte *getBasePtr(int x, int y) { return (byte *)Graphics::Surface::getBasePtr(x, y); } + + void update() { + if (_isScreen) { + g_system->copyRectToScreen((const byte *)pixels, pitch, 0, 0, w, h); + g_system->updateScreen(); + } + } + void updateRect(const Common::Rect &r) { + g_system->copyRectToScreen(getBasePtr(r.left, r.top), pitch, r.left, r.top, r.width(), r.height()); + g_system->updateScreen(); + } + +}; + +/** draw object structure - only used when drawing objects */ +struct DRAWOBJECT { + char *charBase; // character set base address + int transOffset; // transparent character offset + int flags; // object flags - see above for list + PALQ *pPal; // objects palette Q position + int constant; // which colour in palette for monochrome objects + int width; // width of object + int height; // height of object + SCNHANDLE hBits; // image bitmap handle + int lineoffset; // offset to next line + int leftClip; // amount to clip off object left + int rightClip; // amount to clip off object right + int topClip; // amount to clip off object top + int botClip; // amount to clip off object bottom + short xPos; // x position of object + short yPos; // y position of object +}; + + +/*----------------------------------------------------------------------*\ +|* Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void ClearScreen(uint32 val); +void DrawObject(DRAWOBJECT *pObj); + +// called to update a rectangle on the video screen from a video page +void UpdateScreenRect(const Common::Rect &pClip); + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/handle.cpp b/engines/tinsel/handle.cpp new file mode 100644 index 0000000000..610f23012f --- /dev/null +++ b/engines/tinsel/handle.cpp @@ -0,0 +1,366 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * This file contains the handle based Memory Manager code + */ + +#define BODGE + +#include "common/file.h" + +#include "tinsel/dw.h" +#include "tinsel/scn.h" // name of "index" file +#include "tinsel/handle.h" +#include "tinsel/heapmem.h" // heap memory manager + + +// these are included only so the relevant structs can be used in convertLEStructToNative() +#include "tinsel/anim.h" +#include "tinsel/multiobj.h" +#include "tinsel/film.h" +#include "tinsel/object.h" +#include "tinsel/palette.h" +#include "tinsel/text.h" +#include "tinsel/scene.h" + +namespace Tinsel { + +//----------------- EXTERNAL GLOBAL DATA -------------------- + +#ifdef DEBUG +bool bLockedScene = 0; +#endif + + +//----------------- LOCAL DEFINES -------------------- + +struct MEMHANDLE { + char szName[12]; //!< 00 - file name of graphics file + int32 filesize; //!< 12 - file size and flags + PMEM_NODE pNode; //!< 16 - memory node for the graphics +}; + + +/** memory allocation flags - stored in the top bits of the filesize field */ +enum { + fPreload = 0x01000000L, //!< preload memory + fDiscard = 0x02000000L, //!< discard memory + fSound = 0x04000000L, //!< sound data + fGraphic = 0x08000000L, //!< graphic data + fCompressed = 0x10000000L, //!< compressed data + fLoaded = 0x20000000L //!< set when file data has been loaded +}; +#define FSIZE_MASK 0x00FFFFFFL //!< mask to isolate the filesize +#define MALLOC_MASK 0xFF000000L //!< mask to isolate the memory allocation flags +#define OFFSETMASK 0x007fffffL //!< get offset of address +//#define HANDLEMASK 0xFF800000L //!< get handle of address + +//----------------- LOCAL GLOBAL DATA -------------------- + +// handle table gets loaded from index file at runtime +static MEMHANDLE *handleTable = 0; + +// number of handles in the handle table +static uint numHandles = 0; + + +//----------------- FORWARD REFERENCES -------------------- + +static void LoadFile(MEMHANDLE *pH, bool bWarn); // load a memory block as a file + + +/** + * Loads the graphics handle table index file and preloads all the + * permanent graphics etc. + */ +void SetupHandleTable(void) { + enum { RECORD_SIZE = 20 }; + + int len; + uint i; + MEMHANDLE *pH; + Common::File f; + + if (f.open(INDEX_FILENAME)) { + // get size of index file + len = f.size(); + + if (len > 0) { + if ((len % RECORD_SIZE) != 0) { + // index file is corrupt + error("File %s is corrupt", INDEX_FILENAME); + } + + // calc number of handles + numHandles = len / RECORD_SIZE; + + // allocate memory for the index file + handleTable = (MEMHANDLE *)calloc(numHandles, sizeof(struct MEMHANDLE)); + + // make sure memory allocated + assert(handleTable); + + // load data + for (i = 0; i < numHandles; i++) { + f.read(handleTable[i].szName, 12); + handleTable[i].filesize = f.readUint32LE(); + // The pointer should always be NULL. We don't + // need to read that from the file. + handleTable[i].pNode = NULL; + f.seek(4, SEEK_CUR); + } + + if (f.ioFailed()) { + // index file is corrupt + error("File %s is corrupt", INDEX_FILENAME); + } + + // close the file + f.close(); + } else { // index file is corrupt + error("File %s is corrupt", INDEX_FILENAME); + } + } else { // cannot find the index file + error("Cannot find file %s", INDEX_FILENAME); + } + + // allocate memory nodes and load all permanent graphics + for (i = 0, pH = handleTable; i < numHandles; i++, pH++) { + if (pH->filesize & fPreload) { + // allocate a fixed memory node for permanent files + pH->pNode = MemoryAlloc(DWM_FIXED, pH->filesize & FSIZE_MASK); + + // make sure memory allocated + assert(pH->pNode); + + // load the data + LoadFile(pH, true); + } +#ifdef BODGE + else if ((pH->filesize & FSIZE_MASK) == 8) { + pH->pNode = NULL; + } +#endif + else { + // allocate a discarded memory node for other files + pH->pNode = MemoryAlloc( + DWM_MOVEABLE | DWM_DISCARDABLE | DWM_NOALLOC, + pH->filesize & FSIZE_MASK); + + // make sure memory allocated + assert(pH->pNode); + } + } +} + +void FreeHandleTable(void) { + if (handleTable) { + free(handleTable); + handleTable = NULL; + } +} + +/** + * Loads a memory block as a file. + * @param pH Memory block pointer + * @param bWarn If set, treat warnings as errors + */ +void LoadFile(MEMHANDLE *pH, bool bWarn) { + Common::File f; + char szFilename[sizeof(pH->szName) + 1]; + + if (pH->filesize & fCompressed) { + error("Compression handling has been removed!"); + } + + // extract and zero terminate the filename + strncpy(szFilename, pH->szName, sizeof(pH->szName)); + szFilename[sizeof(pH->szName)] = 0; + + if (f.open(szFilename)) { + // read the data + int bytes; + uint8 *addr; + + if (pH->filesize & fPreload) + // preload - no need to lock the memory + addr = (uint8 *)pH->pNode; + else { + // discardable - lock the memory + addr = (uint8 *)MemoryLock(pH->pNode); + } +#ifdef DEBUG + if (addr == NULL) { + if (pH->filesize & fPreload) + // preload - no need to lock the memory + addr = (uint8 *)pH->pNode; + else { + // discardable - lock the memory + addr = (uint8 *)MemoryLock(pH->pNode); + } + } +#endif + + // make sure address is valid + assert(addr); + + bytes = f.read(addr, pH->filesize & FSIZE_MASK); + + // close the file + f.close(); + + if ((pH->filesize & fPreload) == 0) { + // discardable - unlock the memory + MemoryUnlock(pH->pNode); + } + + // set the loaded flag + pH->filesize |= fLoaded; + + if (bytes == (pH->filesize & FSIZE_MASK)) { + return; + } + + if (bWarn) + // file is corrupt + error("File %s is corrupt", szFilename); + } + + if (bWarn) + // cannot find file + error("Cannot find file %s", szFilename); +} + +/** + * Returns the address of a image, given its memory handle. + * @param offset Handle and offset to data + */ +uint8 *LockMem(SCNHANDLE offset) { + uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use + MEMHANDLE *pH; // points to table entry + + // range check the memory handle + assert(handle < numHandles); + + pH = handleTable + handle; + + if (pH->filesize & fPreload) { + // permanent files are already loaded + return (uint8 *)pH->pNode + (offset & OFFSETMASK); + } else { + if (pH->pNode->pBaseAddr && (pH->filesize & fLoaded)) + // already allocated and loaded + return pH->pNode->pBaseAddr + (offset & OFFSETMASK); + + if (pH->pNode->pBaseAddr == NULL) + // must have been discarded - reallocate the memory + MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, + DWM_MOVEABLE | DWM_DISCARDABLE); + + if (pH->pNode->pBaseAddr == NULL) + error("Out of memory"); + + LoadFile(pH, true); + + // make sure address is valid + assert(pH->pNode->pBaseAddr); + + return pH->pNode->pBaseAddr + (offset & OFFSETMASK); + } +} + +/** + * Called to make the current scene non-discardable. + * @param offset Handle and offset to data + */ +void LockScene(SCNHANDLE offset) { + + uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use + MEMHANDLE *pH; // points to table entry + +#ifdef DEBUG + assert(!bLockedScene); // Trying to lock more than one scene +#endif + + // range check the memory handle + assert(handle < numHandles); + + pH = handleTable + handle; + + // compact the heap to avoid fragmentation while scene is non-discardable + HeapCompact(MAX_INT, false); + + if ((pH->filesize & fPreload) == 0) { + // change the flags for the node + MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE); +#ifdef DEBUG + bLockedScene = true; +#endif + } +} + +/** + * Called to make the current scene discardable again. + * @param offset Handle and offset to data + */ +void UnlockScene(SCNHANDLE offset) { + + uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use + MEMHANDLE *pH; // points to table entry + + // range check the memory handle + assert(handle < numHandles); + + pH = handleTable + handle; + + if ((pH->filesize & fPreload) == 0) { + // change the flags for the node + MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE | DWM_DISCARDABLE); +#ifdef DEBUG + bLockedScene = false; +#endif + } +} + +/*----------------------------------------------------------------------*/ + +#ifdef BODGE + +/** + * Validates that a specified handle pointer is valid + * @param offset Handle and offset to data + */ +bool ValidHandle(SCNHANDLE offset) { + uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use + MEMHANDLE *pH; // points to table entry + + // range check the memory handle + assert(handle < numHandles); + + pH = handleTable + handle; + + return (pH->filesize & FSIZE_MASK) != 8; +} +#endif + +} // end of namespace Tinsel diff --git a/engines/tinsel/handle.h b/engines/tinsel/handle.h new file mode 100644 index 0000000000..2cb1638d9d --- /dev/null +++ b/engines/tinsel/handle.h @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Graphics Memory Manager data structures + * TODO: This should really be named dos_hand.h, or the dos_hand.cpp should be renamed + */ + +#ifndef TINSEL_HANDLE_H // prevent multiple includes +#define TINSEL_HANDLE_H + +#include "tinsel/dw.h" // new data types + +namespace Tinsel { + +/*----------------------------------------------------------------------*\ +|* Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void SetupHandleTable(void); // Loads the graphics handle table index file and preloads all the permanent graphics etc. +void FreeHandleTable(void); + +uint8 *LockMem( // returns the addr of a image, given its memory handle + SCNHANDLE offset); // handle and offset to data + +void LockScene( // Called to make the current scene non-discardable + SCNHANDLE offset); // handle and offset to data + +void UnlockScene( // Called to make the current scene discardable again + SCNHANDLE offset); // handle and offset to data + +} // end of namespace Tinsel + +#endif // TINSEL_HANDLE_H diff --git a/engines/tinsel/heapmem.cpp b/engines/tinsel/heapmem.cpp new file mode 100644 index 0000000000..f3df3d4391 --- /dev/null +++ b/engines/tinsel/heapmem.cpp @@ -0,0 +1,594 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * This file contains the handle based Memory Manager code. + */ + +#include "tinsel/heapmem.h" +#include "tinsel/timers.h" // For DwGetCurrentTime + +namespace Tinsel { + +// minimum memory required for MS-DOS version of game +#define MIN_MEM 2506752L + +// list of all memory nodes +MEM_NODE mnodeList[NUM_MNODES]; + +// pointer to the linked list of free mnodes +static PMEM_NODE pFreeMemNodes; + +#ifdef DEBUG +// diagnostic mnode counters +static int numNodes; +static int maxNodes; +#endif + +// the mnode heap sentinel +static MEM_NODE heapSentinel; + +// +static PMEM_NODE AllocMemNode(void); + + +/** + * Initialises the memory manager. + */ +void MemoryInit(void) { + PMEM_NODE pNode; + +#ifdef DEBUG + // clear number of nodes in use + numNodes = 0; +#endif + + // place first node on free list + pFreeMemNodes = mnodeList; + + // link all other objects after first + for (int i = 1; i < NUM_MNODES; i++) { + mnodeList[i - 1].pNext = mnodeList + i; + } + + // null the last mnode + mnodeList[NUM_MNODES - 1].pNext = NULL; + + // allocatea big chunk of memory + const uint32 size = 2*MIN_MEM+655360L; + uint8 *mem = (uint8 *)malloc(size); + assert(mem); + + // allocate a mnode for this memory + pNode = AllocMemNode(); + + // make sure mnode was allocated + assert(pNode); + + // convert segment to memory address + pNode->pBaseAddr = mem; + + // set size of the memory heap + pNode->size = size; + + // clear the memory + memset(pNode->pBaseAddr, 0, size); + + // set cyclic links to the sentinel + heapSentinel.pPrev = pNode; + heapSentinel.pNext = pNode; + pNode->pPrev = &heapSentinel; + pNode->pNext = &heapSentinel; + + // flag sentinel as locked + heapSentinel.flags = DWM_LOCKED | DWM_SENTINEL; +} + + +#ifdef DEBUG +/** + * Shows the maximum number of mnodes used at once. + */ + +void MemoryStats(void) { + printf("%i mnodes of %i used.\n", maxNodes, NUM_MNODES); +} +#endif + +/** + * Allocate a mnode from the free list. + */ +static PMEM_NODE AllocMemNode(void) { + // get the first free mnode + PMEM_NODE pMemNode = pFreeMemNodes; + + // make sure a mnode is available + assert(pMemNode); // Out of memory nodes + + // the next free mnode + pFreeMemNodes = pMemNode->pNext; + + // wipe out the mnode + memset(pMemNode, 0, sizeof(MEM_NODE)); + +#ifdef DEBUG + // one more mnode in use + if (++numNodes > maxNodes) + maxNodes = numNodes; +#endif + + // return new mnode + return pMemNode; +} + +/** + * Return a mnode back to the free list. + * @param pMemNode Node of the memory object + */ +void FreeMemNode(PMEM_NODE pMemNode) { + // validate mnode pointer + assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + +#ifdef DEBUG + // one less mnode in use + --numNodes; + assert(numNodes >= 0); +#endif + + // place free list in mnode next + pMemNode->pNext = pFreeMemNodes; + + // add mnode to top of free list + pFreeMemNodes = pMemNode; +} + + +/** + * Tries to make space for the specified number of bytes on the specified heap. + * @param size Number of bytes to free up + * @param bDiscard When set - will discard blocks to fullfill the request + */ +bool HeapCompact(long size, bool bDiscard) { + PMEM_NODE pHeap = &heapSentinel; + PMEM_NODE pPrev, pCur, pOldest; + long largest; // size of largest free block + uint32 oldest; // time of the oldest discardable block + + while (true) { + bool bChanged; + + do { + bChanged = false; + for (pPrev = pHeap->pNext, pCur = pPrev->pNext; + pCur != pHeap; pPrev = pCur, pCur = pCur->pNext) { + if (pPrev->flags == 0 && pCur->flags == 0) { + // set the changed flag + bChanged = true; + + // both blocks are free - merge them + pPrev->size += pCur->size; + + // unlink the current mnode + pPrev->pNext = pCur->pNext; + pCur->pNext->pPrev = pPrev; + + // free the current mnode + FreeMemNode(pCur); + + // leave the loop + break; + } else if ((pPrev->flags & (DWM_MOVEABLE | DWM_LOCKED | DWM_DISCARDED)) == DWM_MOVEABLE + && pCur->flags == 0) { + // a free block after a moveable block - swap them + + // set the changed flag + bChanged = true; + + // move the unlocked blocks data up (can overlap) + memmove(pPrev->pBaseAddr + pCur->size, + pPrev->pBaseAddr, pPrev->size); + + // swap the order in the linked list + pPrev->pPrev->pNext = pCur; + pCur->pNext->pPrev = pPrev; + + pCur->pPrev = pPrev->pPrev; + pPrev->pPrev = pCur; + + pPrev->pNext = pCur->pNext; + pCur->pNext = pPrev; + + pCur->pBaseAddr = pPrev->pBaseAddr; + pPrev->pBaseAddr += pCur->size; + + // leave the loop + break; + } + } + } while (bChanged); + + // find the largest free block + for (largest = 0, pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) { + if (pCur->flags == 0 && pCur->size > largest) + largest = pCur->size; + } + + if (largest >= size) + // we have freed enough memory + return true; + + if (!bDiscard) + // we cannot free enough without discarding blocks + return false; + + // find the oldest discardable block + oldest = DwGetCurrentTime(); + pOldest = NULL; + for (pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) { + if ((pCur->flags & (DWM_DISCARDABLE | DWM_DISCARDED | DWM_LOCKED)) + == DWM_DISCARDABLE) { + // found a non-discarded discardable block + if (pCur->lruTime < oldest) { + oldest = pCur->lruTime; + pOldest = pCur; + } + } + } + + if (pOldest) + // discard the oldest block + MemoryDiscard(pOldest); + else + // cannot discard any blocks + return false; + } +} + +/** + * Allocates the specified number of bytes from the heap. + * @param flags Allocation attributes + * @param size Number of bytes to allocate + */ +PMEM_NODE MemoryAlloc(int flags, long size) { + PMEM_NODE pHeap = &heapSentinel; + PMEM_NODE pNode; + bool bCompacted = true; // set when heap has been compacted + + // compact the heap if we are allocating fixed memory + if (flags & DWM_FIXED) + HeapCompact(MAX_INT, false); + + while ((flags & DWM_NOALLOC) == 0 && bCompacted) { + // search the heap for a free block + + for (pNode = pHeap->pNext; pNode != pHeap; pNode = pNode->pNext) { + if (pNode->flags == 0 && pNode->size >= size) { + // a free block of the required size + pNode->flags = flags; + + // update the LRU time + pNode->lruTime = DwGetCurrentTime() + 1; + + if (pNode->size == size) { + // an exact fit + + // check for zeroing the block + if (flags & DWM_ZEROINIT) + memset(pNode->pBaseAddr, 0, size); + + if (flags & DWM_FIXED) + // lock the memory + return (PMEM_NODE)MemoryLock(pNode); + else + // just return the node + return pNode; + } else { + // allocate a node for the remainder of the free block + PMEM_NODE pTemp = AllocMemNode(); + + // calc size of the free block + long freeSize = pNode->size - size; + + // set size of free block + pTemp->size = freeSize; + + // set size of node + pNode->size = size; + + if (flags & DWM_FIXED) { + // place the free node after pNode + pTemp->pBaseAddr = pNode->pBaseAddr + size; + pTemp->pNext = pNode->pNext; + pTemp->pPrev = pNode; + pNode->pNext->pPrev = pTemp; + pNode->pNext = pTemp; + + // check for zeroing the block + if (flags & DWM_ZEROINIT) + memset(pNode->pBaseAddr, 0, size); + + return (PMEM_NODE)MemoryLock(pNode); + } else { + // place the free node before pNode + pTemp->pBaseAddr = pNode->pBaseAddr; + pNode->pBaseAddr += freeSize; + pTemp->pNext = pNode; + pTemp->pPrev = pNode->pPrev; + pNode->pPrev->pNext = pTemp; + pNode->pPrev = pTemp; + + // check for zeroing the block + if (flags & DWM_ZEROINIT) + memset(pNode->pBaseAddr, 0, size); + + return pNode; + } + } + } + } + // compact the heap if we get to here + bCompacted = HeapCompact(size, (flags & DWM_NOCOMPACT) ? false : true); + } + + // not allocated a block if we get to here + if (flags & DWM_DISCARDABLE) { + // chain a discarded node onto the end of the heap + pNode = AllocMemNode(); + pNode->flags = flags | DWM_DISCARDED; + + // set mnode at the end of the list + pNode->pPrev = pHeap->pPrev; + pNode->pNext = pHeap; + + // fix links to this mnode + pHeap->pPrev->pNext = pNode; + pHeap->pPrev = pNode; + + // return the discarded node + return pNode; + } + + // could not allocate a block + return NULL; +} + +/** + * Discards the specified memory object. + * @param pMemNode Node of the memory object + */ +void MemoryDiscard(PMEM_NODE pMemNode) { + // validate mnode pointer + assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + + // object must be discardable + assert(pMemNode->flags & DWM_DISCARDABLE); + + // object cannot be locked + assert((pMemNode->flags & DWM_LOCKED) == 0); + + if ((pMemNode->flags & DWM_DISCARDED) == 0) { + // allocate a free node to replace this node + PMEM_NODE pTemp = AllocMemNode(); + + // copy node data + memcpy(pTemp, pMemNode, sizeof(MEM_NODE)); + + // flag as a free block + pTemp->flags = 0; + + // link in the free node + pTemp->pPrev->pNext = pTemp; + pTemp->pNext->pPrev = pTemp; + + // discard the node + pMemNode->flags |= DWM_DISCARDED; + pMemNode->pBaseAddr = NULL; + pMemNode->size = 0; + + // and place it at the end of the heap + while ((pTemp->flags & DWM_SENTINEL) != DWM_SENTINEL) + pTemp = pTemp->pNext; + + // pTemp now points to the heap sentinel + // set mnode at the end of the list + pMemNode->pPrev = pTemp->pPrev; + pMemNode->pNext = pTemp; + + // fix links to this mnode + pTemp->pPrev->pNext = pMemNode; + pTemp->pPrev = pMemNode; + } +} + +/** + * Frees the specified memory object and invalidates its node. + * @param pMemNode Node of the memory object + */ +void MemoryFree(PMEM_NODE pMemNode) { + PMEM_NODE pPrev, pNext; + + // validate mnode pointer + assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + + // get pointer to the next mnode + pNext = pMemNode->pNext; + + // get pointer to the previous mnode + pPrev = pMemNode->pPrev; + + if (pPrev->flags == 0) { + // there is a previous free mnode + pPrev->size += pMemNode->size; + + // unlink this mnode + pPrev->pNext = pNext; // previous to next + pNext->pPrev = pPrev; // next to previous + + // free this mnode + FreeMemNode(pMemNode); + + pMemNode = pPrev; + } + if (pNext->flags == 0) { + // the next mnode is free + pMemNode->size += pNext->size; + + // flag as a free block + pMemNode->flags = 0; + + // unlink the next mnode + pMemNode->pNext = pNext->pNext; + pNext->pNext->pPrev = pMemNode; + + // free the next mnode + FreeMemNode(pNext); + } +} + +/** + * Locks a memory object and returns a pointer to the first byte + * of the objects memory block. + * @param pMemNode Node of the memory object + */ +void *MemoryLock(PMEM_NODE pMemNode) { + // validate mnode pointer + assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + + // make sure memory object is not already locked + assert((pMemNode->flags & DWM_LOCKED) == 0); + + // check for a discarded or null memory object + if ((pMemNode->flags & DWM_DISCARDED) || pMemNode->size == 0) + return NULL; + + // set the lock flag + pMemNode->flags |= DWM_LOCKED; + + // return memory objects base address + return pMemNode->pBaseAddr; +} + +/** + * Changes the size or attributes of a specified memory object. + * @param pMemNode Node of the memory object + * @param size New size of block + * @param flags How to reallocate the object + */ +PMEM_NODE MemoryReAlloc(PMEM_NODE pMemNode, long size, int flags) { + PMEM_NODE pNew; + + // validate mnode pointer + assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + + // validate the flags + // cannot be fixed and moveable + assert((flags & (DWM_FIXED | DWM_MOVEABLE)) != (DWM_FIXED | DWM_MOVEABLE)); + + // cannot be fixed and discardable + assert((flags & (DWM_FIXED | DWM_DISCARDABLE)) != (DWM_FIXED | DWM_DISCARDABLE)); + + // must be fixed or moveable + assert(flags & (DWM_FIXED | DWM_MOVEABLE)); + + // align the size to machine boundary requirements + size = (size + sizeof(int) - 1) & ~(sizeof(int) - 1); + + // validate the size + assert(size); + + // make sure we want the node on the same heap + assert((flags & (DWM_SOUND | DWM_GRAPHIC)) == (pMemNode->flags & (DWM_SOUND | DWM_GRAPHIC))); + + if (size == pMemNode->size) { + // must be just a change in flags + + // update the nodes flags + pMemNode->flags = flags; + } else { + // unlink the mnode from the current heap + pMemNode->pNext->pPrev = pMemNode->pPrev; + pMemNode->pPrev->pNext = pMemNode->pNext; + + // allocate a new node + pNew = MemoryAlloc((flags & ~DWM_FIXED) | DWM_MOVEABLE, size); + + // make sure memory allocated + assert(pNew != NULL); + + // update the nodes flags + pNew->flags = flags; + + // copy the node to the current node + memcpy(pMemNode, pNew, sizeof(MEM_NODE)); + + // relink the mnode into the list + pMemNode->pPrev->pNext = pMemNode; + pMemNode->pNext->pPrev = pMemNode; + + // free the new node + FreeMemNode(pNew); + } + + if (flags & DWM_FIXED) + // lock the memory + return (PMEM_NODE)MemoryLock(pMemNode); + else + // just return the node + return pMemNode; +} + +/** + * Unlocks a memory object. + * @param pMemNode Node of the memory object + */ +void MemoryUnlock(PMEM_NODE pMemNode) { + // validate mnode pointer + assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1); + + // make sure memory object is already locked + assert(pMemNode->flags & DWM_LOCKED); + + // clear the lock flag + pMemNode->flags &= ~DWM_LOCKED; + + // update the LRU time + pMemNode->lruTime = DwGetCurrentTime(); +} + +/** + * Retrieves the mnode associated with the specified pointer to a memory object. + * @param pMem Address of memory object + */ +PMEM_NODE MemoryHandle(void *pMem) { + PMEM_NODE pNode; + // search the DOS heap + for (pNode = heapSentinel.pNext; pNode != &heapSentinel; pNode = pNode->pNext) { + if (pNode->pBaseAddr == pMem) + // found it + return pNode; + } + + // not found if we get to here + return NULL; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/heapmem.h b/engines/tinsel/heapmem.h new file mode 100644 index 0000000000..c5d022b7f9 --- /dev/null +++ b/engines/tinsel/heapmem.h @@ -0,0 +1,110 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * This file contains the handle based Memory Manager defines + */ + +#ifndef TINSEL_HEAPMEM_H +#define TINSEL_HEAPMEM_H + +#include "tinsel/dw.h" // new data types + +namespace Tinsel { + +#define NUM_MNODES 192 // the number of memory management nodes (was 128, then 192) + +struct MEM_NODE { + MEM_NODE *pNext; // link to the next node in the list + MEM_NODE *pPrev; // link to the previous node in the list + uint8 *pBaseAddr; // base address of the memory object + long size; // size of the memory object + uint32 lruTime; // time when memory object was last accessed + int flags; // allocation attributes +}; +typedef MEM_NODE *PMEM_NODE; + +// allocation flags for the MemoryAlloc function +#define DWM_FIXED 0x0001 // allocates fixed memory +#define DWM_MOVEABLE 0x0002 // allocates movable memory +#define DWM_DISCARDABLE 0x0004 // allocates discardable memory +#define DWM_NOALLOC 0x0008 // when used with discardable memory - allocates a discarded block +#define DWM_NOCOMPACT 0x0010 // does not discard memory to satisfy the allocation request +#define DWM_ZEROINIT 0x0020 // initialises memory contents to zero +#define DWM_SOUND 0x0040 // allocate from the sound pool +#define DWM_GRAPHIC 0x0080 // allocate from the graphics pool + +// return value from the MemoryFlags function +#define DWM_DISCARDED 0x0100 // the objects memory block has been discarded + +// internal allocation flags +#define DWM_LOCKED 0x0200 // the objects memory block is locked +#define DWM_SENTINEL 0x0400 // the objects memory block is a sentinel + + +/*----------------------------------------------------------------------*\ +|* Memory Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void MemoryInit(void); // initialises the memory manager + +#ifdef DEBUG +void MemoryStats(void); // Shows the maximum number of mnodes used at once +#endif + +PMEM_NODE MemoryAlloc( // allocates the specified number of bytes from the heap + int flags, // allocation attributes + long size); // number of bytes to allocate + +void MemoryDiscard( // discards the specified memory object + PMEM_NODE pMemNode); // node of the memory object + +int MemoryFlags( // returns information about the specified memory object + PMEM_NODE pMemNode); // node of the memory object + +void MemoryFree( // frees the specified memory object and invalidates its node + PMEM_NODE pMemNode); // node of the memory object + +PMEM_NODE MemoryHandle( // Retrieves the mnode associated with the specified pointer to a memory object + void *pMem); // address of memory object + +void *MemoryLock( // locks a memory object and returns a pointer to the first byte of the objects memory block + PMEM_NODE pMemNode); // node of the memory object + +PMEM_NODE MemoryReAlloc( // changes the size or attributes of a specified memory object + PMEM_NODE pMemNode, // node of the memory object + long size, // new size of block + int flags); // how to reallocate the object + +long MemorySize( // returns the size, in bytes, of the specified memory object + PMEM_NODE pMemNode); // node of the memory object + +void MemoryUnlock( // unlocks a memory object + PMEM_NODE pMemNode); // node of the memory object + +bool HeapCompact( // Allocates the specified number of bytes from the specified heap + long size, // number of bytes to free up + bool bDiscard); // when set - will discard blocks to fullfill the request + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/inventory.cpp b/engines/tinsel/inventory.cpp new file mode 100644 index 0000000000..96ee01edf6 --- /dev/null +++ b/engines/tinsel/inventory.cpp @@ -0,0 +1,4533 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Handles the inventory and conversation windows. + * + * And the save/load game windows. Some of this will be platform + * specific - I'll try to separate this ASAP. + * + * And there's still a bit of tidying and commenting to do yet. + */ + +//#define USE_3FLAGS 1 + +#include "tinsel/actors.h" +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/font.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/multiobj.h" +#include "tinsel/music.h" +#include "tinsel/polygons.h" +#include "tinsel/savescn.h" +#include "tinsel/sched.h" +#include "tinsel/serializer.h" +#include "tinsel/sound.h" +#include "tinsel/strres.h" +#include "tinsel/text.h" +#include "tinsel/timers.h" // For ONE_SECOND constant +#include "tinsel/tinsel.h" // For engine access +#include "tinsel/token.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" + +namespace Tinsel { + +//----------------- EXTERNAL GLOBAL DATA -------------------- + +// In DOS_DW.C +extern bool bRestart; // restart flag - set to restart the game + +#ifdef MAC_OPTIONS +// In MAC_SOUND.C +extern int volMaster; +#endif + + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// Tag functions in PDISPLAY.C +extern void EnableTags(void); +extern void DisableTags(void); + + +//----------------- LOCAL DEFINES -------------------- + +//#define ALL_CURSORS + +#define INV_PICKUP BE_SLEFT // Local names +#define INV_LOOK BE_SRIGHT // for button events +#define INV_ACTION BE_DLEFT // + + +// For SlideSlider() and similar +enum SSFN { + S_START, S_SLIDE, S_END, S_TIMEUP, S_TIMEDN +}; + +/** attribute values - may become bit field if further attributes are added */ +enum { + IO_ONLYINV1 = 0x01, + IO_ONLYINV2 = 0x02, + IO_DROPCODE = 0x04 +}; + +//----------------------- +// Moveable window translucent rectangle position limits +enum { + MAXLEFT = 315, // + MINRIGHT = 3, // These values keep 2 pixcells + MINTOP = -13, // of header on the screen. + MAXTOP = 195 // +}; + +//----------------------- +// Indices into winPartsf's reels +#define IX_SLIDE 0 // Slider +#define IX_V26 1 +#define IX_V52 2 +#define IX_V78 3 +#define IX_V104 4 +#define IX_V130 5 +#define IX_H26 6 +#define IX_H52 7 +#define IX_H78 8 +#define IX_H104 9 +#define IX_H130 10 +#define IX_H156 11 +#define IX_H182 12 +#define IX_H208 13 +#define IX_H234 14 +#define IX_TL 15 // Top left corner +#define IX_TR 16 // Top right corner +#define IX_BL 17 // Bottom left corner +#define IX_BR 18 // Bottom right corner +#define IX_H25 19 +#define IX_V11 20 +#define IX_RTL 21 // Re-sizing top left corner +#define IX_RTR 22 // Re-sizing top right corner +#define IX_RBR 23 // Re-sizing bottom right corner +#define IX_CURLR 24 // } +#define IX_CURUD 25 // } +#define IX_CURDU 26 // } Custom cursors +#define IX_CURDD 27 // } +#define IX_CURUP 28 // } +#define IX_CURDOWN 29 // } +#define IX_MDGROOVE 30 // 'Mixing desk' slider background +#define IX_MDSLIDER 34 // 'Mixing desk' slider + +#define IX_BLANK1 35 // +#define IX_BLANK2 36 // +#define IX_BLANK3 37 // +#define IX_CIRCLE1 38 // +#define IX_CIRCLE2 39 // +#define IX_CROSS1 40 // +#define IX_CROSS2 41 // +#define IX_CROSS3 42 // +#define IX_QUIT1 43 // +#define IX_QUIT2 44 // +#define IX_QUIT3 45 // +#define IX_TICK1 46 // +#define IX_TICK2 47 // +#define IX_TICK3 48 // +#define IX_NTR 49 // New top right corner +#define HOPEDFORREELS 50 + +#define NORMGRAPH 0 +#define DOWNGRAPH 1 +#define HIGRAPH 2 +//----------------------- +#define FIX_UK 0 +#define FIX_FR 1 +#define FIX_GR 2 +#define FIX_IT 3 +#define FIX_SP 4 +#define FIX_USA 5 +#define HOPEDFORFREELS 6 // Expected flag reels +//----------------------- + +#define MAX_ININV 150 // Max in an inventory +#define MAX_CONVBASIC 10 // Max permanent conversation icons + +#define MAXHICONS 10 // Max dimensions of +#define MAXVICONS 6 // an inventory window + +#define ITEM_WIDTH 25 // Dimensions of an icon +#define ITEM_HEIGHT 25 // + +// Number of objects that makes up an empty window +#define MAX_WCOMP 21 // 4 corners + (3+3) sides + (2+2) extra sides + // + Bground + title + slider + // + more Needed for save game window + +#define MAX_ICONS MAXHICONS*MAXVICONS + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +//----- Permanent data (compiled in) ----- + +// Save game name editing cursor + +#define CURSOR_CHAR '_' +char sCursor[2] = { CURSOR_CHAR, 0 }; +static const int hFillers[MAXHICONS] = { + IX_H26, // 2 icons wide + IX_H52, // 3 + IX_H78, // 4 + IX_H104, // 5 + IX_H130, // 6 + IX_H156, // 7 + IX_H182, // 8 + IX_H208, // 9 + IX_H234 // 10 icons wide +}; +static const int vFillers[MAXVICONS] = { + IX_V26, // 2 icons high + IX_V52, // 3 + IX_V78, // 4 + IX_V104, // 5 + IX_V130 // 6 icons high +}; + + +//----- Permanent data (set once) ----- + +static SCNHANDLE winPartsf = 0; // Window members and cursors' graphic data +static SCNHANDLE flagFilm = 0; // Window members and cursors' graphic data +static SCNHANDLE configStrings[20]; + +static PINV_OBJECT pio = 0; // Inventory objects' data +static int numObjects = 0; // Number of inventory objects + + +//----- Permanent data (updated, valid while inventory closed) ----- + +static enum {NO_INV, IDLE_INV, ACTIVE_INV, BOGUS_INV} InventoryState; + +static int HeldItem = INV_NOICON; // Current held item + +struct INV_DEF { + + int MinHicons; // } + int MinVicons; // } Dimension limits + int MaxHicons; // } + int MaxVicons; // } + + int NoofHicons; // } + int NoofVicons; // } Current dimentsions + + int ItemOrder[MAX_ININV]; // Contained items + int NoofItems; // Current number of held items + + int FirstDisp; // Index to first item currently displayed + + int inventoryX; // } Display position + int inventoryY; // } + int otherX; // } Display position + int otherY; // } + + int MaxInvObj; // Max. allowed contents + + SCNHANDLE hInvTitle; // Window heading + + bool resizable; // Re-sizable window? + bool moveable; // Moveable window? + + int sNoofHicons; // } + int sNoofVicons; // } Current dimensions + + bool bMax; // Maximised last time open? + +}; +typedef INV_DEF *PINV_DEF; + +static INV_DEF InvD[NUM_INV]; // Conversation + 2 inventories + ... + + +// Permanent contents of conversation inventory +static int Inv0Order[MAX_CONVBASIC]; // Basic items i.e. permanent contents +static int Num0Order = 0; // - copy to conv. inventory at pop-up time + + + +//----- Data pertinant to current active inventory ----- + +static int ino = 0; // Which inventory is currently active + +static bool InventoryHidden = false; +static bool InventoryMaximised = false; + +static enum { ID_NONE, ID_MOVE, ID_SLIDE, + ID_BOTTOM, ID_TOP, ID_LEFT, ID_RIGHT, + ID_TLEFT, ID_TRIGHT, ID_BLEFT, ID_BRIGHT, + ID_CSLIDE, ID_MDCONT } InvDragging; + +static int SuppH = 0; // 'Linear' element of +static int SuppV = 0; // dimensions during re-sizing + +static int Ychange = 0; // +static int Ycompensate = 0; // All to do with re-sizing. +static int Xchange = 0; // +static int Xcompensate = 0; // + +static bool ItemsChanged = 0; // When set, causes items to be re-drawn + +static bool bOpenConf = 0; + +static int TL = 0, TR = 0, BL = 0, BR = 0; // Used during window construction +static int TLwidth = 0, TLheight = 0; // +static int TRwidth = 0; // +static int BLheight = 0; // + + + +static OBJECT *objArray[MAX_WCOMP]; // Current display objects (window) +static OBJECT *iconArray[MAX_ICONS]; // Current display objects (icons) +static ANIM iconAnims[MAX_ICONS]; +static OBJECT *DobjArray[MAX_WCOMP]; // Current display objects (re-sizing window) + +static OBJECT *RectObject = 0, *SlideObject = 0; // Current display objects, for reference + // objects are in objArray. + +static int slideY = 0; // For positioning the slider +static int slideYmax = 0, slideYmin = 0; // + +// Also to do with the slider +static struct { int n; int y; } slideStuff[MAX_ININV+1]; + +#define MAXSLIDES 4 +struct MDSLIDES { + int num; + OBJECT *obj; + int min, max; +}; +static MDSLIDES mdSlides[MAXSLIDES]; +static int numMdSlides = 0; + +static int GlitterIndex = 0; + +static HPOLYGON thisConvPoly = 0; // Conversation code is in a polygon code block +static int thisConvIcon = 0; // Passed to polygon code via convIcon() +static int pointedIcon = INV_NOICON; // used by InvLabels - icon pointed to on last call +static volatile int PointedWaitCount = 0; // used by InvTinselProcess - fix the 'repeated pressing bug' +static int sX = 0; // used by SlideMSlider() - current x-coordinate +static int lX = 0; // used by SlideMSlider() - last x-coordinate + +//----- Data pertinant to configure (incl. load/save game) ----- + +#define COL_MAINBOX TBLUE1 // Base blue colour +#define COL_BOX TBLUE1 +#define COL_HILIGHT TBLUE4 + +#ifdef JAPAN +#define BOX_HEIGHT 17 +#define EDIT_BOX1_WIDTH 149 +#else +#define BOX_HEIGHT 13 +#define EDIT_BOX1_WIDTH 145 +#endif +#define EDIT_BOX2_WIDTH 166 + +// RGROUP Radio button group - 1 is selectable at a time. Action on double click +// ARSBUT Action if a radio button is selected +// AABUT Action always +// AATBUT Action always, text box +// AAGBUT Action always, graphic button +// SLIDER Not a button at all +typedef enum {RGROUP, ARSBUT, AABUT, AATBUT, ARSGBUT, AAGBUT, SLIDER, + TOGGLE, DCTEST, FLIP, FRGROUP, NOTHING} BTYPE; + +typedef enum {NOFUNC, SAVEGAME, LOADGAME, IQUITGAME, CLOSEWIN, + OPENLOAD, OPENSAVE, OPENREST, + OPENSOUND, OPENCONT, +#ifndef JAPAN + OPENSUBT, +#endif + OPENQUIT, + INITGAME, MIDIVOL, + CLANG, RLANG +#ifdef MAC_OPTIONS + , MASTERVOL, SAMPVOL +#endif + } BFUNC; + +typedef struct { + BTYPE boxType; + BFUNC boxFunc; + char *boxText; + int ixText; + int xpos; + int ypos; + int w; // Doubles as max value for SLIDERs + int h; // Doubles as iteration size for SLIDERs + int *ival; + int bi; // Base index for AAGBUTs +} CONFBOX, *PCONFBOX; + + +#define NO_HEADING (-1) +#define USE_POINTER (-1) +#define SIX_LOAD_OPTION 0 +#define SIX_SAVE_OPTION 1 +#define SIX_RESTART_OPTION 2 +#define SIX_SOUND_OPTION 3 +#define SIX_CONTROL_OPTION 4 +#ifndef JAPAN +#define SIX_SUBTITLES_OPTION 5 +#endif +#define SIX_QUIT_OPTION 6 +#define SIX_RESUME_OPTION 7 +#define SIX_LOAD_HEADING 8 +#define SIX_SAVE_HEADING 9 +#define SIX_RESTART_HEADING 10 +#define SIX_MVOL_SLIDER 11 +#define SIX_SVOL_SLIDER 12 +#define SIX_VVOL_SLIDER 13 +#define SIX_DCLICK_SLIDER 14 +#define SIX_DCLICK_TEST 15 +#define SIX_SWAP_TOGGLE 16 +#define SIX_TSPEED_SLIDER 17 +#define SIX_STITLE_TOGGLE 18 +#define SIX_QUIT_HEADING 19 + + +/*-------------------------------------------------------------*\ +| This is the main menu (that comes up when you hit F1 on a PC) | +\*-------------------------------------------------------------*/ + +#ifdef JAPAN +#define FBY 11 // y-offset of first button +#define FBX 13 // x-offset of first button +#else +#define FBY 20 // y-offset of first button +#define FBX 15 // x-offset of first button +#endif + +CONFBOX optionBox[] = { + + { AATBUT, OPENLOAD, NULL, SIX_LOAD_OPTION, FBX, FBY, EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENSAVE, NULL, SIX_SAVE_OPTION, FBX, FBY + (BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENREST, NULL, SIX_RESTART_OPTION, FBX, FBY + 2*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENSOUND, NULL, SIX_SOUND_OPTION, FBX, FBY + 3*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENCONT, NULL, SIX_CONTROL_OPTION, FBX, FBY + 4*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, +#ifdef JAPAN +// TODO: If in JAPAN mode, simply disable the subtitles button? + { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION, FBX, FBY + 5*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION, FBX, FBY + 6*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 } +#else + { AATBUT, OPENSUBT, NULL, SIX_SUBTITLES_OPTION,FBX, FBY + 5*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION, FBX, FBY + 6*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }, + { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION, FBX, FBY + 7*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 } +#endif + +}; + +/*-------------------------------------------------------------*\ +| These are the load and save game menus. | +\*-------------------------------------------------------------*/ + +#ifdef JAPAN +#define NUM_SL_RGROUP 7 // number of visible slots +#define SY 32 // y-position of first slot +#else +#define NUM_SL_RGROUP 9 // number of visible slots +#define SY 31 // y-position of first slot +#endif + +CONFBOX loadBox[NUM_SL_RGROUP+2] = { + + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#ifndef JAPAN + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#endif + { ARSGBUT, LOADGAME, NULL, USE_POINTER, 230, 44, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX_CROSS1 } + +}; + +CONFBOX saveBox[NUM_SL_RGROUP+2] = { + + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#ifndef JAPAN + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, + { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 }, +#endif + { ARSGBUT, SAVEGAME, NULL,USE_POINTER, 230, 44, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX_CROSS1 } + +}; + + +/*-------------------------------------------------------------*\ +| This is the restart confirmation 'menu'. | +\*-------------------------------------------------------------*/ + +CONFBOX restartBox[] = { + +#ifdef JAPAN + { AAGBUT, INITGAME, NULL, USE_POINTER, 96, 44, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 56, 44, 23, 19, NULL, IX_CROSS1 } +#else + { AAGBUT, INITGAME, NULL, USE_POINTER, 70, 28, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX_CROSS1 } +#endif + +}; + + +/*-------------------------------------------------------------*\ +| This is the sound control 'menu'. | +\*-------------------------------------------------------------*/ + +#ifdef MAC_OPTIONS + CONFBOX soundBox[] = { + { SLIDER, MASTERVOL, NULL, SIX_MVOL_SLIDER, 142, 20, 100, 2, &volMaster, 0 }, + { SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER, 142, 20+40, 100, 2, &volMidi, 0 }, + { SLIDER, SAMPVOL, NULL, SIX_SVOL_SLIDER, 142, 20+2*40, 100, 2, &volSound, 0 }, + { SLIDER, SAMPVOL, NULL, SIX_VVOL_SLIDER, 142, 20+3*40, 100, 2, &volVoice, 0 } + }; +#else +CONFBOX soundBox[] = { + { SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER, 142, 25, MAXMIDIVOL, 2, &volMidi, 0 }, + { SLIDER, NOFUNC, NULL, SIX_SVOL_SLIDER, 142, 25+40, MAXSAMPVOL, 2, &volSound, 0 }, + { SLIDER, NOFUNC, NULL, SIX_VVOL_SLIDER, 142, 25+2*40, MAXSAMPVOL, 2, &volVoice, 0 } +}; +#endif + + +/*-------------------------------------------------------------*\ +| This is the (mouse) control 'menu'. | +\*-------------------------------------------------------------*/ + +int bFlipped; // looks like this is just so the code has something to alter! + + +#ifdef MAC_OPTIONS +CONFBOX controlBox[] = { + + { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER, 142, 25, 3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 }, + { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST, 142, 25+30, 23, 19, &bFlipped, IX_CIRCLE1 } + +}; +#else +CONFBOX controlBox[] = { + + { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER, 142, 25, 3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 }, + { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST, 142, 25+30, 23, 19, &bFlipped, IX_CIRCLE1 }, +#ifdef JAPAN + { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE, 205, 25+70, 23, 19, &bSwapButtons, 0 } +#else + { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE, 155, 25+70, 23, 19, &bSwapButtons, 0 } +#endif + +}; +#endif + + +/*-------------------------------------------------------------*\ +| This is the subtitles 'menu'. | +\*-------------------------------------------------------------*/ + +#ifndef JAPAN +CONFBOX subtitlesBox[] = { + +#ifdef USE_5FLAGS + { FRGROUP, NOFUNC, NULL, USE_POINTER, 15, 100, 56, 32, NULL, FIX_UK }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 85, 100, 56, 32, NULL, FIX_FR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 155, 100, 56, 32, NULL, FIX_GR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 50, 137, 56, 32, NULL, FIX_IT }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 120, 137, 56, 32, NULL, FIX_SP }, +#endif +#ifdef USE_4FLAGS + { FRGROUP, NOFUNC, NULL, USE_POINTER, 20, 100, 56, 32, NULL, FIX_FR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 108, 100, 56, 32, NULL, FIX_GR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 64, 137, 56, 32, NULL, FIX_IT }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 152, 137, 56, 32, NULL, FIX_SP }, +#endif +#ifdef USE_3FLAGS + { FRGROUP, NOFUNC, NULL, USE_POINTER, 15, 118, 56, 32, NULL, FIX_FR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 85, 118, 56, 32, NULL, FIX_GR }, + { FRGROUP, NOFUNC, NULL, USE_POINTER, 155, 118, 56, 32, NULL, FIX_SP }, +#endif + + { SLIDER, NOFUNC, NULL, SIX_TSPEED_SLIDER, 142, 20, 100, 2, &speedText, 0 }, + { TOGGLE, NOFUNC, NULL, SIX_STITLE_TOGGLE, 142, 20+40, 23, 19, &bSubtitles, 0 }, + +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) + { ARSGBUT, CLANG, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, RLANG, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX_CROSS1 } +#endif + +}; +#endif + + +/*-------------------------------------------------------------*\ +| This is the quit confirmation 'menu'. | +\*-------------------------------------------------------------*/ + +CONFBOX quitBox[] = { +#ifdef JAPAN + { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 44, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 44, 23, 19, NULL, IX_CROSS1 } +#else + { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 28, 23, 19, NULL, IX_TICK1 }, + { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX_CROSS1 } +#endif +}; + + +CONFBOX topwinBox[] = { + { NOTHING, NOFUNC, NULL, USE_POINTER, 0, 0, 0, 0, NULL, 0 } +}; + + + +typedef struct { + int h; + int v; + int x; + int y; + bool bExtraWin; + PCONFBOX Box; + int NumBoxes; + int ixHeading; +} CONFINIT, *PCONFINIT; + +CONFINIT ciOption = { 6, 5, 72, 23, false, optionBox, sizeof(optionBox)/sizeof(CONFBOX), NO_HEADING }; + +CONFINIT ciLoad = { 10, 6, 20, 16, true, loadBox, sizeof(loadBox)/sizeof(CONFBOX), SIX_LOAD_HEADING }; +CONFINIT ciSave = { 10, 6, 20, 16, true, saveBox, sizeof(saveBox)/sizeof(CONFBOX), SIX_SAVE_HEADING }; +#ifdef JAPAN +CONFINIT ciRestart = { 6, 2, 72, 53, false, restartBox, sizeof(restartBox)/sizeof(CONFBOX), SIX_RESTART_HEADING }; +#else +CONFINIT ciRestart = { 4, 2, 98, 53, false, restartBox, sizeof(restartBox)/sizeof(CONFBOX), SIX_RESTART_HEADING }; +#endif +CONFINIT ciSound = { 10, 5, 20, 16, false, soundBox, sizeof(soundBox)/sizeof(CONFBOX), NO_HEADING }; +#ifdef MAC_OPTIONS + CONFINIT ciControl = { 10, 3, 20, 40, false, controlBox, sizeof(controlBox)/sizeof(CONFBOX), NO_HEADING }; +#else + CONFINIT ciControl = { 10, 5, 20, 16, false, controlBox, sizeof(controlBox)/sizeof(CONFBOX), NO_HEADING }; +#endif +#ifndef JAPAN +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) +CONFINIT ciSubtitles = { 10, 6, 20, 16, false, subtitlesBox, sizeof(subtitlesBox)/sizeof(CONFBOX), NO_HEADING }; +#else +CONFINIT ciSubtitles = { 10, 3, 20, 16, false, subtitlesBox, sizeof(subtitlesBox)/sizeof(CONFBOX), NO_HEADING }; +#endif +#endif +CONFINIT ciQuit = { 4, 2, 98, 53, false, quitBox, sizeof(quitBox)/sizeof(CONFBOX), SIX_QUIT_HEADING }; + +CONFINIT ciTopWin = { 6, 5, 72, 23, false, topwinBox, 0, NO_HEADING }; + +#define NOBOX (-1) + +// Conf window globals +static struct { + PCONFBOX Box; + int NumBoxes; + bool bExtraWin; + int ixHeading; + bool editableRgroup; + + int selBox; + int pointBox; // Box pointed to on last call + int saveModifier; + int fileBase; + int numSaved; +} cd = { + NULL, 0, false, 0, false, + NOBOX, NOBOX, 0, 0, 0 +}; + +// For editing save game names +char sedit[SG_DESC_LEN+2]; + +#define HL1 0 // Hilight that moves with the cursor +#define HL2 1 // Hilight on selected RGROUP box +#define HL3 2 // Text on selected RGROUP box +#define NUMHL 3 + + +// Data for button press/toggle effects +static struct { + bool bButAnim; + PCONFBOX box; + bool press; // true = button press; false = button toggle +} g_buttonEffect = { false, 0, false }; + + +//----- LOCAL FORWARD REFERENCES ----- + +enum { + IB_NONE = -1, // + IB_UP = -2, // negative numbers returned + IB_DOWN = -3, // by WhichInvBox() + IB_SLIDE = -4, // + IB_SLIDE_UP = -5, // + IB_SLIDE_DOWN = -6 // +}; + +enum { + HI_BIT = ((uint)MIN_INT >> 1), // The next to top bit + IS_LEFT = HI_BIT, + IS_SLIDER = (IS_LEFT >> 1), + IS_RIGHT = (IS_SLIDER >> 1), + IS_MASK = (IS_LEFT | IS_SLIDER | IS_RIGHT) +}; + +static int WhichInvBox(int curX, int curY, bool bSlides); +static void SlideMSlider(int x, SSFN fn); +static OBJECT *AddObject(const FREEL *pfreel, int num); +static void AddBoxes(bool posnSlide); + +static void ConfActionSpecial(int i); + + +/*-------------------------------------------------------------------------*/ +/*** Magic numbers ***/ + +#define M_SW 5 // Side width +#define M_TH 5 // Top height +#ifdef JAPAN +#define M_TOFF 6 // Title text Y offset from top +#define M_TBB 20 // Title box bottom Y offset +#else +#define M_TOFF 4 // Title text Y offset from top +#define M_TBB 14 // Title box bottom Y offset +#endif +#define M_SBL 26 // Scroll bar left X offset +#define M_SH 5 // Slider height (*) +#define M_SW 5 // Slider width (*) +#define M_SXOFF 9 // Slider X offset from right-hand side +#ifdef JAPAN +#define M_IUT 22 // Y offset of top of up arrow +#define M_IUB 30 // Y offset of bottom of up arrow +#else +#define M_IUT 16 // Y offset of top of up arrow +#define M_IUB 24 // Y offset of bottom of up arrow +#endif +#define M_IDT 10 // Y offset (from bottom) of top of down arrow +#define M_IDB 3 // Y offset (from bottom) of bottom of down arrow +#define M_IAL 12 // X offset (from right) of left of scroll arrows +#define M_IAR 3 // X offset (from right) of right of scroll arrows + +#define START_ICONX (M_SW+1) // } Relative offset of first icon +#define START_ICONY (M_TBB+M_TH+1) // } within the inventory window + +/*-------------------------------------------------------------------------*/ + + + +#ifndef JAPAN +bool LanguageChange(void) { + LANGUAGE nLang; + +#ifdef USE_3FLAGS + // VERY quick dodgy bodge + if (cd.selBox == 0) + nLang = TXT_FRENCH; + else if (cd.selBox == 1) + nLang = TXT_GERMAN; + else + nLang = TXT_SPANISH; + if (nLang != language) { +#elif defined(USE_4FLAGS) + nLang = (LANGUAGE)(cd.selBox + 1); + if (nLang != language) { +#else + if (cd.selBox != language) { + nLang = (LANGUAGE)cd.selBox; +#endif + KillInventory(); + ChangeLanguage(nLang); + language = nLang; + return true; + } + else + return false; +} +#endif + +/**************************************************************************/ +/******************** Some miscellaneous functions ************************/ +/**************************************************************************/ + +/*---------------------------------------------------------------------*\ +| DumpIconArray()/DumpDobjArray()/DumpObjArray() | +|-----------------------------------------------------------------------| +| Delete all the objects in iconArray[]/DobjArray[]/objArray[] | +\*---------------------------------------------------------------------*/ +static void DumpIconArray(void){ + for (int i = 0; i < MAX_ICONS; i++) { + if (iconArray[i] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[i]); + iconArray[i] = NULL; + } + } +} + +/** + * Delete all the objects in DobjArray[] + */ + +static void DumpDobjArray(void) { + for (int i = 0; i < MAX_WCOMP; i++) { + if (DobjArray[i] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), DobjArray[i]); + DobjArray[i] = NULL; + } + } +} + +/** + * Delete all the objects in objArray[] + */ + +static void DumpObjArray(void) { + for (int i = 0; i < MAX_WCOMP; i++) { + if (objArray[i] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), objArray[i]); + objArray[i] = NULL; + } + } +} + +/** + * Convert item ID number to pointer to item's compiled data + * i.e. Image data and Glitter code. + */ +PINV_OBJECT findInvObject(int num) { + PINV_OBJECT retval = pio; + + for (int i = 0; i < numObjects; i++, retval++) { + if (retval->id == num) + return retval; + } + + error("Trying to manipulate undefined inventory icon"); +} + +/** + * Returns position of an item in one of the inventories. + * The actual position is not important for the uses that this is put to. + */ + +int InventoryPos(int num) { + int i; + + for (i = 0; i < InvD[INV_1].NoofItems; i++) // First inventory + if (InvD[INV_1].ItemOrder[i] == num) + return i; + + for (i = 0; i < InvD[INV_2].NoofItems; i++) // Second inventory + if (InvD[INV_2].ItemOrder[i] == num) + return i; + + if (HeldItem == num) + return INV_HELDNOTIN; // Held, but not in either inventory + + return INV_NOICON; // Not held, not in either inventory +} + +bool IsInInventory(int object, int invnum) { + assert(invnum == INV_1 || invnum == INV_2); + + for (int i = 0; i < InvD[invnum].NoofItems; i++) // First inventory + if (InvD[invnum].ItemOrder[i] == object) + return true; + + return false; +} + +/** + * Returns which item is held (INV_NOICON (-1) if none) + */ + +int WhichItemHeld(void) { + return HeldItem; +} + +/** + * Called from the cursor module when it re-initialises (at the start of + * a new scene). For if we are holding something at scene-change time. + */ + +void InventoryIconCursor(void) { + PINV_OBJECT invObj; + + if (HeldItem != INV_NOICON) { + invObj = findInvObject(HeldItem); + SetAuxCursor(invObj->hFilm); + } +} + +/** + * Returns TRUE if the inventory is active. + */ + +bool InventoryActive(void) { + return (InventoryState == ACTIVE_INV); +} + +int WhichInventoryOpen(void) { + if (InventoryState != ACTIVE_INV) + return 0; + else + return ino; +} + + +/**************************************************************************/ +/************** Running inventory item's Glitter code *********************/ +/**************************************************************************/ + +struct ITP_INIT { + PINV_OBJECT pinvo; + USER_EVENT event; + BUTEVENT bev; +}; + +/** + * Run inventory item's Glitter code + */ +static void InvTinselProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + PINT_CONTEXT pic; + int ThisPointedWait; // Fix the 'repeated pressing bug' + CORO_END_CONTEXT(_ctx); + + // get the stuff copied to process when it was created + ITP_INIT *to = (ITP_INIT *)ProcessGetParamsSelf(); + + CORO_BEGIN_CODE(_ctx); + + CORO_INVOKE_1(AllowDclick, to->bev); + + _ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, to->event, NOPOLY, 0, to->pinvo); + CORO_INVOKE_1(Interpret, _ctx->pic); + + if (to->event == POINTED) { + _ctx->ThisPointedWait = ++PointedWaitCount; + while (1) { + CORO_SLEEP(1); + int x, y; + GetCursorXY(&x, &y, false); + if (InvItemId(x, y) != to->pinvo->id) + break; + + // Fix the 'repeated pressing bug' + if (_ctx->ThisPointedWait != PointedWaitCount) + CORO_KILL_SELF(); + } + + _ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, UNPOINT, NOPOLY, 0, to->pinvo); + CORO_INVOKE_1(Interpret, _ctx->pic); + } + + CORO_END_CODE; +} + +/** + * Run inventory item's Glitter code + */ +void RunInvTinselCode(PINV_OBJECT pinvo, USER_EVENT event, BUTEVENT be, int index) { + ITP_INIT to = { pinvo, event, be }; + + if (InventoryHidden) + return; + + GlitterIndex = index; + CoroutineInstall(PID_TCODE, InvTinselProcess, &to, sizeof(to)); +} + +/**************************************************************************/ +/****************** Load/Save game specific functions *********************/ +/**************************************************************************/ + +/** + * Set first load/save file entry displayed. + * Point Box[] text pointers to appropriate file descriptions. + */ + +void firstFile(int first) { + int i, j; + + i = getList(); + + cd.numSaved = i; + + if (first < 0) + first = 0; + else if (first > MAX_SFILES-NUM_SL_RGROUP) + first = MAX_SFILES-NUM_SL_RGROUP; + + if (first == 0 && i < MAX_SFILES && cd.Box == saveBox) { + // Blank first entry for new save + cd.Box[0].boxText = NULL; + cd.saveModifier = j = 1; + } else { + cd.saveModifier = j = 0; + } + + for (i = first; j < NUM_SL_RGROUP; j++, i++) { + cd.Box[j].boxText = ListEntry(i, LE_DESC); + } + + cd.fileBase = first; +} + +/** + * Save the game using filename from selected slot & current description. + */ + +void InvSaveGame(void) { + if (cd.selBox != NOBOX) { +#ifndef JAPAN + sedit[strlen(sedit)-1] = 0; // Don't include the cursor! +#endif + SaveGame(ListEntry(cd.selBox-cd.saveModifier+cd.fileBase, LE_NAME), sedit); + } +} + +/** + * Load the selected saved game. + */ +void InvLoadGame(void) { + int rGame; + + if (cd.selBox != NOBOX && (cd.selBox+cd.fileBase < cd.numSaved)) { + rGame = cd.selBox; + cd.selBox = NOBOX; + if (iconArray[HL3] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); + iconArray[HL3] = NULL; + } + if (iconArray[HL2] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); + iconArray[HL2] = NULL; + } + if (iconArray[HL1] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = NULL; + } + RestoreGame(rGame+cd.fileBase); + } +} + +/** + * Edit the string in sedit[] + * Returns TRUE if the string was altered. + */ +#ifndef JAPAN +bool UpdateString(const Common::KeyState &kbd) { + int cpos; + + if (!cd.editableRgroup) + return false; + + cpos = strlen(sedit)-1; + + if (kbd.keycode == Common::KEYCODE_BACKSPACE) { + if (!cpos) + return false; + sedit[cpos] = 0; + cpos--; + sedit[cpos] = CURSOR_CHAR; + return true; +// } else if (isalnum(c) || c == ',' || c == '.' || c == '\'' || (c == ' ' && cpos != 0)) { + } else if (IsCharImage(hTagFontHandle(), kbd.ascii) || (kbd.ascii == ' ' && cpos != 0)) { + if (cpos == SG_DESC_LEN) + return false; + sedit[cpos] = kbd.ascii; + cpos++; + sedit[cpos] = CURSOR_CHAR; + sedit[cpos+1] = 0; + return true; + } + return false; +} +#endif + +/** + * Keystrokes get sent here when load/save screen is up. + */ +bool InvKeyIn(const Common::KeyState &kbd) { + if (kbd.keycode == Common::KEYCODE_PAGEUP || + kbd.keycode == Common::KEYCODE_PAGEDOWN || + kbd.keycode == Common::KEYCODE_HOME || + kbd.keycode == Common::KEYCODE_END) + return true; // Key needs processing + + if (kbd.keycode == 0 && kbd.ascii == 0) { + ; + } else if (kbd.keycode == Common::KEYCODE_RETURN) { + return true; // Key needs processing + } else if (kbd.keycode == Common::KEYCODE_ESCAPE) { + return true; // Key needs processing + } else { +#ifndef JAPAN + if (UpdateString(kbd)) { + /* + * Delete display of text currently being edited, + * and replace it with freshly edited text. + */ + if (iconArray[HL3] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); + iconArray[HL3] = NULL; + } + iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0, + InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2, + InvD[ino].inventoryY + cd.Box[cd.selBox].ypos, + hTagFontHandle(), 0); + if (MultiRightmost(iconArray[HL3]) > 213) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); + UpdateString(Common::KeyState(Common::KEYCODE_BACKSPACE)); + iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0, + InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2, + InvD[ino].inventoryY + cd.Box[cd.selBox].ypos, + hTagFontHandle(), 0); + } + MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2); + } +#endif + } + return false; +} + +/*---------------------------------------------------------------------*\ +| Select() | +|-----------------------------------------------------------------------| +| Highlights selected box. | +| If it's editable (save game), copy existing description and add a | +| cursor. | +\*---------------------------------------------------------------------*/ +void Select(int i, bool force) { +#ifdef JAPAN + time_t secs_now; + struct tm *time_now; +#endif + + i &= ~IS_MASK; + + if (cd.selBox == i && !force) + return; + + cd.selBox = i; + + // Clear previous selected highlight and text + if (iconArray[HL2] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); + iconArray[HL2] = NULL; + } + if (iconArray[HL3] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]); + iconArray[HL3] = NULL; + } + + // New highlight box + switch (cd.Box[i].boxType) { + case RGROUP: + iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w, cd.Box[i].h); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); + MultiSetAniXY(iconArray[HL2], + InvD[ino].inventoryX + cd.Box[i].xpos, + InvD[ino].inventoryY + cd.Box[i].ypos); + + // Z-position of box, and add edit text if appropriate + if (cd.editableRgroup) { + MultiSetZPosition(iconArray[HL2], Z_INV_ITEXT+1); + + assert(cd.Box[i].ixText == USE_POINTER); +#ifdef JAPAN + // Current date and time + time(&secs_now); + time_now = localtime(&secs_now); + strftime(sedit, SG_DESC_LEN, "%D %H:%M", time_now); +#else + // Current description with cursor appended + if (cd.Box[i].boxText != NULL) { + strcpy(sedit, cd.Box[i].boxText); + strcat(sedit, sCursor); + } else { + strcpy(sedit, sCursor); + } +#endif + iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0, + InvD[ino].inventoryX + cd.Box[i].xpos + 2, +#ifdef JAPAN + InvD[ino].inventoryY + cd.Box[i].ypos + 2, +#else + InvD[ino].inventoryY + cd.Box[i].ypos, +#endif + hTagFontHandle(), 0); + MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2); + } else { + MultiSetZPosition(iconArray[HL2], Z_INV_ICONS + 1); + } + + _vm->divertKeyInput(InvKeyIn); + + break; + +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) + case FRGROUP: + iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w+6, cd.Box[i].h+6); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]); + MultiSetAniXY(iconArray[HL2], + InvD[ino].inventoryX + cd.Box[i].xpos - 2, + InvD[ino].inventoryY + cd.Box[i].ypos - 2); + MultiSetZPosition(iconArray[HL2], Z_INV_BRECT+1); + + break; +#endif + default: + break; + } +} + + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/** + * If the item is not already held, hold it. + */ + +void HoldItem(int item) { + PINV_OBJECT invObj; + + if (HeldItem != item) { + if (item == INV_NOICON && HeldItem != INV_NOICON) + DelAuxCursor(); // no longer aux cursor + + if (item != INV_NOICON) { + invObj = findInvObject(item); + SetAuxCursor(invObj->hFilm); // and is aux. cursor + } + + HeldItem = item; // Item held + } + + // Redraw contents - held item not displayed as a content. + ItemsChanged = true; +} + +/** + * Stop holding an item. + */ + +void DropItem(int item) { + if (HeldItem == item) { + HeldItem = INV_NOICON; // Item not held + DelAuxCursor(); // no longer aux cursor + } + + // Redraw contents - held item was not displayed as a content. + ItemsChanged = true; +} + +/** + * Stick the item into an inventory list (ItemOrder[]), and hold the + * item if requested. + */ + +void AddToInventory(int invno, int icon, bool hold) { + int i; + bool bOpen; +#ifdef DEBUG + PINV_OBJECT invObj; +#endif + + assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_OPEN)); // Trying to add to illegal inventory + + if (invno == INV_OPEN) { + assert(InventoryState == ACTIVE_INV && (ino == INV_1 || ino == INV_2)); // addopeninv() with inventry not open + invno = ino; + bOpen = true; + + // Make sure it doesn't get in both! + RemFromInventory(ino == INV_1 ? INV_2 : INV_1, icon); + } else + bOpen = false; + +#ifdef DEBUG + invObj = findInvObject(icon); + if ((invObj->attribute & IO_ONLYINV1 && invno != INV_1) + || (invObj->attribute & IO_ONLYINV2 && invno != INV_2)) + error("Trying to add resticted object to wrong inventory"); +#endif + + if (invno == INV_1) + RemFromInventory(INV_2, icon); + else if (invno == INV_2) + RemFromInventory(INV_1, icon); + + // See if it's already there + for (i = 0; i < InvD[invno].NoofItems; i++) { + if (InvD[invno].ItemOrder[i] == icon) + break; + } + + // Add it if it isn't already there + if (i == InvD[invno].NoofItems) { + if (!bOpen) { + if (invno == INV_CONV) { + // For conversation, insert before last icon + // which will always be the goodbye icon + InvD[invno].ItemOrder[InvD[invno].NoofItems] = InvD[invno].ItemOrder[InvD[invno].NoofItems-1]; + InvD[invno].ItemOrder[InvD[invno].NoofItems-1] = icon; + InvD[invno].NoofItems++; + } else { + InvD[invno].ItemOrder[InvD[invno].NoofItems++] = icon; + } + ItemsChanged = true; + } else { + // It could be that the index is beyond what you'd expect + // as delinv may well have been called + if (GlitterIndex < InvD[invno].NoofItems) { + memmove(&InvD[invno].ItemOrder[GlitterIndex + 1], + &InvD[invno].ItemOrder[GlitterIndex], + (InvD[invno].NoofItems-GlitterIndex)*sizeof(int)); + InvD[invno].ItemOrder[GlitterIndex] = icon; + } else { + InvD[invno].ItemOrder[InvD[invno].NoofItems] = icon; + } + InvD[invno].NoofItems++; + } + } + + // Hold it if requested + if (hold) + HoldItem(icon); +} + +/** + * Take the item from the inventory list (ItemOrder[]). + * Return FALSE if item wasn't present, true if it was. + */ + +bool RemFromInventory(int invno, int icon) { + int i; + + assert(invno == INV_1 || invno == INV_2 || invno == INV_CONV); // Trying to delete from illegal inventory + + // See if it's there + for (i = 0; i < InvD[invno].NoofItems; i++) { + if (InvD[invno].ItemOrder[i] == icon) + break; + } + + if (i == InvD[invno].NoofItems) + return false; // Item wasn't there + else { + memmove(&InvD[invno].ItemOrder[i], &InvD[invno].ItemOrder[i+1], (InvD[invno].NoofItems-i)*sizeof(int)); + InvD[invno].NoofItems--; + ItemsChanged = true; + return true; // Item removed + } +} + + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/*---------------------------------------------------------------------*\ +| InvArea() | +|-----------------------------------------------------------------------| +| Work out which area of the inventory window the cursor is in. | +|-----------------------------------------------------------------------| +| This used to be worked out with appropriately defined magic numbers. | +| Then the graphic changed and I got it right again. Then the graphic | +| changed and I got fed up of faffing about. It's probably easier just | +| to rework all this. | +\*---------------------------------------------------------------------*/ +enum { I_NOTIN, I_MOVE, I_BODY, + I_TLEFT, I_TRIGHT, I_BLEFT, I_BRIGHT, + I_TOP, I_BOTTOM, I_LEFT, I_RIGHT, + I_UP, I_SLIDE_UP, I_SLIDE, I_SLIDE_DOWN, I_DOWN, + I_ENDCHANGE +}; + +#define EXTRA 1 // This was introduced when we decided to increase + // the active area of the borders for re-sizing. + +/*---------------------------------*/ +#define LeftX InvD[ino].inventoryX +#define TopY InvD[ino].inventoryY +/*---------------------------------*/ + +int InvArea(int x, int y) { + int RightX = MultiRightmost(RectObject) + 1; + int BottomY = MultiLowest(RectObject) + 1; + +// Outside the whole rectangle? + if (x <= LeftX - EXTRA || x > RightX + EXTRA + || y <= TopY - EXTRA || y > BottomY + EXTRA) + return I_NOTIN; + +// The bottom line + if (y > BottomY - 2 - EXTRA) { // Below top of bottom line? + if (x <= LeftX + 2 + EXTRA) + return I_BLEFT; // Bottom left corner + else if (x > RightX - 2 - EXTRA) + return I_BRIGHT; // Bottom right corner + else + return I_BOTTOM; // Just plain bottom + } + +// The top line + if (y <= TopY + 2 + EXTRA) { // Above bottom of top line? + if (x <= LeftX + 2 + EXTRA) + return I_TLEFT; // Top left corner + else if (x > RightX - 2 - EXTRA) + return I_TRIGHT; // Top right corner + else + return I_TOP; // Just plain top + } + +// Sides + if (x <= LeftX + 2 + EXTRA) // Left of right of left side? + return I_LEFT; + else if (x > RightX - 2 - EXTRA) // Right of left of right side? + return I_RIGHT; + +// From here down still needs fixing up properly +/* +* In the move area? +*/ + if (ino != INV_CONF + && x >= LeftX + M_SW - 2 && x <= RightX - M_SW + 3 && + y >= TopY + M_TH - 2 && y < TopY + M_TBB + 2) + return I_MOVE; + +/* +* Scroll bits +*/ + if (ino == INV_CONF && cd.bExtraWin) { + } else { + if (x > RightX - M_IAL + 3 && x <= RightX - M_IAR + 1) { + if (y > TopY + M_IUT + 1 && y < TopY + M_IUB - 1) + return I_UP; + if (y > BottomY - M_IDT + 4 && y <= BottomY - M_IDB + 1) + return I_DOWN; + + if (y >= TopY + slideYmin && y < TopY + slideYmax + M_SH) { + if (y < TopY + slideY) + return I_SLIDE_UP; + if (y < TopY + slideY + M_SH) + return I_SLIDE; + else + return I_SLIDE_DOWN; + } + } + } + + return I_BODY; +} + +/** + * Returns the id of the icon displayed under the given position. + * Also return co-ordinates of items tag display position, if requested. + */ + +int InvItem(int *x, int *y, bool update) { + int itop, ileft; + int row, col; + int item; + int IconsX; + + itop = InvD[ino].inventoryY + START_ICONY; + + IconsX = InvD[ino].inventoryX + START_ICONX; + + for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) { + ileft = IconsX; + + for (col = 0; col < InvD[ino].NoofHicons; col++, item++) { + if (*x >= ileft && *x < ileft + ITEM_WIDTH && + *y >= itop && *y < itop + ITEM_HEIGHT) { + if (update) { + *x = ileft + ITEM_WIDTH/2; + *y = itop /*+ ITEM_HEIGHT/4*/; + } + return item; + } + + ileft += ITEM_WIDTH + 1; + } + itop += ITEM_HEIGHT + 1; + } + return INV_NOICON; +} + +/** + * Returns the id of the icon displayed under the given position. + */ + +int InvItemId(int x, int y) { + int itop, ileft; + int row, col; + int item; + + if (InventoryHidden || InventoryState == IDLE_INV) + return INV_NOICON; + + itop = InvD[ino].inventoryY + START_ICONY; + + int IconsX = InvD[ino].inventoryX + START_ICONX; + + for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) { + ileft = IconsX; + + for (col = 0; col < InvD[ino].NoofHicons; col++, item++) { + if (x >= ileft && x < ileft + ITEM_WIDTH && + y >= itop && y < itop + ITEM_HEIGHT) { + return InvD[ino].ItemOrder[item]; + } + + ileft += ITEM_WIDTH + 1; + } + itop += ITEM_HEIGHT + 1; + } + return INV_NOICON; +} + +/*---------------------------------------------------------------------*\ +| WhichInvBox() | +|-----------------------------------------------------------------------| +| Finds which box the cursor is in. | +\*---------------------------------------------------------------------*/ +#define MD_YSLIDTOP 7 +#define MD_YSLIDBOT 18 +#define MD_YBUTTOP 9 +#define MD_YBUTBOT 16 +#define MD_XLBUTL 1 +#define MD_XLBUTR 10 +#define MD_XRBUTL 105 +#define MD_XRBUTR 114 + +static int WhichInvBox(int curX, int curY, bool bSlides) { + if (bSlides) { + for (int i = 0; i < numMdSlides; i++) { + if (curY > MultiHighest(mdSlides[i].obj) && curY < MultiLowest(mdSlides[i].obj) + && curX > MultiLeftmost(mdSlides[i].obj) && curX < MultiRightmost(mdSlides[i].obj)) + return mdSlides[i].num | IS_SLIDER; + } + } + + curX -= InvD[ino].inventoryX; + curY -= InvD[ino].inventoryY; + + for (int i = 0; i < cd.NumBoxes; i++) { + switch (cd.Box[i].boxType) { + case SLIDER: + if (bSlides) { + if (curY >= cd.Box[i].ypos+MD_YBUTTOP && curY < cd.Box[i].ypos+MD_YBUTBOT) { + if (curX >= cd.Box[i].xpos+MD_XLBUTL && curX < cd.Box[i].xpos+MD_XLBUTR) + return i | IS_LEFT; + if (curX >= cd.Box[i].xpos+MD_XRBUTL && curX < cd.Box[i].xpos+MD_XRBUTR) + return i | IS_RIGHT; + } + } + break; + + case AAGBUT: + case ARSGBUT: + case TOGGLE: + case FLIP: + if (curY > cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h + && curX > cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w) + return i; + break; + + default: + // 'Normal' box + if (curY >= cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h + && curX >= cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w) + return i; + break; + } + } + + if (cd.bExtraWin) { + if (curX > 20 + 181 && curX < 20 + 181 + 8 && + curY > 24 + 2 && curY < 24 + 139 + 5) { + + if (curY < 24 + 2 + 5) { + return IB_UP; + } else if (curY > 24 + 139) { + return IB_DOWN; + } else if (curY+InvD[ino].inventoryY >= slideY && curY+InvD[ino].inventoryY < slideY + 5) { + return IB_SLIDE; + } else if (curY+InvD[ino].inventoryY < slideY) { + return IB_SLIDE_UP; + } else if (curY+InvD[ino].inventoryY >= slideY + 5) { + return IB_SLIDE_DOWN; + } + } + } + + return IB_NONE; +} + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/** + * InBoxes + */ +void InvBoxes(bool InBody, int curX, int curY) { + int index; // Box pointed to on this call + const FILM *pfilm; + + // Find out which icon is currently pointed to + if (!InBody) + index = -1; + else { + index = WhichInvBox(curX, curY, false); + } + + // If no icon pointed to, or points to (logical position of) + // currently held icon, then no icon is pointed to! + if (index < 0) { + // unhigh-light box (if one was) + cd.pointBox = NOBOX; + if (iconArray[HL1] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = NULL; + } + } else if (index != cd.pointBox) { + cd.pointBox = index; + // A new box is pointed to - high-light it + if (iconArray[HL1] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = NULL; + } + if ((cd.Box[cd.pointBox].boxType == ARSBUT && cd.selBox != NOBOX) || +///* I don't agree */ cd.Box[cd.pointBox].boxType == RGROUP || + cd.Box[cd.pointBox].boxType == AATBUT || + cd.Box[cd.pointBox].boxType == AABUT) { + iconArray[HL1] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[cd.pointBox].w, cd.Box[cd.pointBox].h); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + MultiSetAniXY(iconArray[HL1], + InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos, + InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + } + else if (cd.Box[cd.pointBox].boxType == AAGBUT || + cd.Box[cd.pointBox].boxType == ARSGBUT || + cd.Box[cd.pointBox].boxType == TOGGLE) { + pfilm = (const FILM *)LockMem(winPartsf); + + iconArray[HL1] = AddObject(&pfilm->reels[cd.Box[cd.pointBox].bi+HIGRAPH], -1); + MultiSetAniXY(iconArray[HL1], + InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos, + InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + } + } +} + +static void ButtonPress(CORO_PARAM, PCONFBOX box) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + const FILM *pfilm; + + assert(box->boxType == AAGBUT || box->boxType == ARSGBUT); + + // Replace highlight image with normal image + pfilm = (const FILM *)LockMem(winPartsf); + if (iconArray[HL1] != NULL) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + pfilm = (const FILM *)LockMem(winPartsf); + iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1); + MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + + // Hold normal image for 1 frame + CORO_SLEEP(1); + if (iconArray[HL1] == NULL) + return; + + // Replace normal image with depresses image + pfilm = (const FILM *)LockMem(winPartsf); + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1); + MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + + // Hold depressed image for 2 frames + CORO_SLEEP(2); + if (iconArray[HL1] == NULL) + return; + + // Replace depressed image with normal image + pfilm = (const FILM *)LockMem(winPartsf); + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1); + MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + + CORO_SLEEP(1); + + CORO_END_CODE; +} + +static void ButtonToggle(CORO_PARAM, PCONFBOX box) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + const FILM *pfilm; + + assert(box->boxType == TOGGLE); + + // Remove hilight image + if (iconArray[HL1] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = NULL; + } + + // Hold normal image for 1 frame + CORO_SLEEP(1); + if (InventoryState != ACTIVE_INV) + return; + + // Add depressed image + pfilm = (const FILM *)LockMem(winPartsf); + iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1); + MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + + // Hold depressed image for 1 frame + CORO_SLEEP(1); + if (iconArray[HL1] == NULL) + return; + + // Toggle state + (*box->ival) = *(box->ival) ^ 1; // XOR with true + box->bi = *(box->ival) ? IX_TICK1 : IX_CROSS1; + AddBoxes(false); + // Keep highlight (e.g. flag) + if (cd.selBox != NOBOX) + Select(cd.selBox, true); + + // New state, depressed image + pfilm = (const FILM *)LockMem(winPartsf); + if (iconArray[HL1] != NULL) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1); + MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + + // Hold new depressed image for 1 frame + CORO_SLEEP(1); + if (iconArray[HL1] == NULL) + return; + + // New state, normal + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = NULL; + + // Hold normal image for 1 frame + CORO_SLEEP(1); + if (InventoryState != ACTIVE_INV) + return; + + // New state, highlighted + pfilm = (const FILM *)LockMem(winPartsf); + if (iconArray[HL1] != NULL) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]); + iconArray[HL1] = AddObject(&pfilm->reels[box->bi+HIGRAPH], -1); + MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos); + MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1); + + CORO_END_CODE; +} + +/** + * Monitors for POINTED event for inventory icons. + */ + +void InvLabels(bool InBody, int aniX, int aniY) { + int index; // Icon pointed to on this call + PINV_OBJECT invObj; + + // Find out which icon is currently pointed to + if (!InBody) + index = INV_NOICON; + else { + index = InvItem(&aniX, &aniY, false); + if (index != INV_NOICON) { + if (index >= InvD[ino].NoofItems) + index = INV_NOICON; + else + index = InvD[ino].ItemOrder[index]; + } + } + + // If no icon pointed to, or points to (logical position of) + // currently held icon, then no icon is pointed to! + if (index == INV_NOICON || index == HeldItem) { + pointedIcon = INV_NOICON; + } else if (index != pointedIcon) { + // A new icon is pointed to - run its script with POINTED event + invObj = findInvObject(index); + if (invObj->hScript) + RunInvTinselCode(invObj, POINTED, BE_NONE, index); + pointedIcon = index; + } +} + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/** + * All to do with the slider. + * I can't remember how it works - or, indeed, what it does. + * It seems to set up slideStuff[], an array of possible first-displayed + * icons set against the matching y-positions of the slider. + */ + +void AdjustTop(void) { + int tMissing, bMissing, nMissing; + int nslideY; + int rowsWanted; + int slideRange; + int n, i; + + // Only do this if there's a slider + if (!SlideObject) + return; + + rowsWanted = (InvD[ino].NoofItems - InvD[ino].FirstDisp + InvD[ino].NoofHicons-1) / InvD[ino].NoofHicons; + + while (rowsWanted < InvD[ino].NoofVicons) { + if (InvD[ino].FirstDisp) { + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + if (InvD[ino].FirstDisp < 0) + InvD[ino].FirstDisp = 0; + rowsWanted++; + } else + break; + } + tMissing = InvD[ino].FirstDisp ? (InvD[ino].FirstDisp + InvD[ino].NoofHicons-1)/InvD[ino].NoofHicons : 0; + bMissing = (rowsWanted > InvD[ino].NoofVicons) ? rowsWanted - InvD[ino].NoofVicons : 0; + + nMissing = tMissing + bMissing; + slideRange = slideYmax - slideYmin; + + if (!tMissing) + nslideY = slideYmin; + else if (!bMissing) + nslideY = slideYmax; + else { + nslideY = tMissing*slideRange/nMissing; + nslideY += slideYmin; + } + + if (nMissing) { + n = InvD[ino].FirstDisp - tMissing*InvD[ino].NoofHicons; + for (i = 0; i <= nMissing; i++, n += InvD[ino].NoofHicons) { + slideStuff[i].n = n; + slideStuff[i].y = (i*slideRange/nMissing) + slideYmin; + } + if (slideStuff[0].n < 0) + slideStuff[0].n = 0; + assert(i < MAX_ININV + 1); + slideStuff[i].n = -1; + } else { + slideStuff[0].n = 0; + slideStuff[0].y = slideYmin; + slideStuff[1].n = -1; + } + + if (nslideY != slideY) { + MultiMoveRelXY(SlideObject, 0, nslideY - slideY); + slideY = nslideY; + } +} + +/** + * Insert an inventory icon object onto the display list. + */ + +OBJECT *AddInvObject(int num, const FREEL **pfreel, const FILM **pfilm) { + PINV_OBJECT invObj; // Icon data + const MULTI_INIT *pmi; // Its INIT structure - from the reel + PIMAGE pim; // ... you get the picture + OBJECT * pPlayObj; // The object we insert + + invObj = findInvObject(num); + + // Get pointer to image + pim = GetImageFromFilm(invObj->hFilm, 0, pfreel, &pmi, pfilm); + + // Poke in the background palette + pim->hImgPal = TO_LE_32(BackPal()); + + // Set up the multi-object + pPlayObj = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj); + + return pPlayObj; +} + +/** + * Create display objects for the displayed icons in an inventory window. + */ + +void FillInInventory(void) { + int Index; // Index into ItemOrder[] + int n = 0; // index into iconArray[] + int xpos, ypos; + int row, col; + const FREEL *pfr; + const FILM *pfilm; + + DumpIconArray(); + + if (InvDragging != ID_SLIDE) + AdjustTop(); // Set up slideStuff[] + + Index = InvD[ino].FirstDisp; // Start from first displayed object + n = 0; + ypos = START_ICONY; // Y-offset of first display row + + for (row = 0; row < InvD[ino].NoofVicons; row++, ypos += ITEM_HEIGHT + 1) { + xpos = START_ICONX; // X-offset of first display column + + for (col = 0; col < InvD[ino].NoofHicons; col++) { + if (Index >= InvD[ino].NoofItems) + break; + else if (InvD[ino].ItemOrder[Index] != HeldItem) { + // Create a display object and position it + iconArray[n] = AddInvObject(InvD[ino].ItemOrder[Index], &pfr, &pfilm); + MultiSetAniXY(iconArray[n], InvD[ino].inventoryX + xpos , InvD[ino].inventoryY + ypos); + MultiSetZPosition(iconArray[n], Z_INV_ICONS); + + InitStepAnimScript(&iconAnims[n], iconArray[n], FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + + n++; + } + Index++; + xpos += ITEM_WIDTH + 1; // X-offset of next display column + } + } +} + +/** + * Set up a rectangle as the background to the inventory window. + * Additionally, sticks the window title up. + */ + +enum {FROM_HANDLE, FROM_STRING}; + +void AddBackground(OBJECT **rect, OBJECT **title, int extraH, int extraV, int textFrom) { + // Why not 2 ???? + int width = TLwidth + extraH + TRwidth - 3; + int height = TLheight + extraV + BLheight - 3; + + // Create a rectangle object + RectObject = *rect = TranslucentObject(width, height); + + // add it to display list and position it + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), *rect); + MultiSetAniXY(*rect, InvD[ino].inventoryX + 1, InvD[ino].inventoryY + 1); + MultiSetZPosition(*rect, Z_INV_BRECT); + + // Create text object using title string + if (textFrom == FROM_HANDLE) { + LoadStringRes(InvD[ino].hInvTitle, tBufferAddr(), TBUFSZ); + *title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, + InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF, + hTagFontHandle(), TXT_CENTRE); + assert(*title); // Inventory title string produced NULL text + MultiSetZPosition(*title, Z_INV_HTEXT); + } else if (textFrom == FROM_STRING && cd.ixHeading != NO_HEADING) { + LoadStringRes(configStrings[cd.ixHeading], tBufferAddr(), TBUFSZ); + *title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, + InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF, + hTagFontHandle(), TXT_CENTRE); + assert(*title); // Inventory title string produced NULL text + MultiSetZPosition(*title, Z_INV_HTEXT); + } +} + +/** + * Insert a part of the inventory window frame onto the display list. + */ + +static OBJECT *AddObject(const FREEL *pfreel, int num) { + const MULTI_INIT *pmi; // Get the MULTI_INIT structure + PIMAGE pim; + OBJECT * pPlayObj; + + // Get pointer to image + pim = GetImageFromReel(pfreel, &pmi); + + // Poke in the background palette + pim->hImgPal = TO_LE_32(BackPal()); + + // Horrible bodge involving global variables to save + // width and/or height of some window frame components + if (num == TL) { + TLwidth = FROM_LE_16(pim->imgWidth); + TLheight = FROM_LE_16(pim->imgHeight); + } else if (num == TR) { + TRwidth = FROM_LE_16(pim->imgWidth); + } else if (num == BL) { + BLheight = FROM_LE_16(pim->imgHeight); + } + + // Set up and insert the multi-object + pPlayObj = MultiInitObject(pmi); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj); + + return pPlayObj; +} + +/** + * Display the scroll bar slider. + */ + +void AddSlider(OBJECT **slide, const FILM *pfilm) { + SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1); + MultiSetAniXY(*slide, MultiRightmost(RectObject)-M_SXOFF+2, InvD[ino].inventoryY + slideY); + MultiSetZPosition(*slide, Z_INV_MFRAME); +} + +enum { + SLIDE_RANGE = 81, + SLIDE_MINX = 8, + SLIDE_MAXX = 8+SLIDE_RANGE, + + MDTEXT_YOFF = 6, + MDTEXT_XOFF = -4 +}; + +/** + * Display a box with some text in it. + */ + +void AddBox(int *pi, int i) { + int x = InvD[ino].inventoryX + cd.Box[i].xpos; + int y = InvD[ino].inventoryY + cd.Box[i].ypos; + int *pival = cd.Box[i].ival; + int xdisp; + const FILM *pfilm; + + switch (cd.Box[i].boxType) { + default: + // Give us a box + iconArray[*pi] = RectangleObject(BackPal(), COL_BOX, cd.Box[i].w, cd.Box[i].h); + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[*pi]); + MultiSetAniXY(iconArray[*pi], x, y); + MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); + *pi += 1; + + // Stick in the text + if (cd.Box[i].ixText == USE_POINTER) { + if (cd.Box[i].boxText != NULL) { + if (cd.Box[i].boxType == RGROUP) { + iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0, +#ifdef JAPAN + x+2, y+2, hTagFontHandle(), 0); +#else + x+2, y, hTagFontHandle(), 0); +#endif + } else { + iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0, +#ifdef JAPAN +// Note: it never seems to go here! + x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE); +#else + x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE); +#endif + } + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + } + } else { + LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); + assert(cd.Box[i].boxType != RGROUP); // You'll need to add some code! + iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, +#ifdef JAPAN + x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE); +#else + x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE); +#endif + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + } + break; + + case AAGBUT: + case ARSGBUT: + pfilm = (const FILM *)LockMem(winPartsf); + + iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1); + MultiSetAniXY(iconArray[*pi], x, y); + MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); + *pi += 1; + + break; + +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) + case FRGROUP: + assert(flagFilm != 0); // Language flags not declared! + + pfilm = (const FILM *)LockMem(flagFilm); + + if (bAmerica && cd.Box[i].bi == FIX_UK) + cd.Box[i].bi = FIX_USA; + + iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1); + MultiSetAniXY(iconArray[*pi], x, y); + MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+2); + *pi += 1; + + break; +#endif + case FLIP: + pfilm = (const FILM *)LockMem(winPartsf); + + if (*(cd.Box[i].ival)) + iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1); + else + iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+1], -1); + MultiSetAniXY(iconArray[*pi], x, y); + MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); + *pi += 1; + + // Stick in the text + assert(cd.Box[i].ixText != USE_POINTER); + LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); + iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, + x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT); + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + break; + + case TOGGLE: + pfilm = (const FILM *)LockMem(winPartsf); + + cd.Box[i].bi = *(cd.Box[i].ival) ? IX_TICK1 : IX_CROSS1; + iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1); + MultiSetAniXY(iconArray[*pi], x, y); + MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1); + *pi += 1; + + // Stick in the text + assert(cd.Box[i].ixText != USE_POINTER); + LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); + iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, + x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT); + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + break; + + case SLIDER: + pfilm = (const FILM *)LockMem(winPartsf); + xdisp = SLIDE_RANGE*(*pival)/cd.Box[i].w; + + iconArray[*pi] = AddObject(&pfilm->reels[IX_MDGROOVE], -1); + MultiSetAniXY(iconArray[*pi], x, y); + MultiSetZPosition(iconArray[*pi], Z_MDGROOVE); + *pi += 1; + iconArray[*pi] = AddObject(&pfilm->reels[IX_MDSLIDER], -1); + MultiSetAniXY(iconArray[*pi], x+SLIDE_MINX+xdisp, y); + MultiSetZPosition(iconArray[*pi], Z_MDSLIDER); + assert(numMdSlides < MAXSLIDES); + mdSlides[numMdSlides].num = i; + mdSlides[numMdSlides].min = x+SLIDE_MINX; + mdSlides[numMdSlides].max = x+SLIDE_MAXX; + mdSlides[numMdSlides++].obj = iconArray[*pi]; + *pi += 1; + + // Stick in the text + assert(cd.Box[i].ixText != USE_POINTER); + LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ); + iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0, + x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT); + MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT); + *pi += 1; + break; + } +} + +/** + * Display some boxes. + */ +static void AddBoxes(bool posnSlide) { + int oCount = NUMHL; // Object count - allow for HL1, HL2 etc. + + DumpIconArray(); + numMdSlides = 0; + + for (int i = 0; i < cd.NumBoxes; i++) { + AddBox(&oCount, i); + } + + if (cd.bExtraWin) { + if (posnSlide) + slideY = slideYmin + (cd.fileBase*(slideYmax-slideYmin))/(MAX_SFILES-NUM_SL_RGROUP); + MultiSetAniXY(SlideObject, InvD[ino].inventoryX + 24 + 179, slideY); + } + + assert(oCount < MAX_ICONS); // added too many icons +} + +/** + * Display the scroll bar slider. + */ + +void AddEWSlider(OBJECT **slide, const FILM *pfilm) { + SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1); + MultiSetAniXY(*slide, InvD[ino].inventoryX + 24 + 127, slideY); + MultiSetZPosition(*slide, Z_INV_MFRAME); +} + +/** + * AddExtraWindow + */ + +int AddExtraWindow(int x, int y, OBJECT **retObj) { + int n = 0; + const FILM *pfilm; + + // Get the frame's data + pfilm = (const FILM *)LockMem(winPartsf); + + x += 20; + y += 24; + +// Draw the four corners + retObj[n] = AddObject(&pfilm->reels[IX_RTL], -1); // Top left + MultiSetAniXY(retObj[n], x, y); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_NTR], -1); // Top right + MultiSetAniXY(retObj[n], x + 152, y); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_BL], -1); // Bottom left + MultiSetAniXY(retObj[n], x, y + 124); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_BR], -1); // Bottom right + MultiSetAniXY(retObj[n], x + 152, y + 124); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + +// Draw the edges + retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Top + MultiSetAniXY(retObj[n], x + 6, y); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Bottom + MultiSetAniXY(retObj[n], x + 6, y + 143); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Left + MultiSetAniXY(retObj[n], x, y + 20); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 1 + MultiSetAniXY(retObj[n], x + 179, y + 20); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 2 + MultiSetAniXY(retObj[n], x + 188, y + 20); + MultiSetZPosition(retObj[n], Z_INV_MFRAME); + n++; + + slideY = slideYmin = y + 9; + slideYmax = y + 134; + AddEWSlider(&retObj[n++], pfilm); + + return n; +} + + +enum InventoryType { EMPTY, FULL, CONF }; + +/** + * Construct an inventory window - either a standard one, with + * background, slider and icons, or a re-sizing window. + */ +void ConstructInventory(InventoryType filling) { + int eH, eV; // Extra width and height + int n = 0; // Index into object array + int zpos; // Z-position of frame + int invX = InvD[ino].inventoryX; + int invY = InvD[ino].inventoryY; + OBJECT **retObj; + const FILM *pfilm; + + extern bool RePosition(void); // Forward reference + // Select the object array to use + if (filling == FULL || filling == CONF) { + retObj = objArray; // Standard window + zpos = Z_INV_MFRAME; + } else { + retObj = DobjArray; // Re-sizing window + zpos = Z_INV_RFRAME; + } + + // Dispose of anything it may be replacing + for (int i = 0; i < MAX_WCOMP; i++) { + if (retObj[i] != NULL) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), retObj[i]); + retObj[i] = NULL; + } + } + + // Get the frame's data + pfilm = (const FILM *)LockMem(winPartsf); + + // Standard window is of granular dimensions + if (filling == FULL) { + // Round-up/down to nearest number of icons + if (SuppH > ITEM_WIDTH / 2) + InvD[ino].NoofHicons++; + if (SuppV > ITEM_HEIGHT / 2) + InvD[ino].NoofVicons++; + SuppH = SuppV = 0; + } + + // Extra width and height + eH = (InvD[ino].NoofHicons - 1) * (ITEM_WIDTH+1) + SuppH; + eV = (InvD[ino].NoofVicons - 1) * (ITEM_HEIGHT+1) + SuppV; + + // Which window frame corners to use + if (filling == FULL && ino != INV_CONV) { + TL = IX_TL; + TR = IX_TR; + BL = IX_BL; + BR = IX_BR; + } else { + TL = IX_RTL; + TR = IX_RTR; + BL = IX_BL; + BR = IX_RBR; + } + +// Draw the four corners + retObj[n] = AddObject(&pfilm->reels[TL], TL); + MultiSetAniXY(retObj[n], invX, invY); + MultiSetZPosition(retObj[n], zpos); + n++; + retObj[n] = AddObject(&pfilm->reels[TR], TR); + MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY); + MultiSetZPosition(retObj[n], zpos); + n++; + retObj[n] = AddObject(&pfilm->reels[BL], BL); + MultiSetAniXY(retObj[n], invX, invY + TLheight + eV); + MultiSetZPosition(retObj[n], zpos); + n++; + retObj[n] = AddObject(&pfilm->reels[BR], BR); + MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY + TLheight + eV); + MultiSetZPosition(retObj[n], zpos); + n++; + +// Draw extra Top and bottom parts + if (InvD[ino].NoofHicons > 1) { + // Top side + retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1); + MultiSetAniXY(retObj[n], invX + TLwidth, invY); + MultiSetZPosition(retObj[n], zpos); + n++; + + // Bottom of header box + if (filling == FULL) { + retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1); + MultiSetAniXY(retObj[n], invX + TLwidth, invY + M_TBB + 1); + MultiSetZPosition(retObj[n], zpos); + n++; + + // Extra bits for conversation - hopefully temporary + if (ino == INV_CONV) { + retObj[n] = AddObject(&pfilm->reels[IX_H26], -1); + MultiSetAniXY(retObj[n], invX + TLwidth - 2, invY + M_TBB + 1); + MultiSetZPosition(retObj[n], zpos); + n++; + + retObj[n] = AddObject(&pfilm->reels[IX_H52], -1); + MultiSetAniXY(retObj[n], invX + eH - 10, invY + M_TBB + 1); + MultiSetZPosition(retObj[n], zpos); + n++; + } + } + + // Bottom side + retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1); + MultiSetAniXY(retObj[n], invX + TLwidth, invY + TLheight + eV + BLheight - M_TH + 1); + MultiSetZPosition(retObj[n], zpos); + n++; + } + if (SuppH) { + int offx = TLwidth + eH - 26; + if (offx < TLwidth) // Not too far! + offx = TLwidth; + + // Top side extra + retObj[n] = AddObject(&pfilm->reels[IX_H26], -1); + MultiSetAniXY(retObj[n], invX + offx, invY); + MultiSetZPosition(retObj[n], zpos); + n++; + + // Bottom side extra + retObj[n] = AddObject(&pfilm->reels[IX_H26], -1); + MultiSetAniXY(retObj[n], invX + offx, invY + TLheight + eV + BLheight - M_TH + 1); + MultiSetZPosition(retObj[n], zpos); + n++; + } + +// Draw extra side parts + if (InvD[ino].NoofVicons > 1) { + // Left side + retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1); + MultiSetAniXY(retObj[n], invX, invY + TLheight); + MultiSetZPosition(retObj[n], zpos); + n++; + + // Left side of scroll bar + if (filling == FULL && ino != INV_CONV) { + retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1); + MultiSetAniXY(retObj[n], invX + TLwidth + eH + M_SBL + 1, invY + TLheight); + MultiSetZPosition(retObj[n], zpos); + n++; + } + + // Right side + retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1); + MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + TLheight); + MultiSetZPosition(retObj[n], zpos); + n++; + } + if (SuppV) { + int offy = TLheight + eV - 26; + if (offy < 5) + offy = 5; + + // Left side extra + retObj[n] = AddObject(&pfilm->reels[IX_V26], -1); + MultiSetAniXY(retObj[n], invX, invY + offy); + MultiSetZPosition(retObj[n], zpos); + n++; + + // Right side extra + retObj[n] = AddObject(&pfilm->reels[IX_V26], -1); + MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + offy); + MultiSetZPosition(retObj[n], zpos); + n++; + } + + OBJECT **rect, **title; + +// Draw background, slider and icons + if (filling == FULL) { + rect = &retObj[n++]; + title = &retObj[n++]; + + AddBackground(rect, title, eH, eV, FROM_HANDLE); + + if (ino == INV_CONV) + SlideObject = NULL; + else if (InvD[ino].NoofItems > InvD[ino].NoofHicons*InvD[ino].NoofVicons) { + slideYmin = TLheight - 2; + slideYmax = TLheight + eV + 10; + AddSlider(&retObj[n++], pfilm); + } + + FillInInventory(); + } + else if (filling == CONF) { + rect = &retObj[n++]; + title = &retObj[n++]; + + AddBackground(rect, title, eH, eV, FROM_STRING); + if (cd.bExtraWin) + n += AddExtraWindow(invX, invY, &retObj[n]); + AddBoxes(true); + } + + assert(n < MAX_WCOMP); // added more parts than we can handle! + + // Reposition returns TRUE if needs to move + if (InvD[ino].moveable && filling == FULL && RePosition()) { + ConstructInventory(FULL); + } +} + + +/** + * Call this when drawing a 'FULL', movable inventory. Checks that the + * position of the Translucent object is within limits. If it isn't, + * adjusts the x/y position of the current inventory and returns TRUE. + */ +bool RePosition(void) { + int p; + bool bMoveitMoveit = false; + + assert(RectObject); // no recangle object! + + // Test for off-screen horizontally + p = MultiLeftmost(RectObject); + if (p > MAXLEFT) { + // Too far to the right + InvD[ino].inventoryX += MAXLEFT - p; + bMoveitMoveit = true; // I like to.... + } else { + // Too far to the left? + p = MultiRightmost(RectObject); + if (p < MINRIGHT) { + InvD[ino].inventoryX += MINRIGHT - p; + bMoveitMoveit = true; // I like to.... + } + } + + // Test for off-screen vertically + p = MultiHighest(RectObject); + if (p < MINTOP) { + // Too high + InvD[ino].inventoryY += MINTOP - p; + bMoveitMoveit = true; // I like to.... + } else if (p > MAXTOP) { + // Too low + InvD[ino].inventoryY += MAXTOP - p; + bMoveitMoveit = true; // I like to.... + } + + return bMoveitMoveit; +} + +/**************************************************************************/ +/***/ +/**************************************************************************/ + +/** + * Get the cursor's reel, poke in the background palette, + * and customise the cursor. + */ +void AlterCursor(int num) { + const FREEL *pfreel; + PIMAGE pim; + + // Get pointer to image + pim = GetImageFromFilm(winPartsf, num, &pfreel); + + // Poke in the background palette + pim->hImgPal = TO_LE_32(BackPal()); + + SetTempCursor(FROM_LE_32(pfreel->script)); +} + +enum InvCursorFN {IC_AREA, IC_DROP}; + +/** + * InvCursor + */ +void InvCursor(InvCursorFN fn, int CurX, int CurY) { + static enum { IC_NORMAL, IC_DR, IC_UR, IC_TB, IC_LR, + IC_INV, IC_UP, IC_DN } ICursor = IC_NORMAL; // FIXME: local static var + + int area; // The part of the window the cursor is over + bool restoreMain = false; + + // If currently dragging, don't be messing about with the cursor shape + if (InvDragging != ID_NONE) + return; + + switch (fn) { + case IC_DROP: + ICursor = IC_NORMAL; + InvCursor(IC_AREA, CurX, CurY); + break; + + case IC_AREA: + area = InvArea(CurX, CurY); + + // Check for POINTED events + if (ino == INV_CONF) + InvBoxes(area == I_BODY, CurX, CurY); + else + InvLabels(area == I_BODY, CurX, CurY); + + // No cursor trails while within inventory window + if (area == I_NOTIN) + UnHideCursorTrails(); + else + HideCursorTrails(); + + switch (area) { + case I_NOTIN: + restoreMain = true; + break; + + case I_TLEFT: + case I_BRIGHT: + if (!InvD[ino].resizable) + restoreMain = true; + else if (ICursor != IC_DR) { + AlterCursor(IX_CURDD); + ICursor = IC_DR; + } + break; + + case I_TRIGHT: + case I_BLEFT: + if (!InvD[ino].resizable) + restoreMain = true; + else if (ICursor != IC_UR) { + AlterCursor(IX_CURDU); + ICursor = IC_UR; + } + break; + + case I_TOP: + case I_BOTTOM: + if (!InvD[ino].resizable) { + restoreMain = true; + break; + } + if (ICursor != IC_TB) { + AlterCursor(IX_CURUD); + ICursor = IC_TB; + } + break; + + case I_LEFT: + case I_RIGHT: + if (!InvD[ino].resizable) + restoreMain = true; + else if (ICursor != IC_LR) { + AlterCursor(IX_CURLR); + ICursor = IC_LR; + } + break; + + case I_UP: + case I_SLIDE_UP: + case I_DOWN: + case I_SLIDE_DOWN: + case I_SLIDE: + case I_MOVE: + case I_BODY: + restoreMain = true; + break; + } + break; + } + + if (restoreMain && ICursor != IC_NORMAL) { + RestoreMainCursor(); + ICursor = IC_NORMAL; + } +} + + + + +/*-------------------------------------------------------------------------*/ + + +/**************************************************************************/ +/******************** Conversation specific functions *********************/ +/**************************************************************************/ + + +void ConvAction(int index) { + assert(ino == INV_CONV); // not conv. window! + + switch (index) { + case INV_NOICON: + return; + + case INV_CLOSEICON: + thisConvIcon = -1; // Postamble + break; + + case INV_OPENICON: + thisConvIcon = -2; // Preamble + break; + + default: + thisConvIcon = InvD[ino].ItemOrder[index]; + break; + } + + RunPolyTinselCode(thisConvPoly, CONVERSE, BE_NONE, true); +} +/*-------------------------------------------------------------------------*/ + +void AddIconToPermanentDefaultList(int icon) { + int i; + + // See if it's already there + for (i = 0; i < Num0Order; i++) { + if (Inv0Order[i] == icon) + break; + } + + // Add it if it isn't already there + if (i == Num0Order) { + Inv0Order[Num0Order++] = icon; + } +} + +/*-------------------------------------------------------------------------*/ + +void convPos(int fn) { + if (fn == CONV_DEF) + InvD[INV_CONV].inventoryY = 8; + else if (fn == CONV_BOTTOM) + InvD[INV_CONV].inventoryY = 150; +} + +void ConvPoly(HPOLYGON hPoly) { + thisConvPoly = hPoly; +} + +int convIcon(void) { + return thisConvIcon; +} + +void CloseDownConv(void) { + if (InventoryState == ACTIVE_INV && ino == INV_CONV) { + KillInventory(); + } +} + +void convHide(bool hide) { + int aniX, aniY; + int i; + + if (InventoryState == ACTIVE_INV && ino == INV_CONV) { + if (hide) { + for (i = 0; objArray[i] && i < MAX_WCOMP; i++) { + MultiAdjustXY(objArray[i], 2*SCREEN_WIDTH, 0); + } + for (i = 0; iconArray[i] && i < MAX_ICONS; i++) { + MultiAdjustXY(iconArray[i], 2*SCREEN_WIDTH, 0); + } + InventoryHidden = true; + + InvLabels(false, 0, 0); + } else { + InventoryHidden = false; + + for (i = 0; objArray[i] && i < MAX_WCOMP; i++) { + MultiAdjustXY(objArray[i], -2*SCREEN_WIDTH, 0); + } + + // Don't flash if items changed. If they have, will be redrawn anyway. + if (!ItemsChanged) { + for (i = 0; iconArray[i] && i < MAX_ICONS; i++) { + MultiAdjustXY(iconArray[i], -2*SCREEN_WIDTH, 0); + } + } + + GetCursorXY(&aniX, &aniY, false); + InvLabels(true, aniX, aniY); + } + } +} + +bool convHid(void) { + return InventoryHidden; +} + + +/**************************************************************************/ +/******************* Open and closing functions ***************************/ +/**************************************************************************/ + +/** + * Start up an inventory window. + */ + +void PopUpInventory(int invno) { + assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_CONF)); // Trying to open illegal inventory + + if (InventoryState == IDLE_INV) { + bOpenConf = false; // Better safe than sorry... + + DisableTags(); // Tags disabled during inventory + + if (invno == INV_CONV) { // Conversation window? + // Start conversation with permanent contents + memset(InvD[INV_CONV].ItemOrder, 0, MAX_ININV*sizeof(int)); + memcpy(InvD[INV_CONV].ItemOrder, Inv0Order, Num0Order*sizeof(int)); + InvD[INV_CONV].NoofItems = Num0Order; + thisConvIcon = 0; + } else if (invno == INV_CONF) { // Configuration window? + cd.selBox = NOBOX; + cd.pointBox = NOBOX; + } + + ino = invno; // The open inventory + + ItemsChanged = false; // Nothing changed + InvDragging = ID_NONE; // Not dragging + InventoryState = ACTIVE_INV; // Inventory actiive + InventoryHidden = false; // Not hidden + InventoryMaximised = InvD[ino].bMax; + if (invno != INV_CONF) // Configuration window? + ConstructInventory(FULL); // Draw it up + else { + ConstructInventory(CONF); // Draw it up + } + } +} + +void SetConfGlobals(PCONFINIT ci) { + InvD[INV_CONF].MinHicons = InvD[INV_CONF].MaxHicons = InvD[INV_CONF].NoofHicons = ci->h; + InvD[INV_CONF].MaxVicons = InvD[INV_CONF].MinVicons = InvD[INV_CONF].NoofVicons = ci->v; + InvD[INV_CONF].inventoryX = ci->x; + InvD[INV_CONF].inventoryY = ci->y; + cd.bExtraWin = ci->bExtraWin; + cd.Box = ci->Box; + cd.NumBoxes = ci->NumBoxes; + cd.ixHeading = ci->ixHeading; +} + +/** + * PopupConf + */ + +void PopUpConf(CONFTYPE type) { + int curX, curY; + + if (InventoryState != IDLE_INV) + return; + + InvD[INV_CONF].resizable = false; + InvD[INV_CONF].moveable = false; + + switch (type) { + case SAVE: + case LOAD: + if (type == SAVE) { + SetCursorScreenXY(262, 91); + SetConfGlobals(&ciSave); + cd.editableRgroup = true; + } else { + SetConfGlobals(&ciLoad); + cd.editableRgroup = false; + } + firstFile(0); + break; + + case QUIT: +#ifdef JAPAN + SetCursorScreenXY(180, 106); +#else + SetCursorScreenXY(180, 90); +#endif + SetConfGlobals(&ciQuit); + break; + + case RESTART: +#ifdef JAPAN + SetCursorScreenXY(180, 106); +#else + SetCursorScreenXY(180, 90); +#endif + SetConfGlobals(&ciRestart); + break; + + case OPTION: + SetConfGlobals(&ciOption); + break; + + case CONTROLS: + SetConfGlobals(&ciControl); + break; + + case SOUND: + SetConfGlobals(&ciSound); + break; + +#ifndef JAPAN + case SUBT: + SetConfGlobals(&ciSubtitles); + break; +#endif + + case TOPWIN: + SetConfGlobals(&ciTopWin); + ino = INV_CONF; + ConstructInventory(CONF); // Draw it up + InventoryState = BOGUS_INV; + return; + + default: + return; + } + + if (HeldItem != INV_NOICON) + DelAuxCursor(); // no longer aux cursor + + PopUpInventory(INV_CONF); + + if (type == SAVE || type == LOAD) + Select(0, false); +#ifndef JAPAN +#if !defined(USE_3FLAGS) || !defined(USE_4FLAGS) || !defined(USE_5FLAGS) + else if (type == SUBT) { +#ifdef USE_3FLAGS + // VERY quick dirty bodges + if (language == TXT_FRENCH) + Select(0, false); + else if (language == TXT_GERMAN) + Select(1, false); + else + Select(2, false); +#elif defined(USE_4FLAGS) + Select(language-1, false); +#else + Select(language, false); +#endif + } +#endif +#endif // JAPAN + + GetCursorXY(&curX, &curY, false); + InvCursor(IC_AREA, curX, curY); +} + +/** + * Close down an inventory window. + */ + +void KillInventory(void) { + if (objArray[0] != NULL) { + DumpObjArray(); + DumpDobjArray(); + DumpIconArray(); + } + + if (InventoryState == ACTIVE_INV) { + EnableTags(); + + InvD[ino].bMax = InventoryMaximised; + + UnHideCursorTrails(); + _vm->divertKeyInput(NULL); + } + + InventoryState = IDLE_INV; + + if (bOpenConf) { + bOpenConf = false; + PopUpConf(OPTION); + } else if (ino == INV_CONF) + InventoryIconCursor(); +} + +void CloseInventory(void) { + // If not active, ignore this + if (InventoryState != ACTIVE_INV) + return; + + // If hidden, a conversation action is still underway - ignore this + if (InventoryHidden) + return; + + // If conversation, this is a closeing event + if (ino == INV_CONV) + ConvAction(INV_CLOSEICON); + + KillInventory(); + + RestoreMainCursor(); +} + + + +/**************************************************************************/ +/************************ The inventory process ***************************/ +/**************************************************************************/ + +/** + * Redraws the icons if appropriate. Also handle button press/toggle effects + */ +void InventoryProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + while (1) { + CORO_SLEEP(1); // allow scheduling + + if (objArray[0] != NULL) { + if (ItemsChanged && ino != INV_CONF && !InventoryHidden) { + FillInInventory(); + + // Needed when clicking on scroll bar. + int curX, curY; + GetCursorXY(&curX, &curY, false); + InvCursor(IC_AREA, curX, curY); + + ItemsChanged = false; + } + if (ino != INV_CONF) { + for (int i = 0; i < MAX_ICONS; i++) { + if (iconArray[i] != NULL) + StepAnimScript(&iconAnims[i]); + } + } + if (InvDragging == ID_MDCONT) { + // Mixing desk control + int sval, index, *pival; + + index = cd.selBox & ~IS_MASK; + pival = cd.Box[index].ival; + sval = *pival; + + if (cd.selBox & IS_LEFT) { + *pival -= cd.Box[index].h; + if (*pival < 0) + *pival = 0; + } else if (cd.selBox & IS_RIGHT) { + *pival += cd.Box[index].h; + if (*pival > cd.Box[index].w) + *pival = cd.Box[index].w; + } + + if (sval != *pival) { + SlideMSlider(0, (cd.selBox & IS_RIGHT) ? S_TIMEUP : S_TIMEDN); + } + } + } + + if (g_buttonEffect.bButAnim) { + assert(g_buttonEffect.box); + if (g_buttonEffect.press) { + if (g_buttonEffect.box->boxType == AAGBUT || g_buttonEffect.box->boxType == ARSGBUT) + CORO_INVOKE_1(ButtonPress, g_buttonEffect.box); + switch (g_buttonEffect.box->boxFunc) { + case SAVEGAME: + KillInventory(); + InvSaveGame(); + break; + case LOADGAME: + KillInventory(); + InvLoadGame(); + break; + case IQUITGAME: + _vm->quitFlag = true; + break; + case CLOSEWIN: + KillInventory(); + break; + case OPENLOAD: + KillInventory(); + PopUpConf(LOAD); + break; + case OPENSAVE: + KillInventory(); + PopUpConf(SAVE); + break; + case OPENREST: + KillInventory(); + PopUpConf(RESTART); + break; + case OPENSOUND: + KillInventory(); + PopUpConf(SOUND); + break; + case OPENCONT: + KillInventory(); + PopUpConf(CONTROLS); + break; + #ifndef JAPAN + case OPENSUBT: + KillInventory(); + PopUpConf(SUBT); + break; + #endif + case OPENQUIT: + KillInventory(); + PopUpConf(QUIT); + break; + case INITGAME: + KillInventory(); + bRestart = true; + break; + #if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) + case CLANG: + if (!LanguageChange()) + KillInventory(); + break; + case RLANG: + KillInventory(); + break; + #endif + default: + break; + } + } else + CORO_INVOKE_1(ButtonToggle, g_buttonEffect.box); + + g_buttonEffect.bButAnim = false; + } + + } + CORO_END_CODE; +} + +/**************************************************************************/ +/*************** Drag stuff - Resizing and moving window ******************/ +/**************************************************************************/ + +/** + * Appears to find the nearest entry in slideStuff[] to the supplied + * y-coordinate. + */ +int NearestSlideY(int fity) { + int nearDist = 1000; + int thisDist; + int nearI = 0; // Index of nearest fit + int i = 0; + + do { + thisDist = ABS(slideStuff[i].y - fity); + if (thisDist < nearDist) { + nearDist = thisDist; + nearI = i; + } + } while (slideStuff[++i].n != -1); + return nearI; +} + +/** + * Gets called at the start and end of a drag on the slider, and upon + * y-movement during such a drag. + */ +void SlideSlider(int y, SSFN fn) { + static int newY = 0, lasti = 0; // FIXME: local static var + int gotoY, ati; + + // Only do this if there's a slider + if (!SlideObject) + return; + + switch (fn) { + case S_START: // Start of a drag on the slider + newY = slideY; + lasti = NearestSlideY(slideY); + break; + + case S_SLIDE: // Y-movement during drag + newY = newY + y; // New y-position + + if (newY < slideYmin) + gotoY = slideYmin; // Above top limit + else if (newY > slideYmax) + gotoY = slideYmax; // Below bottom limit + else + gotoY = newY; // Hunky-Dory + + // Move slider to new position + MultiMoveRelXY(SlideObject, 0, gotoY - slideY); + slideY = gotoY; + + // Re-draw icons if necessary + ati = NearestSlideY(slideY); + if (ati != lasti) { + InvD[ino].FirstDisp = slideStuff[ati].n; + assert(InvD[ino].FirstDisp >= 0); // negative first displayed + ItemsChanged = true; + lasti = ati; + } + break; + + case S_END: // End of a drag on the slider + // Draw icons from new start icon + ati = NearestSlideY(slideY); + InvD[ino].FirstDisp = slideStuff[ati].n; + ItemsChanged = true; + break; + + default: + break; + } +} + +/** + * Gets called at the start and end of a drag on the slider, and upon + * y-movement during such a drag. + */ + +void SlideCSlider(int y, SSFN fn) { + static int newY = 0; // FIXME: local static var + int gotoY; + int fc; + + // Only do this if there's a slider + if (!SlideObject) + return; + + switch (fn) { + case S_START: // Start of a drag on the slider + newY = slideY; + break; + + case S_SLIDE: // Y-movement during drag + newY = newY + y; // New y-position + + if (newY < slideYmin) + gotoY = slideYmin; // Above top limit + else if (newY > slideYmax) + gotoY = slideYmax; // Below bottom limit + else + gotoY = newY; // Hunky-Dory + + slideY = gotoY; + + fc = cd.fileBase; + firstFile((slideY-slideYmin)*(MAX_SFILES-NUM_SL_RGROUP)/(slideYmax-slideYmin)); + if (fc != cd.fileBase) { + AddBoxes(false); + fc -= cd.fileBase; + cd.selBox += fc; + if (cd.selBox < 0) + cd.selBox = 0; + else if (cd.selBox >= NUM_SL_RGROUP) + cd.selBox = NUM_SL_RGROUP-1; + Select(cd.selBox, true); + } + break; + + case S_END: // End of a drag on the slider + break; + + default: + break; + } +} + +/** + * Gets called at the start and end of a drag on a mixing desk slider, + * and upon x-movement during such a drag. + */ + +static void SlideMSlider(int x, SSFN fn) { + static int newX = 0; // FIXME: local static var + int gotoX; + int index, i; + + if (fn == S_END || fn == S_TIMEUP || fn == S_TIMEDN) + ; + else if (!(cd.selBox & IS_SLIDER)) + return; + + // Work out the indices + index = cd.selBox & ~IS_MASK; + for (i = 0; i < numMdSlides; i++) + if (mdSlides[i].num == index) + break; + assert(i < numMdSlides); + + switch (fn) { + case S_START: // Start of a drag on the slider + // can use index as a throw-away value + GetAniPosition(mdSlides[i].obj, &newX, &index); + lX = sX = newX; + break; + + case S_SLIDE: // X-movement during drag + if (x == 0) + return; + + newX = newX + x; // New x-position + + if (newX < mdSlides[i].min) + gotoX = mdSlides[i].min; // Below bottom limit + else if (newX > mdSlides[i].max) + gotoX = mdSlides[i].max; // Above top limit + else + gotoX = newX; // Hunky-Dory + + // Move slider to new position + MultiMoveRelXY(mdSlides[i].obj, gotoX - sX, 0); + sX = gotoX; + + if (lX != sX) { + *cd.Box[index].ival = (sX - mdSlides[i].min)*cd.Box[index].w/SLIDE_RANGE; + if (cd.Box[index].boxFunc == MIDIVOL) + SetMidiVolume(*cd.Box[index].ival); +#ifdef MAC_OPTIONS + if (cd.Box[index].boxFunc == MASTERVOL) + SetSystemVolume(*cd.Box[index].ival); + + if (cd.Box[index].boxFunc == SAMPVOL) + SetSampleVolume(*cd.Box[index].ival); +#endif + lX = sX; + } + break; + + case S_TIMEUP: + case S_TIMEDN: + gotoX = SLIDE_RANGE*(*cd.Box[index].ival)/cd.Box[index].w; + MultiSetAniX(mdSlides[i].obj, mdSlides[i].min+gotoX); + + if (cd.Box[index].boxFunc == MIDIVOL) + SetMidiVolume(*cd.Box[index].ival); +#ifdef MAC_OPTIONS + if (cd.Box[index].boxFunc == MASTERVOL) + SetSystemVolume(*cd.Box[index].ival); + + if (cd.Box[index].boxFunc == SAMPVOL) + SetSampleVolume(*cd.Box[index].ival); +#endif + break; + + case S_END: // End of a drag on the slider + AddBoxes(false); // Might change position slightly +#ifndef JAPAN + if (ino == INV_CONF && cd.Box == subtitlesBox) + Select(language, false); +#endif + break; + } +} + +/** + * Called from ChangeingSize() during re-sizing. + */ + +void GettingTaller(void) { + if (SuppV) { + Ychange += SuppV; + if (Ycompensate == 'T') + InvD[ino].inventoryY += SuppV; + SuppV = 0; + } + while (Ychange > (ITEM_HEIGHT+1) && InvD[ino].NoofVicons < InvD[ino].MaxVicons) { + Ychange -= (ITEM_HEIGHT+1); + InvD[ino].NoofVicons++; + if (Ycompensate == 'T') + InvD[ino].inventoryY -= (ITEM_HEIGHT+1); + } + if (InvD[ino].NoofVicons < InvD[ino].MaxVicons) { + SuppV = Ychange; + Ychange = 0; + if (Ycompensate == 'T') + InvD[ino].inventoryY -= SuppV; + } +} + +/** + * Called from ChangeingSize() during re-sizing. + */ + +void GettingShorter(void) { + int StartNvi = InvD[ino].NoofVicons; + int StartUv = SuppV; + + if (SuppV) { + Ychange += (SuppV - (ITEM_HEIGHT+1)); + InvD[ino].NoofVicons++; + SuppV = 0; + } + while (Ychange < -(ITEM_HEIGHT+1) && InvD[ino].NoofVicons > InvD[ino].MinVicons) { + Ychange += (ITEM_HEIGHT+1); + InvD[ino].NoofVicons--; + } + if (InvD[ino].NoofVicons > InvD[ino].MinVicons && Ychange) { + SuppV = (ITEM_HEIGHT+1) + Ychange; + InvD[ino].NoofVicons--; + Ychange = 0; + } + if (Ycompensate == 'T') + InvD[ino].inventoryY += (ITEM_HEIGHT+1)*(StartNvi - InvD[ino].NoofVicons) - (SuppV - StartUv); +} + +/** + * Called from ChangeingSize() during re-sizing. + */ + +void GettingWider(void) { + int StartNhi = InvD[ino].NoofHicons; + int StartUh = SuppH; + + if (SuppH) { + Xchange += SuppH; + SuppH = 0; + } + while (Xchange > (ITEM_WIDTH+1) && InvD[ino].NoofHicons < InvD[ino].MaxHicons) { + Xchange -= (ITEM_WIDTH+1); + InvD[ino].NoofHicons++; + } + if (InvD[ino].NoofHicons < InvD[ino].MaxHicons) { + SuppH = Xchange; + Xchange = 0; + } + if (Xcompensate == 'L') + InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh); +} + +/** + * Called from ChangeingSize() during re-sizing. + */ + +void GettingNarrower(void) { + int StartNhi = InvD[ino].NoofHicons; + int StartUh = SuppH; + + if (SuppH) { + Xchange += (SuppH - (ITEM_WIDTH+1)); + InvD[ino].NoofHicons++; + SuppH = 0; + } + while (Xchange < -(ITEM_WIDTH+1) && InvD[ino].NoofHicons > InvD[ino].MinHicons) { + Xchange += (ITEM_WIDTH+1); + InvD[ino].NoofHicons--; + } + if (InvD[ino].NoofHicons > InvD[ino].MinHicons && Xchange) { + SuppH = (ITEM_WIDTH+1) + Xchange; + InvD[ino].NoofHicons--; + Xchange = 0; + } + if (Xcompensate == 'L') + InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh); +} + + +/** + * Called from Xmovement()/Ymovement() during re-sizing. + */ + +void ChangeingSize(void) { + /* Make it taller or shorter if necessary. */ + if (Ychange > 0) + GettingTaller(); + else if (Ychange < 0) + GettingShorter(); + + /* Make it wider or narrower if necessary. */ + if (Xchange > 0) + GettingWider(); + else if (Xchange < 0) + GettingNarrower(); + + ConstructInventory(EMPTY); +} + +/** + * Called from cursor module when cursor moves while inventory is up. + */ + +void Xmovement(int x) { + int aniX, aniY; + int i; + + if (x && objArray[0] != NULL) { + switch (InvDragging) { + case ID_MOVE: + GetAniPosition(objArray[0], &InvD[ino].inventoryX, &aniY); + InvD[ino].inventoryX +=x; + MultiSetAniX(objArray[0], InvD[ino].inventoryX); + for (i = 1; objArray[i] && i < MAX_WCOMP; i++) + MultiMoveRelXY(objArray[i], x, 0); + for (i = 0; iconArray[i] && i < MAX_ICONS; i++) + MultiMoveRelXY(iconArray[i], x, 0); + break; + + case ID_LEFT: + case ID_TLEFT: + case ID_BLEFT: + Xchange -= x; + ChangeingSize(); + break; + + case ID_RIGHT: + case ID_TRIGHT: + case ID_BRIGHT: + Xchange += x; + ChangeingSize(); + break; + + case ID_NONE: + GetCursorXY(&aniX, &aniY, false); + InvCursor(IC_AREA, aniX, aniY); + break; + + case ID_MDCONT: + SlideMSlider(x, S_SLIDE); + break; + + default: + break; + } + } +} + +/** + * Called from cursor module when cursor moves while inventory is up. + */ + +void Ymovement(int y) { + int aniX, aniY; + int i; + + if (y && objArray[0] != NULL) { + switch (InvDragging) { + case ID_MOVE: + GetAniPosition(objArray[0], &aniX, &InvD[ino].inventoryY); + InvD[ino].inventoryY +=y; + MultiSetAniY(objArray[0], InvD[ino].inventoryY); + for (i = 1; objArray[i] && i < MAX_WCOMP; i++) + MultiMoveRelXY(objArray[i], 0, y); + for (i = 0; iconArray[i] && i < MAX_ICONS; i++) + MultiMoveRelXY(iconArray[i], 0, y); + break; + + case ID_SLIDE: + SlideSlider(y, S_SLIDE); + break; + + case ID_CSLIDE: + SlideCSlider(y, S_SLIDE); + break; + + case ID_BOTTOM: + case ID_BLEFT: + case ID_BRIGHT: + Ychange += y; + ChangeingSize(); + break; + + case ID_TOP: + case ID_TLEFT: + case ID_TRIGHT: + Ychange -= y; + ChangeingSize(); + break; + + case ID_NONE: + GetCursorXY(&aniX, &aniY, false); + InvCursor(IC_AREA, aniX, aniY); + break; + + default: + break; + } + } +} + +/** + * Called when a drag is commencing. + */ + +void InvDragStart(void) { + int curX, curY; // cursor's animation position + + GetCursorXY(&curX, &curY, false); + +/* +* Do something different for Save/Restore screens +*/ + if (ino == INV_CONF) { + int whichbox; + + whichbox = WhichInvBox(curX, curY, true); + + if (whichbox == IB_SLIDE) { + InvDragging = ID_CSLIDE; + SlideCSlider(0, S_START); + } else if (whichbox > 0 && (whichbox & IS_MASK)) { + InvDragging = ID_MDCONT; // Mixing desk control + cd.selBox = whichbox; + SlideMSlider(0, S_START); + } + return; + } + +/* +* Normal operation +*/ + switch (InvArea(curX, curY)) { + case I_MOVE: + if (InvD[ino].moveable) { + InvDragging = ID_MOVE; + } + break; + + case I_SLIDE: + InvDragging = ID_SLIDE; + SlideSlider(0, S_START); + break; + + case I_BOTTOM: + if (InvD[ino].resizable) { + Ychange = 0; + InvDragging = ID_BOTTOM; + Ycompensate = 'B'; + } + break; + + case I_TOP: + if (InvD[ino].resizable) { + Ychange = 0; + InvDragging = ID_TOP; + Ycompensate = 'T'; + } + break; + + case I_LEFT: + if (InvD[ino].resizable) { + Xchange = 0; + InvDragging = ID_LEFT; + Xcompensate = 'L'; + } + break; + + case I_RIGHT: + if (InvD[ino].resizable) { + Xchange = 0; + InvDragging = ID_RIGHT; + Xcompensate = 'R'; + } + break; + + case I_TLEFT: + if (InvD[ino].resizable) { + Ychange = 0; + Ycompensate = 'T'; + Xchange = 0; + Xcompensate = 'L'; + InvDragging = ID_TLEFT; + } + break; + + case I_TRIGHT: + if (InvD[ino].resizable) { + Ychange = 0; + Ycompensate = 'T'; + Xchange = 0; + Xcompensate = 'R'; + InvDragging = ID_TRIGHT; + } + break; + + case I_BLEFT: + if (InvD[ino].resizable) { + Ychange = 0; + Ycompensate = 'B'; + Xchange = 0; + Xcompensate = 'L'; + InvDragging = ID_BLEFT; + } + break; + + case I_BRIGHT: + if (InvD[ino].resizable) { + Ychange = 0; + Ycompensate = 'B'; + Xchange = 0; + Xcompensate = 'R'; + InvDragging = ID_BRIGHT; + } + break; + } +} + +/** + * Called when a drag is over. + */ + +void InvDragEnd(void) { + int curX, curY; // cursor's animation position + + GetCursorXY(&curX, &curY, false); + + if (InvDragging != ID_NONE) { + if (InvDragging == ID_SLIDE) { + SlideSlider(0, S_END); + } else if (InvDragging == ID_CSLIDE) { + ; // No action + } else if (InvDragging == ID_MDCONT) { + SlideMSlider(0, S_END); + } else if (InvDragging == ID_MOVE) { + ; // No action + } else { + // Were re-sizing. Redraw the whole thing. + DumpDobjArray(); + DumpObjArray(); + ConstructInventory(FULL); + + // If this was the maximised, it no longer is! + if (InventoryMaximised) { + InventoryMaximised = false; + InvD[ino].otherX = InvD[ino].inventoryX; + InvD[ino].otherY = InvD[ino].inventoryY; + } + } + InvDragging = ID_NONE; + } + + // Cursor could well now be inappropriate + InvCursor(IC_AREA, curX, curY); + + Xchange = Ychange = 0; // Probably no need, but does no harm! +} + + +/**************************************************************************/ +/************** Incoming events - further processing **********************/ +/**************************************************************************/ + +/** + * ConfAction + */ +void ConfAction(int i, bool dbl) { + + if (i >= 0) { + switch (cd.Box[i].boxType) { + case FLIP: + if (dbl) { + *(cd.Box[i].ival) ^= 1; // XOR with true + AddBoxes(false); + } + break; + + case TOGGLE: + if (!g_buttonEffect.bButAnim) { + g_buttonEffect.bButAnim = true; + g_buttonEffect.box = &cd.Box[i]; + g_buttonEffect.press = false; + } + break; + + case RGROUP: + if (dbl) { + // Already highlighted + switch (cd.Box[i].boxFunc) { + case SAVEGAME: + KillInventory(); + InvSaveGame(); + break; + case LOADGAME: + KillInventory(); + InvLoadGame(); + break; + default: + break; + } + } else { + Select(i, false); + } + break; + +#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS) + case FRGROUP: + if (dbl) { + Select(i, false); + LanguageChange(); + } else { + Select(i, false); + } + break; +#endif + + case AAGBUT: + case ARSGBUT: + case ARSBUT: + case AABUT: + case AATBUT: + if (g_buttonEffect.bButAnim) + break; + + g_buttonEffect.bButAnim = true; + g_buttonEffect.box = &cd.Box[i]; + g_buttonEffect.press = true; + break; + default: + break; + } + } else { + ConfActionSpecial(i); + } +} + +static void ConfActionSpecial(int i) { + switch (i) { + case IB_NONE: + break; + case IB_UP: // Scroll up + if (cd.fileBase > 0) { + firstFile(cd.fileBase-1); + AddBoxes(true); + if (cd.selBox < NUM_SL_RGROUP-1) + cd.selBox += 1; + Select(cd.selBox, true); + } + break; + case IB_DOWN: // Scroll down + if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) { + firstFile(cd.fileBase+1); + AddBoxes(true); + if (cd.selBox) + cd.selBox -= 1; + Select(cd.selBox, true); + } + break; + case IB_SLIDE_UP: + if (cd.fileBase > 0) { + firstFile(cd.fileBase-(NUM_SL_RGROUP-1)); + AddBoxes(true); + cd.selBox = 0; + Select(cd.selBox, true); + } + break; + case IB_SLIDE_DOWN: // Scroll down + if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) { + firstFile(cd.fileBase+(NUM_SL_RGROUP-1)); + AddBoxes(true); + cd.selBox = NUM_SL_RGROUP-1; + Select(cd.selBox, true); + } + break; + } +} +// SLIDE_UP and SLIDE_DOWN on d click?????? + +void InvPutDown(int index) { + int aniX, aniY; + // index is the drop position + int hiIndex; // Current position of held item (if in) + + // Find where the held item is positioned in this inventory (if it is) + for (hiIndex = 0; hiIndex < InvD[ino].NoofItems; hiIndex++) + if (InvD[ino].ItemOrder[hiIndex] == HeldItem) + break; + + // If drop position would leave a gap, move it up + if (index >= InvD[ino].NoofItems) { + if (hiIndex == InvD[ino].NoofItems) // Not in, add it + index = InvD[ino].NoofItems; + else + index = InvD[ino].NoofItems - 1; + } + + if (hiIndex == InvD[ino].NoofItems) { // Not in, add it + if (InvD[ino].NoofItems < InvD[ino].MaxInvObj) { + InvD[ino].NoofItems++; + + // Don't leave it in the other inventory! + if (InventoryPos(HeldItem) != INV_HELDNOTIN) + RemFromInventory(ino == INV_1 ? INV_2 : INV_1, HeldItem); + } else { + // No room at the inn! + return; + } + } + + // Position it in the inventory + if (index < hiIndex) { + memmove(&InvD[ino].ItemOrder[index + 1], &InvD[ino].ItemOrder[index], (hiIndex-index)*sizeof(int)); + InvD[ino].ItemOrder[index] = HeldItem; + } else if (index > hiIndex) { + memmove(&InvD[ino].ItemOrder[hiIndex], &InvD[ino].ItemOrder[hiIndex+1], (index-hiIndex)*sizeof(int)); + InvD[ino].ItemOrder[index] = HeldItem; + } else { + InvD[ino].ItemOrder[index] = HeldItem; + } + + HeldItem = INV_NOICON; + ItemsChanged = true; + DelAuxCursor(); + RestoreMainCursor(); + GetCursorXY(&aniX, &aniY, false); + InvCursor(IC_DROP, aniX, aniY); +} + +void InvPdProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + GetToken(TOKEN_LEFT_BUT); + CORO_SLEEP(dclickSpeed+1); + FreeToken(TOKEN_LEFT_BUT); + + // get the stuff copied to process when it was created + int *pindex = (int *)ProcessGetParamsSelf(); + + InvPutDown(*pindex); + + CORO_END_CODE; +} + +void InvPickup(int index) { + PINV_OBJECT invObj; + + if (index != INV_NOICON) { + if (HeldItem == INV_NOICON && InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) { + // Pick-up + invObj = findInvObject(InvD[ino].ItemOrder[index]); + if (invObj->hScript) + RunInvTinselCode(invObj, WALKTO, INV_PICKUP, index); + } else if (HeldItem != INV_NOICON) { // Put icon down + // Put-down + invObj = findInvObject(HeldItem); + + if (invObj->attribute & IO_DROPCODE && invObj->hScript) + RunInvTinselCode(invObj, PUTDOWN, INV_PICKUP, index); + + else if (!(invObj->attribute & IO_ONLYINV1 && ino !=INV_1) + && !(invObj->attribute & IO_ONLYINV2 && ino !=INV_2)) + CoroutineInstall(PID_TCODE, InvPdProcess, &index, sizeof(index)); + } + } +} + +/** + * Pick up/put down icon + */ +void InvSLClick(void) { + int i; + int aniX, aniY; // Cursor's animation position + + GetCursorXY(&aniX, &aniY, false); + + switch (InvArea(aniX, aniY)) { + case I_NOTIN: + if (ino == INV_CONV) + ConvAction(INV_CLOSEICON); + KillInventory(); + break; + + case I_SLIDE_UP: + if (InvD[ino].NoofVicons == 1) + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + for (i = 1; i < InvD[ino].NoofVicons; i++) + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + if (InvD[ino].FirstDisp < 0) + InvD[ino].FirstDisp = 0; + ItemsChanged = true; + break; + + case I_UP: + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + if (InvD[ino].FirstDisp < 0) + InvD[ino].FirstDisp = 0; + ItemsChanged = true; + break; + + case I_SLIDE_DOWN: + if (InvD[ino].NoofVicons == 1) + if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) + InvD[ino].FirstDisp += InvD[ino].NoofHicons; + for (i = 1; i < InvD[ino].NoofVicons; i++) { + if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) + InvD[ino].FirstDisp += InvD[ino].NoofHicons; + } + ItemsChanged = true; + break; + + case I_DOWN: + if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) { + InvD[ino].FirstDisp += InvD[ino].NoofHicons; + ItemsChanged = true; + } + break; + + case I_BODY: + if (ino == INV_CONF) { + if (!InventoryHidden) + ConfAction(WhichInvBox(aniX, aniY, false), false); + } else { + i = InvItem(&aniX, &aniY, false); + + // Special bodge for David, to + // cater for drop in dead space between icons + if (i == INV_NOICON && HeldItem != INV_NOICON && (ino == INV_1 || ino == INV_2)) { + aniX += 1; // 1 to the right + i = InvItem(&aniX, &aniY, false); + if (i == INV_NOICON) { + aniX -= 1; // 1 down + aniY += 1; + i = InvItem(&aniX, &aniY, false); + if (i == INV_NOICON) { + aniX += 1; // 1 down-right + i = InvItem(&aniX, &aniY, false); + } + } + } + + if (ino == INV_CONV) { + ConvAction(i); + } else + InvPickup(i); + } + break; + } +} + +void InvAction(void) { + int index; + PINV_OBJECT invObj; + int aniX, aniY; + int i; + + GetCursorXY(&aniX, &aniY, false); + + switch (InvArea(aniX, aniY)) { + case I_BODY: + if (ino == INV_CONF) { + if (!InventoryHidden) + ConfAction(WhichInvBox(aniX, aniY, false), true); + } else if (ino == INV_CONV) { + index = InvItem(&aniX, &aniY, false); + ConvAction(index); + } else { + index = InvItem(&aniX, &aniY, false); + if (index != INV_NOICON) { + if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) { + invObj = findInvObject(InvD[ino].ItemOrder[index]); + if (invObj->hScript) + RunInvTinselCode(invObj, ACTION, INV_ACTION, index); + } + } + } + break; + + case I_MOVE: // Maximise/unmaximise inventory + if (!InvD[ino].resizable) + break; + + if (!InventoryMaximised) { + InvD[ino].sNoofHicons = InvD[ino].NoofHicons; + InvD[ino].sNoofVicons = InvD[ino].NoofVicons; + InvD[ino].NoofHicons = InvD[ino].MaxHicons; + InvD[ino].NoofVicons = InvD[ino].MaxVicons; + InventoryMaximised = true; + + i = InvD[ino].inventoryX; + InvD[ino].inventoryX = InvD[ino].otherX; + InvD[ino].otherX = i; + i = InvD[ino].inventoryY; + InvD[ino].inventoryY = InvD[ino].otherY; + InvD[ino].otherY = i; + } else { + InvD[ino].NoofHicons = InvD[ino].sNoofHicons; + InvD[ino].NoofVicons = InvD[ino].sNoofVicons; + InventoryMaximised = false; + + i = InvD[ino].inventoryX; + InvD[ino].inventoryX = InvD[ino].otherX; + InvD[ino].otherX = i; + i = InvD[ino].inventoryY; + InvD[ino].inventoryY = InvD[ino].otherY; + InvD[ino].otherY = i; + } + + // Delete current, and re-draw + DumpDobjArray(); + DumpObjArray(); + ConstructInventory(FULL); + break; + + case I_UP: + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + if (InvD[ino].FirstDisp < 0) + InvD[ino].FirstDisp = 0; + ItemsChanged = true; + break; + case I_DOWN: + if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) { + InvD[ino].FirstDisp += InvD[ino].NoofHicons; + ItemsChanged = true; + } + break; + } + +} + + +void InvLook(void) { + int index; + PINV_OBJECT invObj; + int aniX, aniY; + + GetCursorXY(&aniX, &aniY, false); + + switch (InvArea(aniX, aniY)) { + case I_BODY: + index = InvItem(&aniX, &aniY, false); + if (index != INV_NOICON) { + if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) { + invObj = findInvObject(InvD[ino].ItemOrder[index]); + if (invObj->hScript) + RunInvTinselCode(invObj, LOOK, INV_LOOK, index); + } + } + break; + + case I_NOTIN: + if (ino == INV_CONV) + ConvAction(INV_CLOSEICON); + KillInventory(); + break; + } +} + + +/**************************************************************************/ +/********************* Incoming events ************************************/ +/**************************************************************************/ + + +void ButtonToInventory(BUTEVENT be) { + if (InventoryHidden) + return; + + switch (be) { + case INV_PICKUP: // BE_SLEFT + InvSLClick(); + break; + + case INV_LOOK: // BE_SRIGHT + if (IsConfWindow()) + InvSLClick(); + else + InvLook(); + break; + + case INV_ACTION: // BE_DLEFT + if (InvDragging != ID_MDCONT) + InvDragEnd(); + InvAction(); + break; + + case BE_LDSTART: // Left drag start + InvDragStart(); + break; + + case BE_LDEND: // Left drag end + InvDragEnd(); + break; + +// case BE_DLEFT: // Double click left (also ends left drag) +// ButtonToInventory(LDEND); +// break; + + case BE_RDSTART: + case BE_RDEND: + case BE_UNKNOWN: + break; + default: + break; + } +} + +void KeyToInventory(KEYEVENT ke) { + int i; + + switch (ke) { + case ESC_KEY: + if (InventoryState == ACTIVE_INV && ino == INV_CONF && cd.Box != optionBox) + bOpenConf = true; + CloseInventory(); + break; + + case PGDN_KEY: + if (ino == INV_CONF) { + // Only act if load or save screen + if (cd.Box != loadBox && cd.Box != saveBox) + break; + + ConfActionSpecial(IB_SLIDE_DOWN); + } else { + // This code is a copy of SLClick on IB_SLIDE_DOWN + // TODO: So share this duplicate code + if (InvD[ino].NoofVicons == 1) + if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) + InvD[ino].FirstDisp += InvD[ino].NoofHicons; + for (i = 1; i < InvD[ino].NoofVicons; i++) { + if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) + InvD[ino].FirstDisp += InvD[ino].NoofHicons; + } + ItemsChanged = true; + } + break; + + case PGUP_KEY: + if (ino == INV_CONF) { + // Only act if load or save screen + if (cd.Box != loadBox && cd.Box != saveBox) + break; + + ConfActionSpecial(IB_SLIDE_UP); + } else { + // This code is a copy of SLClick on I_SLIDE_UP + // TODO: So share this duplicate code + if (InvD[ino].NoofVicons == 1) + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + for (i = 1; i < InvD[ino].NoofVicons; i++) + InvD[ino].FirstDisp -= InvD[ino].NoofHicons; + if (InvD[ino].FirstDisp < 0) + InvD[ino].FirstDisp = 0; + ItemsChanged = true; + } + break; + + case HOME_KEY: + if (ino == INV_CONF) { + // Only act if load or save screen + if (cd.Box != loadBox && cd.Box != saveBox) + break; + + firstFile(0); + AddBoxes(true); + cd.selBox = 0; + Select(cd.selBox, true); + } else { + InvD[ino].FirstDisp = 0; + ItemsChanged = true; + } + break; + + case END_KEY: + if (ino == INV_CONF) { + // Only act if load or save screen + if (cd.Box != loadBox && cd.Box != saveBox) + break; + + firstFile(MAX_SFILES); // Will get reduced to appropriate value + AddBoxes(true); + cd.selBox = 0; + Select(cd.selBox, true); + } else { + InvD[ino].FirstDisp = InvD[ino].NoofItems - InvD[ino].NoofHicons*InvD[ino].NoofVicons; + if (InvD[ino].FirstDisp < 0) + InvD[ino].FirstDisp = 0; + ItemsChanged = true; + } + break; + + default: + error("We're at KeyToInventory(), with default"); + } +} + +/**************************************************************************/ +/************************* Odds and Ends **********************************/ +/**************************************************************************/ + +/** + * Called from Glitter function invdepict() + * Changes (permanently) the animation film for that object. + */ + +void invObjectFilm(int object, SCNHANDLE hFilm) { + PINV_OBJECT invObj; + + invObj = findInvObject(object); + invObj->hFilm = hFilm; + + if (HeldItem != object) + ItemsChanged = true; +} + +/** + * (Un)serialize the inventory data for save/restore game. + */ + +void syncInvInfo(Serializer &s) { + for (int i = 0; i < NUM_INV; i++) { + s.syncAsSint32LE(InvD[i].MinHicons); + s.syncAsSint32LE(InvD[i].MinVicons); + s.syncAsSint32LE(InvD[i].MaxHicons); + s.syncAsSint32LE(InvD[i].MaxVicons); + s.syncAsSint32LE(InvD[i].NoofHicons); + s.syncAsSint32LE(InvD[i].NoofVicons); + for (int j = 0; j < MAX_ININV; j++) { + s.syncAsSint32LE(InvD[i].ItemOrder[j]); + } + s.syncAsSint32LE(InvD[i].NoofItems); + s.syncAsSint32LE(InvD[i].FirstDisp); + s.syncAsSint32LE(InvD[i].inventoryX); + s.syncAsSint32LE(InvD[i].inventoryY); + s.syncAsSint32LE(InvD[i].otherX); + s.syncAsSint32LE(InvD[i].otherY); + s.syncAsSint32LE(InvD[i].MaxInvObj); + s.syncAsSint32LE(InvD[i].hInvTitle); + s.syncAsSint32LE(InvD[i].resizable); + s.syncAsSint32LE(InvD[i].moveable); + s.syncAsSint32LE(InvD[i].sNoofHicons); + s.syncAsSint32LE(InvD[i].sNoofVicons); + s.syncAsSint32LE(InvD[i].bMax); + } +} + +/**************************************************************************/ +/************************ Initialisation stuff ****************************/ +/**************************************************************************/ + +/** + * Called from PlayGame(), stores handle to inventory objects' data - + * its id, animation film and Glitter script. + */ +// Note: the SCHANDLE type here has been changed to a void* +void RegisterIcons(void *cptr, int num) { + numObjects = num; + pio = (PINV_OBJECT) cptr; +} + +/** + * Called from Glitter function 'dec_invw()' - Declare the bits that the + * inventory windows are constructed from, and special cursors. + */ + +void setInvWinParts(SCNHANDLE hf) { +#ifdef DEBUG + const FILM *pfilm; +#endif + + winPartsf = hf; + +#ifdef DEBUG + pfilm = (const FILM *)LockMem(hf); + assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORREELS); // not as many reels as expected +#endif +} + +/** + * Called from Glitter function 'dec_flags()' - Declare the language + * flag films + */ + +void setFlagFilms(SCNHANDLE hf) { +#ifdef DEBUG + const FILM *pfilm; +#endif + + flagFilm = hf; + +#ifdef DEBUG + pfilm = (const FILM *)LockMem(hf); + assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORFREELS); // not as many reels as expected +#endif +} + +void setConfigStrings(SCNHANDLE *tp) { + memcpy(configStrings, tp, sizeof(configStrings)); +} + +/** + * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() + * - Declare the heading text and dimensions etc. + */ + +void idec_inv(int num, SCNHANDLE text, int MaxContents, + int MinWidth, int MinHeight, + int StartWidth, int StartHeight, + int MaxWidth, int MaxHeight, + int startx, int starty, bool moveable) { + if (MaxWidth > MAXHICONS) + MaxWidth = MAXHICONS; // Max window width + if (MaxHeight > MAXVICONS) + MaxHeight = MAXVICONS; // Max window height + if (MaxContents > MAX_ININV) + MaxContents = MAX_ININV; // Max contents + + if (StartWidth > MaxWidth) + StartWidth = MaxWidth; + if (StartHeight > MaxHeight) + StartHeight = MaxHeight; + + InventoryState = IDLE_INV; + + InvD[num].MaxHicons = MaxWidth; + InvD[num].MinHicons = MinWidth; + InvD[num].MaxVicons = MaxHeight; + InvD[num].MinVicons = MinHeight; + + InvD[num].NoofHicons = StartWidth; + InvD[num].NoofVicons = StartHeight; + + memset(InvD[num].ItemOrder, 0, sizeof(InvD[num].ItemOrder)); + InvD[num].NoofItems = 0; + + InvD[num].FirstDisp = 0; + + InvD[num].inventoryX = startx; + InvD[num].inventoryY = starty; + InvD[num].otherX = 21; + InvD[num].otherY = 15; + + InvD[num].MaxInvObj = MaxContents; + + InvD[num].hInvTitle = text; + + if (MaxWidth != MinWidth && MaxHeight != MinHeight) + InvD[num].resizable = true; + + InvD[num].moveable = moveable; + + InvD[num].bMax = false; +} + +/** + * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() + * - Declare the heading text and dimensions etc. + */ + +void idec_convw(SCNHANDLE text, int MaxContents, + int MinWidth, int MinHeight, + int StartWidth, int StartHeight, + int MaxWidth, int MaxHeight) { + idec_inv(INV_CONV, text, MaxContents, MinWidth, MinHeight, + StartWidth, StartHeight, MaxWidth, MaxHeight, + 20, 8, true); +} + +/** + * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() + * - Declare the heading text and dimensions etc. + */ + +void idec_inv1(SCNHANDLE text, int MaxContents, + int MinWidth, int MinHeight, + int StartWidth, int StartHeight, + int MaxWidth, int MaxHeight) { + idec_inv(INV_1, text, MaxContents, MinWidth, MinHeight, + StartWidth, StartHeight, MaxWidth, MaxHeight, + 100, 100, true); +} + +/** + * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2() + * - Declare the heading text and dimensions etc. + */ + +void idec_inv2(SCNHANDLE text, int MaxContents, + int MinWidth, int MinHeight, + int StartWidth, int StartHeight, + int MaxWidth, int MaxHeight) { + idec_inv(INV_2, text, MaxContents, MinWidth, MinHeight, + StartWidth, StartHeight, MaxWidth, MaxHeight, + 100, 100, true); +} + +int InvGetLimit(int invno) { + assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported + + return InvD[invno].MaxInvObj; +} + +void InvSetLimit(int invno, int MaxContents) { + assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported + assert(MaxContents >= InvD[invno].NoofItems); // can't reduce maximum contents below current contents + + if (MaxContents > MAX_ININV) + MaxContents = MAX_ININV; // Max contents + + InvD[invno].MaxInvObj = MaxContents; +} + +void InvSetSize(int invno, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { + assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported + + if (StartWidth > MaxWidth) + StartWidth = MaxWidth; + if (StartHeight > MaxHeight) + StartHeight = MaxHeight; + + InvD[invno].MaxHicons = MaxWidth; + InvD[invno].MinHicons = MinWidth; + InvD[invno].MaxVicons = MaxHeight; + InvD[invno].MinVicons = MinHeight; + + InvD[invno].NoofHicons = StartWidth; + InvD[invno].NoofVicons = StartHeight; + + if (MaxWidth != MinWidth && MaxHeight != MinHeight) + InvD[invno].resizable = true; + else + InvD[invno].resizable = false; + + InvD[invno].bMax = false; +} + +/**************************************************************************/ + +bool IsTopWindow(void) { + return (InventoryState == BOGUS_INV); +} + + +bool IsConfWindow(void) { + return (InventoryState == ACTIVE_INV && ino == INV_CONF); +} + + +bool IsConvWindow(void) { + return (InventoryState == ACTIVE_INV && ino == INV_CONV); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/inventory.h b/engines/tinsel/inventory.h new file mode 100644 index 0000000000..6b0c10e3de --- /dev/null +++ b/engines/tinsel/inventory.h @@ -0,0 +1,143 @@ + +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Inventory related functions + */ + +#ifndef TINSEL_INVENTORY_H // prevent multiple includes +#define TINSEL_INVENTORY_H + +#include "tinsel/dw.h" +#include "tinsel/events.h" // for KEYEVENT, BUTEVENT + +namespace Tinsel { + +class Serializer; + +enum { + INV_OPEN = -1, + INV_CONV = 0, + INV_1 = 1, + INV_2 = 2, + INV_CONF = 3, + + NUM_INV = 4 +}; + +/** structure of each inventory object */ +struct INV_OBJECT { + int32 id; // inventory objects id + SCNHANDLE hFilm; // inventory objects animation film + SCNHANDLE hScript; // inventory objects event handling script + int32 attribute; // inventory object's attribute +}; +typedef INV_OBJECT *PINV_OBJECT; + +void PopUpInventory(int invno); + +enum CONFTYPE { + SAVE, LOAD, QUIT, OPTION, RESTART, SOUND, CONTROLS, SUBT, TOPWIN +}; + +void PopUpConf(CONFTYPE type); + + +void Xmovement(int x); +void Ymovement(int y); + +void ButtonToInventory(BUTEVENT be); + +void KeyToInventory(KEYEVENT ke); + + +int WhichItemHeld(void); + +void HoldItem(int item); +void DropItem(int item); +void AddToInventory(int invno, int icon, bool hold); +bool RemFromInventory(int invno, int icon); + + +void RegisterIcons(void *cptr, int num); + +void idec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight); +void idec_inv1(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight); +void idec_inv2(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight); + +bool InventoryActive(void); + +void AddIconToPermanentDefaultList(int icon); + +void convPos(int bpos); +void ConvPoly(HPOLYGON hp); +int convIcon(void); +void CloseDownConv(void); +void convHide(bool hide); +bool convHid(void); + +enum { + INV_NOICON = -1, + INV_CLOSEICON = -2, + INV_OPENICON = -3, + INV_HELDNOTIN = -4 +}; + +void ConvAction(int index); + +void InventoryIconCursor(void); + +void setInvWinParts(SCNHANDLE hf); +void setFlagFilms(SCNHANDLE hf); +void setConfigStrings(SCNHANDLE *tp); + +int InvItem(int *x, int *y, bool update); +int InvItemId(int x, int y); + +int InventoryPos(int num); + +bool IsInInventory(int object, int invnum); + +void KillInventory(void); + +void invObjectFilm(int object, SCNHANDLE hFilm); + +void syncInvInfo(Serializer &s); + +int InvGetLimit(int invno); +void InvSetLimit(int invno, int n); +void InvSetSize(int invno, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight); + +int WhichInventoryOpen(void); + +bool IsTopWindow(void); +bool IsConfWindow(void); +bool IsConvWindow(void); + +} // end of namespace Tinsel + +#endif /* TINSEL_INVENTRY_H */ diff --git a/engines/tinsel/mareels.cpp b/engines/tinsel/mareels.cpp new file mode 100644 index 0000000000..4c64eaf091 --- /dev/null +++ b/engines/tinsel/mareels.cpp @@ -0,0 +1,132 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Functions to set up moving actors' reels. + */ + +#include "tinsel/pcode.h" // For D_UP, D_DOWN +#include "tinsel/rince.h" + +#include "common/util.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +enum { + NUM_INTERVALS = NUM_MAINSCALES - 1, + + // 2 for up and down, 3 allow enough entries for 3 fully subscribed moving actors' worth + MAX_SCRENTRIES = NUM_INTERVALS*2*3 +}; + +struct SCIdataStruct { + int actor; + int scale; + int direction; + SCNHANDLE reels[4]; +}; + +static SCIdataStruct SCIdata[MAX_SCRENTRIES]; + +static int scrEntries = 0; + +/** + * Return handle to actor's talk reel at present scale and direction. + */ +SCNHANDLE GetMactorTalkReel(PMACTOR pActor, TFTYPE dirn) { + assert(1 <= pActor->scale && pActor->scale <= TOTAL_SCALES); + switch (dirn) { + case TF_NONE: + return pActor->TalkReels[pActor->scale-1][pActor->dirn]; + + case TF_UP: + return pActor->TalkReels[pActor->scale-1][AWAY]; + + case TF_DOWN: + return pActor->TalkReels[pActor->scale-1][FORWARD]; + + case TF_LEFT: + return pActor->TalkReels[pActor->scale-1][LEFTREEL]; + + case TF_RIGHT: + return pActor->TalkReels[pActor->scale-1][RIGHTREEL]; + + default: + error("GetMactorTalkReel() - illegal direction!"); + } +} + +/** + * scalingreels + */ +void setscalingreels(int actor, int scale, int direction, + SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) { + assert(scale >= 1 && scale <= NUM_MAINSCALES); // invalid scale + assert(!(scale == 1 && direction == D_UP) && + !(scale == NUM_MAINSCALES && direction == D_DOWN)); // illegal direction from scale + + assert(scrEntries < MAX_SCRENTRIES); // Scaling reels limit reached! + + SCIdata[scrEntries].actor = actor; + SCIdata[scrEntries].scale = scale; + SCIdata[scrEntries].direction = direction; + SCIdata[scrEntries].reels[LEFTREEL] = left; + SCIdata[scrEntries].reels[RIGHTREEL] = right; + SCIdata[scrEntries].reels[FORWARD] = forward; + SCIdata[scrEntries].reels[AWAY] = away; + scrEntries++; +} + +/** + * ScalingReel + */ +SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRREEL reel) { + int d; // Direction + + // The smaller the number, the bigger the scale + if (scale1 < scale2) + d = D_DOWN; + else + d = D_UP; + + for (int i = 0; i < scrEntries; i++) { + if (SCIdata[i].actor == ano && SCIdata[i].scale == scale1 && SCIdata[i].direction == d) { + if (SCIdata[i].reels[reel] == TF_NONE) + return 0; + else + return SCIdata[i].reels[reel]; + } + } + return 0; +} + +/** + * RebootScalingReels + */ +void RebootScalingReels(void) { + scrEntries = 0; + memset(SCIdata, 0, sizeof(SCIdata)); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/module.mk b/engines/tinsel/module.mk new file mode 100644 index 0000000000..b00afcddbc --- /dev/null +++ b/engines/tinsel/module.mk @@ -0,0 +1,52 @@ +MODULE := engines/tinsel + +MODULE_OBJS = \ + actors.o \ + anim.o \ + background.o \ + bg.o \ + cliprect.o \ + config.o \ + cursor.o \ + debugger.o \ + detection.o \ + effect.o \ + events.o \ + faders.o \ + font.o \ + graphics.o \ + handle.o \ + heapmem.o \ + inventory.o \ + mareels.o \ + move.o \ + multiobj.o \ + music.o \ + object.o \ + palette.o \ + pcode.o \ + pdisplay.o \ + play.o \ + polygons.o \ + rince.o \ + saveload.o \ + savescn.o \ + scene.o \ + sched.o \ + scn.o \ + scroll.o \ + sound.o \ + strres.o \ + text.o \ + timers.o \ + tinlib.o \ + tinsel.o \ + token.o + +# This module can be built as a plugin +ifeq ($(ENABLE_TINSEL), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/tinsel/move.cpp b/engines/tinsel/move.cpp new file mode 100644 index 0000000000..803bc5fd7b --- /dev/null +++ b/engines/tinsel/move.cpp @@ -0,0 +1,1618 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Handles walking and use of the path system. + * + * Contains the dodgiest code in the whole system. + */ + +#include "tinsel/actors.h" +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/graphics.h" +#include "tinsel/move.h" +#include "tinsel/multiobj.h" // multi-part object defintions etc. +#include "tinsel/object.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/scroll.h" +#include "tinsel/tinlib.h" // For stand() + +namespace Tinsel { + +//----------------- DEVELOPMENT OPTIONS -------------------- + +#define SLOW_RINCE_DOWN 0 + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern int BackgroundWidth(void); +extern int BackgroundHeight(void); + + +// in POLYGONS.C +// Deliberatley defined here, and not in polygons.h +HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta); + +//----------------- LOCAL DEFINES -------------------- + +#define XMDIST 4 +#define XHMDIST 2 +#define YMDIST 2 +#define YHMDIST 2 + +#define XTHERE 1 +#define XRESTRICT 2 +#define YTHERE 4 +#define YRESTRICT 8 +#define STUCK 16 + +#define LEAVING_PATH 0x100 +#define ENTERING_BLOCK 0x200 +#define ENTERING_MBLOCK 0x400 + +#define ALL_SORTED 1 +#define NOT_SORTED 0 + + +//----------------- LOCAL GLOBAL DATA -------------------- + +#if SLOW_RINCE_DOWN +static int Interlude = 0; // For slowing down walking, for testing +static int BogusVar = 0; // For slowing down walking, for testing +#endif + +static int32 DefaultRefer = 0; +static int hSlowVar = 0; // used by MoveActor() + + +//----------------- FORWARD REFERENCES -------------------- + +static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY, + int *newx, int *newy, int *s1, int *s2, HPOLYGON *hS2p, + bool bOver, bool bBodge, + PMACTOR pActor, PMACTOR *collisionActor = 0); + + +#if SLOW_RINCE_DOWN +/** + * AddInterlude + */ + +void AddInterlude(int n) { + Interlude += n; + if (Interlude < 0) + Interlude = 0; +} +#endif + +/** + * Given (x, y) of a click within a path polygon, checks that the + * co-ordinates are not within a blocking polygon. If it is not, the + * destination is the click point, otherwise tries to find a legal point + * below or above the click point. + * Returns: + * NOT_SORTED - if a destination is worked out (movement required) + * ALL_SORTED - no destination found (so no movement required) + */ +static int ClickedOnPath(int clickX, int clickY, int *ptgtX, int *ptgtY) { + int Loffset, Toffset; + int i; + + /*-------------------------------------- + Clicked within a path, + go to where requested unless blocked. + --------------------------------------*/ + if (InPolygon(clickX, clickY, BLOCKING) == NOPOLY) { + // Not in a blocking polygon - go to where requested. + *ptgtX = clickX; + *ptgtY = clickY; + } else { + /*------------------------------------------------------ + In a Blocking polygon - try searching down and up. + If still nowhere (for now) give up! + ------------------------------------------------------*/ + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + for (i = clickY+1; i < SCREEN_HEIGHT + Toffset; i++) { + // Don't leave the path system + if (InPolygon(clickX, i, PATH) == NOPOLY) { + i = SCREEN_HEIGHT; + break; + } + if (InPolygon(clickX, i, BLOCKING) == NOPOLY) { + *ptgtX = clickX; + *ptgtY = i; + break; + } + } + if (i == SCREEN_HEIGHT) { + for (i = clickY-1; i >= Toffset; i--) { + // Don't leave the path system + if (InPolygon(clickX, i, PATH) == NOPOLY) { + i = -1; + break; + } + if (InPolygon(clickX, i, BLOCKING) == NOPOLY) { + *ptgtX = clickX; + *ptgtY = i; + break; + } + } + } + if (i < 0) { + return ALL_SORTED; + } + } + return NOT_SORTED; +} + +/** + * Given (x, y) of a click within a referral polygon, works out the + * destination according to the referral type. + * Returns: + * NOT_SORTED - if a destination is worked out (movement required) + * ALL_SORTED - no destination found (so no movement required) + */ +static int ClickedOnRefer(HPOLYGON hRefpoly, int clickX, int clickY, int *ptgtX, int *ptgtY) { + int i; + int end; // Extreme of the scene + int Loffset, Toffset; + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + *ptgtX = *ptgtY = -1; + + switch (PolySubtype(hRefpoly)) { + case REF_POINT: // Go to specified node + getPolyNode(hRefpoly, ptgtX, ptgtY); + assert(InPolygon(*ptgtX, *ptgtY, PATH) != NOPOLY); // POINT Referral to illegal point + break; + + case REF_DOWN: // Search downwards + end = BackgroundHeight(); + for (i = clickY+1; i < end; i++) + if (InPolygon(clickX, i, PATH) != NOPOLY + && InPolygon(clickX, i, BLOCKING) == NOPOLY) { + *ptgtX = clickX; + *ptgtY = i; + break; + } + break; + + case REF_UP: // Search upwards + for (i = clickY-1; i >= 0; i--) + if (InPolygon(clickX, i, PATH) != NOPOLY + && InPolygon(clickX, i, BLOCKING) == NOPOLY) { + *ptgtX = clickX; + *ptgtY = i; + break; + } + break; + + case REF_RIGHT: // Search to the right + end = BackgroundWidth(); + for (i = clickX+1; i < end; i++) + if (InPolygon(i, clickY, PATH) != NOPOLY + && InPolygon(i, clickY, BLOCKING) == NOPOLY) { + *ptgtX = i; + *ptgtY = clickY; + break; + } + break; + + case REF_LEFT: // Search to the left + for (i = clickX-1; i >= 0; i--) + if (InPolygon(i, clickY, PATH) != NOPOLY + && InPolygon(i, clickY, BLOCKING) == NOPOLY) { + *ptgtX = i; + *ptgtY = clickY; + break; + } + break; + } + if (*ptgtX != -1 && *ptgtY != -1) { + return NOT_SORTED; + } else + return ALL_SORTED; +} + +/** + * Given (x, y) of a click, works out the destination according to the + * default referral type. + * Returns: + * NOT_SORTED - if a destination is worked out (movement required) + * ALL_SORTED - no destination found (so no movement required) + */ +static int ClickedOnNothing(int clickX, int clickY, int *ptgtX, int *ptgtY) { + int i; + int end; // Extreme of the scene + int Loffset, Toffset; + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + switch (DefaultRefer) { + case REF_DEFAULT: + // Try searching down and up (onscreen). + for (i = clickY+1; i < SCREEN_HEIGHT+Toffset; i++) + if (InPolygon(clickX, i, PATH) != NOPOLY) { + return ClickedOnPath(clickX, i, ptgtX, ptgtY); + } + for (i = clickY-1; i >= Toffset; i--) + if (InPolygon(clickX, i, PATH) != NOPOLY) { + return ClickedOnPath(clickX, i, ptgtX, ptgtY); + } + // Try searching down and up (offscreen). + end = BackgroundHeight(); + for (i = clickY+1; i < end; i++) + if (InPolygon(clickX, i, PATH) != NOPOLY) { + return ClickedOnPath(clickX, i, ptgtX, ptgtY); + } + for (i = clickY-1; i >= 0; i--) + if (InPolygon(clickX, i, PATH) != NOPOLY) { + return ClickedOnPath(clickX, i, ptgtX, ptgtY); + } + break; + + case REF_UP: + for (i = clickY-1; i >= 0; i--) + if (InPolygon(clickX, i, PATH) != NOPOLY) { + return ClickedOnPath(clickX, i, ptgtX, ptgtY); + } + break; + + case REF_DOWN: + end = BackgroundHeight(); + for (i = clickY+1; i < end; i++) + if (InPolygon(clickX, i, PATH) != NOPOLY) { + return ClickedOnPath(clickX, i, ptgtX, ptgtY); + } + break; + + case REF_LEFT: + for (i = clickX-1; i >= 0; i--) + if (InPolygon(i, clickY, PATH) != NOPOLY) { + return ClickedOnPath(i, clickY, ptgtX, ptgtY); + } + break; + + case REF_RIGHT: + end = BackgroundWidth(); + for (i = clickX + 1; i < end; i++) + if (InPolygon(i, clickY, PATH) != NOPOLY) { + return ClickedOnPath(i, clickY, ptgtX, ptgtY); + } + break; + } + + // Going nowhere! + return ALL_SORTED; +} + +/** + * Given (x, y) of the click, ascertains whether the click is within a + * path, within a referral poly, or niether. The appropriate function + * then gets called to give us a revised destination. + * Returns: + * NOT_SORTED - if a destination is worked out (movement required) + * ALL_SORTED - no destination found (so no movement required) + */ +static int WorkOutDestination(int clickX, int clickY, int *ptgtX, int *ptgtY) { + HPOLYGON hPoly; + + /*-------------------------------------- + Clicked within a path? + if not, within a referral poly? + if not, try and sort something out. + ---------------------------------------*/ + if (InPolygon(clickX, clickY, PATH) != NOPOLY) { + return ClickedOnPath(clickX, clickY, ptgtX, ptgtY); + } else if ((hPoly = InPolygon(clickX, clickY, REFER)) != NOPOLY) { + return ClickedOnRefer(hPoly, clickX, clickY, ptgtX, ptgtY); + } else { + return ClickedOnNothing(clickX, clickY, ptgtX, ptgtY); + } +} + +/** + * Work out which reel to adopt for a section of movement. + */ +static DIRREEL GetDirectionReel(int fromx, int fromy, int tox, int toy, DIRREEL lastreel, HPOLYGON hPath) { + int xchange = 0, ychange = 0; + enum {X_NONE, X_LEFT, X_RIGHT, X_NO} xdir; + enum {Y_NONE, Y_UP, Y_DOWN, Y_NO} ydir; + + DIRREEL reel = lastreel; // Leave alone if can't decide + + /* + * Determine size and direction of X movement. + * i.e. left, right, none or not allowed. + */ + if (getPolyReelType(hPath) == REEL_VERT) + xdir = X_NO; + else if (tox == -1) + xdir = X_NONE; + else { + xchange = tox - fromx; + if (xchange > 0) + xdir = X_RIGHT; + else if (xchange < 0) { + xchange = -xchange; + xdir = X_LEFT; + } else + xdir = X_NONE; + } + + /* + * Determine size and direction of Y movement. + * i.e. up, down, none or not allowed. + */ + if (getPolyReelType(hPath) == REEL_HORIZ) + ydir = Y_NO; + else if (toy == -1) + ydir = Y_NONE; + else { + ychange = toy - fromy; + if (ychange > 0) + ydir = Y_DOWN; + else if (ychange < 0) { + ychange = -ychange; + ydir = Y_UP; + } else + ydir = Y_NONE; + } + + /* + * Some adjustment to allow for different x and y pixell sizes. + */ + ychange += ychange; // Double y distance to cover + + /* + * Determine which reel to use. + */ + if (xdir == X_NO) { + // Forced to be FORWARD or AWAY + switch (ydir) { + case Y_DOWN: + reel = FORWARD; + break; + case Y_UP: + reel = AWAY; + break; + default: + if (reel != AWAY) // No gratuitous turn + reel = FORWARD; + break; + } + } else if (ydir == Y_NO) { + // Forced to be LEFTREEL or RIGHTREEL + switch (xdir) { + case X_LEFT: + reel = LEFTREEL; + break; + case X_RIGHT: + reel = RIGHTREEL; + break; + default: + if (reel != LEFTREEL) // No gratuitous turn + reel = RIGHTREEL; + break; + } + } else if (xdir != X_NONE || ydir != Y_NONE) { + if (xdir == X_NONE) + reel = (ydir == Y_DOWN) ? FORWARD : AWAY; + else if (ydir == Y_NONE) + reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL; + else { + bool DontBother = false; + + if (xchange <= 4 && ychange <= 4) { + switch (reel) { + case LEFTREEL: + if (xdir == X_LEFT) + DontBother = true; + break; + case RIGHTREEL: + if (xdir == X_RIGHT) + DontBother = true; + break; + case FORWARD: + if (ydir == Y_DOWN) + DontBother = true; + break; + case AWAY: + if (ydir == Y_UP) + DontBother = true; + break; + } + } + if (!DontBother) { + if (xchange > ychange) + reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL; + else + reel = (ydir == Y_DOWN) ? FORWARD : AWAY; + } + } + } + return reel; +} + +/** + * Haven't moved, look towards the cursor. + */ +static void GotThereWithoutMoving(PMACTOR pActor) { + int curX, curY; + DIRREEL reel; + + if (!pActor->TagReelRunning) { + GetCursorXYNoWait(&curX, &curY, true); + + reel = GetDirectionReel(pActor->objx, pActor->objy, curX, curY, pActor->dirn, pActor->hCpath); + + if (reel != pActor->dirn) + SetMActorWalkReel(pActor, reel, pActor->scale, false); + } +} + +/** + * Arrived at final destination. + */ +static void GotThere(PMACTOR pActor) { + pActor->targetX = pActor->targetY = -1; // 4/1/95 + pActor->ItargetX = pActor->ItargetY = -1; + pActor->UtargetX = pActor->UtargetY = -1; + + // Perhaps we have'nt moved. + if (pActor->objx == (int)pActor->fromx && pActor->objy == (int)pActor->fromy) + GotThereWithoutMoving(pActor); + + ReTagActor(pActor->actorID); // Tag allowed while stationary + + SetMActorStanding(pActor); + + pActor->bMoving = false; +} + +enum cgt { GT_NOTL, GT_NOTB, GT_NOT2, GT_OK, GT_MAY }; + +/** + * Can we get straight there? + */ +static cgt CanGetThere(PMACTOR pActor, int tx, int ty) { + int s1, s2; // s2 not used here! + HPOLYGON hS2p; // nor is s2p! + int nextx, nexty; + + int targetX = tx; + int targetY = ty; // Ultimate destination + int x = pActor->objx; + int y = pActor->objy; // Present position + + while (targetX != -1 || targetY != -1) { + NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, + &s1, &s2, &hS2p, pActor->over, false, pActor); + + if (s1 == (XTHERE | YTHERE)) { + return GT_OK; // Can get there directly. + } else if (s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) { + return GT_MAY; // Can't get there directly. + } else if (s1 & STUCK) { + if (s2 == LEAVING_PATH) + return GT_NOTL; // Can't get there. + else + return GT_NOTB; // Can't get there. + } else if (x == nextx && y == nexty) { + return GT_NOT2; // Can't get there. + } + x = nextx; + y = nexty; + } + return GT_MAY; +} + + +/** + * Set final destination. + */ +static void SetMoverUltDest(PMACTOR pActor, int x, int y) { + pActor->UtargetX = x; + pActor->UtargetY = y; + pActor->hUpath = InPolygon(x, y, PATH); + + assert(pActor->hUpath != NOPOLY || pActor->bIgPath); // Invalid ultimate destination +} + +/** + * Set intermediate destination. + * + * If in final destination path, go straight to target. + * If in a neighbouring path to the final destination, if the target path + * is a follow nodes path, head for the end node, otherwise head straight + * for the target. + * Otherwise, head towards the pseudo-centre or end node of the first + * en-route path. + */ +static void SetMoverIntDest(PMACTOR pActor, int x, int y) { + HPOLYGON hIpath, hTpath; + int node; + + hTpath = InPolygon(x, y, PATH); // Target path +#ifdef DEBUG + if (!pActor->bIgPath) + assert(hTpath != NOPOLY); // SetMoverIntDest() - target not in path +#endif + + if (pActor->hCpath == hTpath || pActor->bIgPath + || IsInPolygon(pActor->objx, pActor->objy, hTpath)) { + // In destination path - head straight for the target. + pActor->ItargetX = x; + pActor->ItargetY = y; + pActor->hIpath = hTpath; + } else if (IsAdjacentPath(pActor->hCpath, hTpath)) { + // In path adjacent to target + if (PolySubtype(hTpath) != NODE) { + // Target path is normal - head for target. + // Added 26/01/95, innroom + if (CanGetThere(pActor, x, y) == GT_NOTL) { + NearestCorner(&x, &y, pActor->hCpath, hTpath); + } + pActor->ItargetX = x; + pActor->ItargetY = y; + } else { + // Target path is node - head for end node. + node = NearestEndNode(hTpath, pActor->objx, pActor->objy); + getNpathNode(hTpath, node, &pActor->ItargetX, &pActor->ItargetY); + + } + pActor->hIpath = hTpath; + } else { + assert(hTpath != NOPOLY); // Error 701 + hIpath = getPathOnTheWay(pActor->hCpath, hTpath); + + if (hIpath != NOPOLY) { + /* Head for an en-route path */ + if (PolySubtype(hIpath) != NODE) { + /* En-route path is normal - head for pseudo centre. */ + if (CanGetThere(pActor, x, y) == GT_OK) { + pActor->ItargetX = x; + pActor->ItargetY = y; + } else { + pActor->ItargetX = PolyCentreX(hIpath); + pActor->ItargetY = PolyCentreY(hIpath); + } + } else { + /* En-route path is node - head for end node. */ + node = NearestEndNode(hIpath, pActor->objx, pActor->objy); + getNpathNode(hIpath, node, &pActor->ItargetX, &pActor->ItargetY); + } + pActor->hIpath = hIpath; + } + } + + pActor->InDifficulty = NO_PROB; +} + +/** + * Set short-term destination and adopt the appropriate reel. + */ +static void SetMoverDest(PMACTOR pActor, int x, int y) { + int scale; + DIRREEL reel; + + // Set the co-ordinates requested. + pActor->targetX = x; + pActor->targetY = y; + pActor->InDifficulty = NO_PROB; + + reel = GetDirectionReel(pActor->objx, pActor->objy, x, y, pActor->dirn, pActor->hCpath); + scale = GetScale(pActor->hCpath, pActor->objy); + if (scale != pActor->scale || reel != pActor->dirn) { + SetMActorWalkReel(pActor, reel, scale, false); + } +} + +/** + * SetNextDest + */ +static void SetNextDest(PMACTOR pActor) { + int targetX, targetY; // Ultimate destination + int x, y; // Present position + int nextx, nexty; + int s1, lstatus = 0; + int s2; + HPOLYGON hS2p; + int i; + HPOLYGON hNpoly; + HPOLYGON hPath; + int znode; + int nx, ny; + int sx, sy; + HPOLYGON hEb; + + int ss1, ss2; + HPOLYGON shS2p; + PMACTOR collisionActor; +#if 1 + int sTargetX, sTargetY; +#endif + + /* + * Desired destination (Itarget) is already set + */ + x = pActor->objx; // Current position + y = pActor->objy; + targetX = pActor->ItargetX; // Desired position + targetY = pActor->ItargetY; + + /* + * If we're where we're headed, end it all (the moving). + */ +// if (x == targetX && y == targetY) + if (ABS(x - targetX) < XMDIST && ABS(y - targetY) < YMDIST) { + if (targetX == pActor->UtargetX && targetY == pActor->UtargetY) { + // Desired position + GotThere(pActor); + return; + } else { + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5001 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + } + } + + if (pActor->bNoPath || pActor->bIgPath) { + /* Can get there directly. */ + SetMoverDest(pActor, targetX, targetY); + pActor->over = false; + return; + } + + /*---------------------------------------------------------------------- + | Some work to do here if we're in a follow nodes polygon - basically + | head for the next node. + ----------------------------------------------------------------------*/ + hNpoly = pActor->hFnpath; // The node path we're in (if any) + switch (pActor->npstatus) { + case NOT_IN: + break; + + case ENTERING: + znode = NearestEndNode(hNpoly, x, y); + /* Hang on, we're probably here already! */ + if (znode) { + pActor->npstatus = GOING_DOWN; + pActor->line = znode-1; + getNpathNode(hNpoly, znode - 1, &nx, &ny); + } else { + pActor->npstatus = GOING_UP; + pActor->line = znode; + getNpathNode(hNpoly, 1, &nx, &ny); + } + SetMoverDest(pActor, nx, ny); + + // Test for pseudo-one-node npaths + if (numNodes(hNpoly) == 2 && + ABS(pActor->objx - pActor->targetX) < XMDIST && + ABS(pActor->objy - pActor->targetY) < YMDIST) { + // That's enough, we're leaving + pActor->npstatus = LEAVING; + } else { + // Normal situation + pActor->over = true; + return; + } + // Fall through for LEAVING + + case LEAVING: + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5002 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + targetX = pActor->ItargetX; // Desired position + targetY = pActor->ItargetY; + break; + + case GOING_UP: + i = pActor->line; // The line we're on + + // Is this the final target line? + if (i+1 == pActor->Tline && hNpoly == pActor->hUpath) { + // The final leg of the journey + pActor->line = i+1; + SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); + pActor->over = false; + return; + } else { + // Go to the next node unless we're at the last one + i++; // The node we're at + if (++i < numNodes(hNpoly)) { + getNpathNode(hNpoly, i, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->line = i-1; + if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST && + ABS(pActor->UtargetY - pActor->targetY) < YMDIST) + pActor->over = false; + else + pActor->over = true; + return; + } else { + // Last node - we're off + pActor->npstatus = LEAVING; + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5003 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + targetX = pActor->ItargetX; // Desired position + targetY = pActor->ItargetY; + break; + } + } + + case GOING_DOWN: + i = pActor->line; // The line we're on and the node we're at + + // Is this the final target line? + if (i - 1 == pActor->Tline && hNpoly == pActor->hUpath) { + // The final leg of the journey + SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); + pActor->line = i-1; + pActor->over = false; + return; + } else { + // Go to the next node unless we're at the last one + if (--i >= 0) { + getNpathNode(hNpoly, i, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->line--; /* The next node to head for */ + if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST && + ABS(pActor->UtargetY - pActor->targetY) < YMDIST) + pActor->over = false; + else + pActor->over = true; + return; + } else { + // Last node - we're off + pActor->npstatus = LEAVING; + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5004 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + targetX = pActor->ItargetX; // Desired position + targetY = pActor->ItargetY; + break; + } + } + } + + + + + /*------------------------------------------------------ + | See if it can get there directly. There may be an + | intermediate destination to head for. + ------------------------------------------------------*/ + + while (targetX != -1 || targetY != -1) { +#if 1 + // 'push' the target + sTargetX = targetX; + sTargetY = targetY; +#endif + NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, + &s1, &s2, &hS2p, pActor->over, false, pActor, &collisionActor); + + if (s1 != (XTHERE | YTHERE) && x == nextx && y == nexty) { + ss1 = s1; + ss2 = s2; + shS2p = hS2p; +#if 1 + // 'pop' the target + targetX = sTargetX; + targetY = sTargetY; +#endif + // Note: this aint right - targetX/Y (may) have been + // nobbled by that last call to NewCoOrdinates() + // Re-instating them (can) leads to oscillation + NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty, + &s1, &s2, &hS2p, pActor->over, true, pActor, &collisionActor); + + if (x == nextx && y == nexty) { + s1 = ss1; + s2 = ss2; + hS2p = shS2p; + } + } + + if (s1 == (XTHERE | YTHERE)) { + /* Can get there directly. */ + SetMoverDest(pActor, nextx, nexty); + pActor->over = false; + break; + } else if ((s1 & STUCK) || s1 == (XRESTRICT + YRESTRICT) + || s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) { + /*------------------------------------------------- + Can't go any further in this direction. | + If it's because of a blocking polygon, try to do | + something about it. | + -------------------------------------------------*/ + if (s2 & ENTERING_BLOCK) { + x = pActor->objx; // Current position + y = pActor->objy; + // Go to the nearest corner of the blocking polygon concerned + BlockingCorner(hS2p, &x, &y, pActor->ItargetX, pActor->ItargetY); + SetMoverDest(pActor, x, y); + pActor->over = false; + } else if (s2 & ENTERING_MBLOCK) { + if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) { + // The best we're going to achieve + SetMoverUltDest(pActor, x, y); + SetMoverDest(pActor, x, y); + } else { + sx = pActor->objx; + sy = pActor->objy; +// pActor->objx = x; +// pActor->objy = y; + + hEb = InitExtraBlock(pActor, collisionActor); + x = pActor->objx; + y = pActor->objy; + BlockingCorner(hEb, &x, &y, pActor->ItargetX, pActor->ItargetY); + + pActor->objx = sx; + pActor->objy = sy; + SetMoverDest(pActor, x, y); + pActor->over = false; + } + } else { + /*---------------------------------------- + Currently, this is as far as we can go. | + Definitely room for improvement here! | + ----------------------------------------*/ + hPath = InPolygon(pActor->ItargetX, pActor->ItargetY, PATH); + if (hPath != pActor->hIpath) { + if (IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hIpath)) + hPath = pActor->hIpath; + } + assert(hPath == pActor->hIpath); + + if (pActor->InDifficulty == NO_PROB) { + x = PolyCentreX(hPath); + y = PolyCentreY(hPath); + SetMoverDest(pActor, x, y); + pActor->InDifficulty = TRY_CENTRE; + pActor->over = false; + } else if (pActor->InDifficulty == TRY_CENTRE) { + NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath); + SetMoverDest(pActor, x, y); + pActor->InDifficulty = TRY_CORNER; + pActor->over = false; + } else if (pActor->InDifficulty == TRY_CORNER) { + NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath); + SetMoverDest(pActor, x, y); + pActor->InDifficulty = TRY_NEXTCORNER; + pActor->over = false; + } + } + break; + } + else if (((lstatus & YRESTRICT) && !(s1 & YRESTRICT)) + || ((lstatus & XRESTRICT) && !(s1 & XRESTRICT))) { + /*----------------------------------------------- + A restriction in a direction has been removed. | + Use this as an intermediate destination. | + -----------------------------------------------*/ + SetMoverDest(pActor, nextx, nexty); + pActor->over = false; + break; + } + + x = nextx; + y = nexty; + + /*------------------------- + Change of path polygon? | + -------------------------*/ + hPath = InPolygon(x, y, PATH); + if (pActor->hCpath != hPath && + !IsInPolygon(x, y, pActor->hCpath) && + !IsAdjacentPath(pActor->hCpath, pActor->hIpath)) { + /*---------------------------------------------------------- + If just entering a follow nodes polygon, go to first node.| + Else if just going to pass through, go to pseudo-centre. | + ----------------------------------------------------------*/ + if (PolySubtype(hPath) == NODE && pActor->hFnpath != hPath && pActor->npstatus != LEAVING) { + int node = NearestEndNode(hPath, x, y); + getNpathNode(hPath, node, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->over = true; + } else if (!IsInPolygon(pActor->ItargetX, pActor->ItargetY, hPath) && + !IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hCpath)) { + SetMoverDest(pActor, PolyCentreX(hPath), PolyCentreY(hPath)); + pActor->over = true; + } else { + SetMoverDest(pActor, pActor->ItargetX, pActor->ItargetY); + } + break; + } + + lstatus = s1; + } +} + +/** + * Work out where the next position should be. + * Check that it's in a path and not in a blocking polygon. + */ +static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY, + int *newx, int *newy, int *s1, int *s2, + HPOLYGON *hS2p, bool bOver, bool bBodge, + PMACTOR pActor, PMACTOR *collisionActor) { + HPOLYGON hPoly; + int sidem, depthm; + int sidesteps, depthsteps; + PMACTOR ma; + + *s1 = *s2 = 0; + + /*------------------------------------------------ + Don't overrun if this is the final destination. | + ------------------------------------------------*/ + if (*targetX == pActor->UtargetX && (*targetY == -1 || *targetY == pActor->UtargetY) || + *targetY == pActor->UtargetY && (*targetX == -1 || *targetX == pActor->UtargetX)) + bOver = false; + + /*---------------------------------------------------- + Decide how big a step to attempt in each direction. | + ----------------------------------------------------*/ + sidesteps = *targetX == -1 ? 0 : *targetX - fromx; + sidesteps = ABS(sidesteps); + + depthsteps = *targetY == -1 ? 0 : *targetY - fromy; + depthsteps = ABS(depthsteps); + + if (sidesteps && depthsteps > sidesteps) { + depthm = YMDIST; + sidem = depthm * sidesteps/depthsteps; + + if (!sidem) + sidem = 1; + } else if (depthsteps && sidesteps > depthsteps) { + sidem = XMDIST; + depthm = sidem * depthsteps/sidesteps; + + if (!depthm) { + if (bBodge) + depthm = 1; + } else if (depthm > YMDIST) + depthm = YMDIST; + } else { + sidem = sidesteps ? XMDIST : 0; + depthm = depthsteps ? YMDIST : 0; + } + + *newx = fromx; + *newy = fromy; + + /*------------------------------------------------------------ + If Left-Right movement is required - then make the move, | + but don't overshoot, and do notice when we're already there | + ------------------------------------------------------------*/ + if (*targetX == -1) + *s1 |= XTHERE; + else { + if (*targetX > fromx) { /* To the right? */ + *newx += sidem; // Move to the right... + if (*newx == *targetX) + *s1 |= XTHERE; + else if (*newx > *targetX) { // ...but don't overshoot + if (!bOver) + *newx = *targetX; + else + *targetX = *newx; + *s1 |= XTHERE; + } + } else if (*targetX < fromx) { /* To the left? */ + *newx -= sidem; // Move to the left... + if (*newx == *targetX) + *s1 |= XTHERE; + else if (*newx < *targetX) { // ...but don't overshoot + if (!bOver) + *newx = *targetX; + else + *targetX = *newx; + *s1 |= XTHERE; + } + } else { + *targetX = -1; // We're already there! + *s1 |= XTHERE; + } + } + + /*-------------------------------------------------------------- + If Up-Down movement is required - then make the move, + but don't overshoot, and do notice when we're already there + --------------------------------------------------------------*/ + if (*targetY == -1) + *s1 |= YTHERE; + else { + if (*targetY > fromy) { /* Downwards? */ + *newy += depthm; // Move down... + if (*newy == *targetY) // ...but don't overshoot + *s1 |= YTHERE; + else if (*newy > *targetY) { // ...but don't overshoot + if (!bOver) + *newy = *targetY; + else + *targetY = *newy; + *s1 |= YTHERE; + } + } else if (*targetY < fromy) { /* Upwards? */ + *newy -= depthm; // Move up... + if (*newy == *targetY) // ...but don't overshoot + *s1 |= YTHERE; + else if (*newy < *targetY) { // ...but don't overshoot + if (!bOver) + *newy = *targetY; + else + *targetY = *newy; + *s1 |= YTHERE; + } + } else { + *targetY = -1; // We're already there! + *s1 |= YTHERE; + } + } + + /* Give over if this is it */ + if (*s1 == (XTHERE | YTHERE)) + return; + + /*------------------------------------------------------ + Have worked out where an optimum step would take us. + Must now check if it's in a legal spot. + ------------------------------------------------------*/ + + if (!pActor->bNoPath && !pActor->bIgPath) { + /*------------------------------ + Must stay in a path polygon. + -------------------------------*/ + hPoly = InPolygon(*newx, *newy, PATH); + if (hPoly == NOPOLY) { + *s2 = LEAVING_PATH; // Trying to leave the path polygons + + if (*newx != fromx && InPolygon(*newx, fromy, PATH) != NOPOLY && InPolygon(*newx, fromy, BLOCKING) == NOPOLY) { + *newy = fromy; + *s1 |= YRESTRICT; + } else if (*newy != fromy && InPolygon(fromx, *newy, PATH) != NOPOLY && InPolygon(fromx, *newy, BLOCKING) == NOPOLY) { + *newx = fromx; + *s1 |= XRESTRICT; + } else { + *newx = fromx; + *newy = fromy; +#if 1 + *targetX = *targetY = -1; +#endif + *s1 |= STUCK; + return; + } + } + + /*-------------------------------------- + Must stay out of blocking polygons. + --------------------------------------*/ + hPoly = InPolygon(*newx, *newy, BLOCKING); + if (hPoly != NOPOLY) { + *s2 = ENTERING_BLOCK; // Trying to enter a blocking poly + *hS2p = hPoly; + + if (*newx != fromx && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) { + *newy = fromy; + *s1 |= YRESTRICT; + } else if (*newy != fromy && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) { + *newx = fromx; + *s1 |= XRESTRICT; + } else { + *newx = fromx; + *newy = fromy; +#if 1 + *targetX = *targetY = -1; +#endif + *s1 |= STUCK; + } + } + /*------------------------------------------------------ + Must stay out of moving actors' blocking polygons. + ------------------------------------------------------*/ + ma = InMActorBlock(pActor, *newx, *newy); + if (ma != NULL) { + // Ignore if already in it (it may have just appeared) + if (!InMActorBlock(pActor, pActor->objx, pActor->objy)) { + *s2 = ENTERING_MBLOCK; // Trying to walk through an actor + + *hS2p = -1; + if (collisionActor) + *collisionActor = ma; + + if (*newx != fromx && InMActorBlock(pActor, *newx, fromy) == NULL + && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) { + *newy = fromy; + *s1 |= YRESTRICT; + } else if (*newy != fromy && InMActorBlock(pActor, fromx, *newy) == NULL + && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) { + *newx = fromx; + *s1 |= XRESTRICT; + } else { + *newx = fromx; + *newy = fromy; +#if 1 + *targetX = *targetY = -1; +#endif + *s1 |= STUCK; + } + } + } + } +} + +/** + * SetOffWithinNodePath + */ +static void SetOffWithinNodePath(PMACTOR pActor, HPOLYGON StartPath, HPOLYGON DestPath, + int targetX, int targetY) { + int endnode; + HPOLYGON hIpath; + int nx, ny; + int x, y; + + if (StartPath == DestPath) { + if (pActor->line == pActor->Tline) { + SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); + pActor->over = false; + } else if (pActor->line < pActor->Tline) { + getNpathNode(StartPath, pActor->line+1, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->npstatus = GOING_UP; + } else if (pActor->line > pActor->Tline) { + getNpathNode(StartPath, pActor->line, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->npstatus = GOING_DOWN; + } + } else { + /* + * Leaving this path - work out + * which end of this path to head for. + */ + assert(DestPath != NOPOLY); // Error 702 + if ((hIpath = getPathOnTheWay(StartPath, DestPath)) == NOPOLY) { + // This should never happen! + // It's the old code that didn't always work. + endnode = NearestEndNode(StartPath, targetX, targetY); + } else { + if (PolySubtype(hIpath) != NODE) { + x = PolyCentreX(hIpath); + y = PolyCentreY(hIpath); + endnode = NearestEndNode(StartPath, x, y); + } else { + endnode = NearEndNode(StartPath, hIpath); + } + } + +#if 1 + if ((pActor->npstatus == LEAVING) && + endnode == NearestEndNode(StartPath, pActor->objx, pActor->objy)) { + // Leave it be + } else +#endif + { + if (endnode) { + getNpathNode(StartPath, pActor->line+1, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->npstatus = GOING_UP; + } else { + getNpathNode(StartPath, pActor->line, &nx, &ny); + SetMoverDest(pActor, nx, ny); + pActor->npstatus = GOING_DOWN; + } + } + } +} + +/** + * Restore a movement, called from restoreMovement() in ACTORS.CPP + */ +void SSetActorDest(PMACTOR pActor) { + if (pActor->UtargetX != -1 && pActor->UtargetY != -1) { + stand(pActor->actorID, pActor->objx, pActor->objy, 0); + + if (pActor->UtargetX != -1 && pActor->UtargetY != -1) { + SetActorDest(pActor, pActor->UtargetX, pActor->UtargetY, + pActor->bIgPath, 0); + } + } else { + stand(pActor->actorID, pActor->objx, pActor->objy, 0); + } +} + +/** + * Initiate a movement, called from WalkTo_Event() + */ +void SetActorDest(PMACTOR pActor, int clickX, int clickY, bool igPath, SCNHANDLE film) { + HPOLYGON StartPath, DestPath = 0; + int targetX, targetY; + + if (pActor->actorID == LeadId()) // Now only for lead actor + UnTagActor(pActor->actorID); // Tag not allowed while moving + pActor->ticket++; + pActor->stop = false; + pActor->over = false; + pActor->fromx = pActor->objx; + pActor->fromy = pActor->objy; + pActor->bMoving = true; + pActor->bIgPath = igPath; + + // Use the supplied reel or restore the normal actor. + if (film != 0) + AlterMActor(pActor, film, AR_WALKREEL); + else + AlterMActor(pActor, 0, AR_NORMAL); + + if (igPath) { + targetX = clickX; + targetY = clickY; + } else { + if (WorkOutDestination(clickX, clickY, &targetX, &targetY) == ALL_SORTED) { + GotThere(pActor); + return; + } + assert(InPolygon(targetX, targetY, PATH) != NOPOLY); // illegal destination! + assert(InPolygon(targetX, targetY, BLOCKING) == NOPOLY); // illegal destination! + } + + + /***** Now have a destination to aim for. *****/ + + /*---------------------------------- + | Don't move if it's not worth it. + ----------------------------------*/ + if (ABS(targetX - pActor->objx) < XMDIST && ABS(targetY - pActor->objy) < YMDIST) { + GotThere(pActor); + return; + } + + /*------------------------------------------------------ + | If the destiation is within a follow nodes polygon, + | set destination as the nearest node. + ------------------------------------------------------*/ + if (!igPath) { + DestPath = InPolygon(targetX, targetY, PATH); + if (PolySubtype(DestPath) == NODE) { + // Find the nearest point on a line, or nearest node + FindBestPoint(DestPath, &targetX, &targetY, &pActor->Tline); + } + } + + assert(pActor->bIgPath || InPolygon(targetX, targetY, PATH) != NOPOLY); // Error 5005 + SetMoverUltDest(pActor, targetX, targetY); + SetMoverIntDest(pActor, targetX, targetY); + + /*------------------------------------------------------------------- + | If in a follow nodes path, need to set off in the right direction! | + -------------------------------------------------------------------*/ + if ((StartPath = pActor->hFnpath) != NOPOLY && !igPath) { + SetOffWithinNodePath(pActor, StartPath, DestPath, targetX, targetY); + } else { + // Set off! + SetNextDest(pActor); + } +} + +/** + * Change scale if appropriate. + */ +static void CheckScale(PMACTOR pActor, HPOLYGON hPath, int ypos) { + int scale; + + scale = GetScale(hPath, ypos); + if (scale != pActor->scale) { + SetMActorWalkReel(pActor, pActor->dirn, scale, false); + } +} + +/** + * Not going anywhere - Kick off again if not at final destination. + */ +static void NotMoving(PMACTOR pActor, int x, int y) { + pActor->targetX = pActor->targetY = -1; + +// if (x == pActor->UtargetX && y == pActor->UtargetY) + if (ABS(x - pActor->UtargetX) < XMDIST && ABS(y - pActor->UtargetY) < YMDIST) { + GotThere(pActor); + return; + } + + if (pActor->ItargetX != -1 || pActor->ItargetY != -1) { + SetNextDest(pActor); + } else if (pActor->UtargetX != -1 || pActor->UtargetY != -1) { + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5006 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + SetNextDest(pActor); + } +} + +/** + * Does the necessary business when entering a different path polygon. + */ +static void EnteringNewPath(PMACTOR pActor, HPOLYGON hPath, int x, int y) { + int firstnode; // First node to go to + int lastnode; // Last node to go to + HPOLYGON hIpath; + int nx, ny; + int nxl, nyl; + + pActor->hCpath = hPath; // current path + + if (hPath == NOPOLY) { + // Not proved this ever happens, but just in case + pActor->hFnpath = NOPOLY; + pActor->npstatus = NOT_IN; + return; + } + + // Is new path a node path? + if (PolySubtype(hPath) == NODE) { + // Node path - usually go to nearest end node + firstnode = NearestEndNode(hPath, x, y); + lastnode = -1; + + // If this is not the destination path, + // find which end nodfe we wish to leave via + if (hPath != pActor->hUpath) { + if (pActor->bIgPath) { + lastnode = NearestEndNode(hPath, pActor->UtargetX, pActor->UtargetY); + } else { + assert(pActor->hUpath != NOPOLY); // Error 703 + hIpath = getPathOnTheWay(hPath, pActor->hUpath); + assert(hIpath != NOPOLY); // No path on the way + + if (PolySubtype(hIpath) != NODE) { + lastnode = NearestEndNode(hPath, PolyCentreX(hIpath), PolyCentreY(hIpath)); + } else { + lastnode = NearEndNode(hPath, hIpath); + } + } + } + // Test for pseudo-one-node npaths + if (lastnode != -1 && numNodes(hPath) == 2) { + getNpathNode(hPath, firstnode, &nx, &ny); + getNpathNode(hPath, lastnode, &nxl, &nyl); + if (nxl == nx && nyl == ny) + firstnode = lastnode; + } + + // If leaving by same node as entering, don't bother. + if (lastnode == firstnode) { + pActor->hFnpath = NOPOLY; + pActor->npstatus = NOT_IN; + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5007 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + SetNextDest(pActor); + } else { + // Head for first node + pActor->over = true; + pActor->npstatus = ENTERING; + pActor->hFnpath = hPath; + pActor->line = firstnode ? firstnode - 1 : firstnode; + if (pActor->line == pActor->Tline && hPath == pActor->hUpath) { + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5008 + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY); + } else { + // This doesn't seem right + getNpathNode(hPath, firstnode, &nx, &ny); + if (ABS(pActor->objx - nx) < XMDIST + && ABS(pActor->objy - ny) < YMDIST) { + pActor->npstatus = ENTERING; + pActor->hFnpath = hPath; + SetNextDest(pActor); + } else { + getNpathNode(hPath, firstnode, &nx, &ny); + SetMoverDest(pActor, nx, ny); + } + } + } + return; + } else { + pActor->hFnpath = NOPOLY; + pActor->npstatus = NOT_IN; + assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5009 +// Added 26/01/95 + if (IsPolyCorner(hPath, pActor->ItargetX, pActor->ItargetY)) + return; + + SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY); + SetNextDest(pActor); + } +} + +/** + * Move + */ +void Move(PMACTOR pActor, int newx, int newy, HPOLYGON hPath) { + MultiSetAniXY(pActor->actorObj, newx, newy); + MAsetZPos(pActor, newy, getPolyZfactor(hPath)); + if (StepAnimScript(&pActor->actorAnim) == ScriptFinished) { + // The end of a scale-change reel + // Revert to normal walking reel + pActor->walkReel = false; + pActor->scount = 0; + SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true); + } + pActor->objx = newx; + pActor->objy = newy; + + // Synchronised walking reels + if (++pActor->scount >= 6) + pActor->scount = 0; +} + +/** + * Called from MActorProcess() on every tick. + * + * Moves the actor as appropriate. + */ +void MoveActor(PMACTOR pActor) { + int newx, newy; + HPOLYGON hPath; + int status, s2; // s2 not used here! + HPOLYGON hS2p; // nor is s2p! + HPOLYGON hEb; + PMACTOR ma; + int sTargetX, sTargetY; + bool bNewPath = false; + + // Only do anything if the actor needs to move! + if (pActor->targetX == -1 && pActor->targetY == -1) + return; + + if (pActor->stop) { + GotThere(pActor); + pActor->stop = false; + SetMActorStanding(pActor); + return; + } + +#if SLOW_RINCE_DOWN +/**/ if (BogusVar++ < Interlude) // Temporary slow-down-the-action code +/**/ return; // +/**/ BogusVar = 0; // +#endif + + // During swalk()s, movement while hidden may be slowed down. + if (pActor->aHidden) { + if (++hSlowVar < pActor->SlowFactor) + return; + hSlowVar = 0; + } + + // 'push' the target + sTargetX = pActor->targetX; + sTargetY = pActor->targetY; + + NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY, + &newx, &newy, &status, &s2, &hS2p, pActor->over, false, pActor); + + if (newx == pActor->objx && newy == pActor->objy) { + // 'pop' the target + pActor->targetX = sTargetX; + pActor->targetY = sTargetY; + + NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY, &newx, &newy, + &status, &s2, &hS2p, pActor->over, true, pActor); + if (newx == pActor->objx && newy == pActor->objy) { + NotMoving(pActor, newx, newy); + return; + } + } + + // Find out which path we're in now + hPath = InPolygon(newx, newy, PATH); + if (hPath == NOPOLY) { + if (pActor->bNoPath) { + Move(pActor, newx, newy, pActor->hCpath); + return; + } else { + // May be marginally outside! + // OR bIgPath may be set. + hPath = pActor->hCpath; + } + } else if (pActor->bNoPath) { + pActor->bNoPath = false; + bNewPath = true; + } else if (hPath != pActor->hCpath) { + if (IsInPolygon(newx, newy, pActor->hCpath)) + hPath = pActor->hCpath; + } + + CheckScale(pActor, hPath, newy); + + /* + * Must stay out of moving actors' blocking polygons. + */ + ma = InMActorBlock(pActor, newx, newy); + if (ma != NULL) { + // Stop if there's no chance of arriving + if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) { + GotThere(pActor); + return; + } + + if (InMActorBlock(pActor, pActor->objx, pActor->objy)) + ; + else { + hEb = InitExtraBlock(pActor, ma); + newx = pActor->objx; + newy = pActor->objy; + BlockingCorner(hEb, &newx, &newy, pActor->ItargetX, pActor->ItargetY); + SetMoverDest(pActor, newx, newy); + return; + } + } + + /*-------------------------------------- + This is where it actually gets moved. + --------------------------------------*/ + Move(pActor, newx, newy, hPath); + + // Entering a new path polygon? + if (hPath != pActor->hCpath || bNewPath) + EnteringNewPath(pActor, hPath, newx, newy); +} + +/** + * Store the default refer type for the current scene. + */ +void SetDefaultRefer(int32 defRefer) { + DefaultRefer = defRefer; +} + +/** + * DoMoveActor + */ +void DoMoveActor(PMACTOR pActor) { + int wasx, wasy; + int i; + +#define NUMBER 1 + + wasx = pActor->objx; + wasy = pActor->objy; + + MoveActor(pActor); + + if ((pActor->targetX != -1 || pActor->targetY != -1) + && (wasx == pActor->objx && wasy == pActor->objy)) { + for (i=0; i < NUMBER; i++) { + MoveActor(pActor); + if (wasx != pActor->objx || wasy != pActor->objy) + break; + } +// assert(i<NUMBER); + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/move.h b/engines/tinsel/move.h new file mode 100644 index 0000000000..2c5f2cfe73 --- /dev/null +++ b/engines/tinsel/move.h @@ -0,0 +1,43 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_MOVE_H // prevent multiple includes +#define TINSEL_MOVE_H + +#include "tinsel/dw.h" // for SCNHANDLE + +namespace Tinsel { + +struct MACTOR; + +void SetActorDest(MACTOR *pActor, int x, int y, bool igPath, SCNHANDLE film); +void SSetActorDest(MACTOR *pActor); +void DoMoveActor(MACTOR *pActor); + +void SetDefaultRefer(int32 defRefer); + +} // end of namespace Tinsel + +#endif /* TINSEL_MOVE_H */ diff --git a/engines/tinsel/multiobj.cpp b/engines/tinsel/multiobj.cpp new file mode 100644 index 0000000000..c60592069f --- /dev/null +++ b/engines/tinsel/multiobj.cpp @@ -0,0 +1,533 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * This file contains utilities to handle multi-part objects. + */ + +#include "tinsel/multiobj.h" +#include "tinsel/handle.h" +#include "tinsel/object.h" + +namespace Tinsel { + +// from object.c +extern OBJECT *objectList; + +/** + * Initialise a multi-part object using a list of images to init + * each object piece. One object is created for each image in the list. + * All objects are given the same palette as the first image. A pointer + * to the first (master) object created is returned. + * @param pInitTbl Pointer to multi-object initialisation table + */ +OBJECT *MultiInitObject(const MULTI_INIT *pInitTbl) { + OBJ_INIT obj_init; // object init table + OBJECT *pFirst, *pObj; // object pointers + FRAME *pFrame; // list of images for the multi-part object + + if (pInitTbl->hMulFrame) { + // we have a frame handle + pFrame = (FRAME *)LockMem(FROM_LE_32(pInitTbl->hMulFrame)); + + obj_init.hObjImg = READ_LE_UINT32(pFrame); // first objects shape + } else { // this must be a animation list for a NULL object + pFrame = NULL; + obj_init.hObjImg = 0; // first objects shape + } + + // init the object init table + obj_init.objFlags = (int)FROM_LE_32(pInitTbl->mulFlags); // all objects have same flags + obj_init.objID = (int)FROM_LE_32(pInitTbl->mulID); // all objects have same ID + obj_init.objX = (int)FROM_LE_32(pInitTbl->mulX); // all objects have same X ani pos + obj_init.objY = (int)FROM_LE_32(pInitTbl->mulY); // all objects have same Y ani pos + obj_init.objZ = (int)FROM_LE_32(pInitTbl->mulZ); // all objects have same Z pos + + // create and init the first object + pObj = pFirst = InitObject(&obj_init); + + if (pFrame) { + // if we have any animation frames + + pFrame++; + + while (READ_LE_UINT32(pFrame) != 0) { + // set next objects shape + obj_init.hObjImg = READ_LE_UINT32(pFrame); + + // create next object and link to previous + pObj = pObj->pSlave = InitObject(&obj_init); + + pFrame++; + } + } + + // null end of list for final object + pObj->pSlave = NULL; + + // return master object + return pFirst; +} + +/** + * Inserts the multi-part object onto the specified object list. + * @param pObjList List to insert multi-part object onto +* @param pInsObj Head of multi-part object to insert + + */ + +void MultiInsertObject(OBJECT *pObjList, OBJECT *pInsObj) { + // validate object pointer + assert(pInsObj >= objectList && pInsObj <= objectList + NUM_OBJECTS - 1); + + // for all the objects that make up this multi-part + do { + // add next part to the specified list + InsertObject(pObjList, pInsObj); + + // next obj in list + pInsObj = pInsObj->pSlave; + } while (pInsObj != NULL); +} + +/** + * Deletes all the pieces of a multi-part object from the + * specified object list. + * @param pObjList List to delete multi-part object from + * @param pMultiObj Multi-part object to be deleted + */ + +void MultiDeleteObject(OBJECT *pObjList, OBJECT *pMultiObj) { + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // for all the objects that make up this multi-part + do { + // delete object + DelObject(pObjList, pMultiObj); + + // next obj in list + pMultiObj = pMultiObj->pSlave; + } + while (pMultiObj != NULL); +} + +/** + * Hides a multi-part object by giving each object a "NullImage" + * image pointer. + * @param pMultiObj Multi-part object to be hidden + */ + +void MultiHideObject(OBJECT *pMultiObj) { + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // set master shape to null animation frame + pMultiObj->hShape = 0; + + // change all objects + MultiReshape(pMultiObj); +} + +/** + * Horizontally flip a multi-part object. + * @param pFlipObj Head of multi-part object to flip + */ + +void MultiHorizontalFlip(OBJECT *pFlipObj) { + // validate object pointer + assert(pFlipObj >= objectList && pFlipObj <= objectList + NUM_OBJECTS - 1); + + // for all the objects that make up this multi-part + do { + // horizontally flip the next part + AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPH, + pFlipObj->hImg); + + // next obj in list + pFlipObj = pFlipObj->pSlave; + } while (pFlipObj != NULL); +} + +/** + * Vertically flip a multi-part object. + * @param pFlipObj Head of multi-part object to flip + */ + +void MultiVerticalFlip(OBJECT *pFlipObj) { + // validate object pointer + assert(pFlipObj >= objectList && pFlipObj <= objectList + NUM_OBJECTS - 1); + + // for all the objects that make up this multi-part + do { + // vertically flip the next part + AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPV, + pFlipObj->hImg); + + // next obj in list + pFlipObj = pFlipObj->pSlave; + } + while (pFlipObj != NULL); +} + +/** + * Adjusts the coordinates of a multi-part object. The adjustments + * take into account the orientation of the object. + * @param pMultiObj Multi-part object to be adjusted + * @param deltaX X adjustment + * @param deltaY Y adjustment + */ + +void MultiAdjustXY(OBJECT *pMultiObj, int deltaX, int deltaY) { + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + if (deltaX == 0 && deltaY == 0) + return; // ignore no change + + if (pMultiObj->flags & DMA_FLIPH) { + // image is flipped horizontally - flip the x direction + deltaX = -deltaX; + } + + if (pMultiObj->flags & DMA_FLIPV) { + // image is flipped vertically - flip the y direction + deltaY = -deltaY; + } + + // for all the objects that make up this multi-part + do { + // signal a change in the object + pMultiObj->flags |= DMA_CHANGED; + + // adjust the x position + pMultiObj->xPos += intToFrac(deltaX); + + // adjust the y position + pMultiObj->yPos += intToFrac(deltaY); + + // next obj in list + pMultiObj = pMultiObj->pSlave; + + } while (pMultiObj != NULL); +} + +/** + * Moves all the pieces of a multi-part object by the specified + * amount. Does not take into account the objects orientation. + * @param pMultiObj Multi-part object to be adjusted + * @param deltaX X movement + * @param deltaY Y movement + */ + +void MultiMoveRelXY(OBJECT *pMultiObj, int deltaX, int deltaY) { + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + if (deltaX == 0 && deltaY == 0) + return; // ignore no change + + // for all the objects that make up this multi-part + do { + // signal a change in the object + pMultiObj->flags |= DMA_CHANGED; + + // adjust the x position + pMultiObj->xPos += intToFrac(deltaX); + + // adjust the y position + pMultiObj->yPos += intToFrac(deltaY); + + // next obj in list + pMultiObj = pMultiObj->pSlave; + + } while (pMultiObj != NULL); +} + +/** + * Sets the x & y anim position of all pieces of a multi-part object. + * @param pMultiObj Multi-part object whose position is to be changed + * @param newAniX New x animation position + * @param newAniY New y animation position + */ + +void MultiSetAniXY(OBJECT *pMultiObj, int newAniX, int newAniY) { + int curAniX, curAniY; // objects current animation position + + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // get master objects current animation position + GetAniPosition(pMultiObj, &curAniX, &curAniY); + + // calc difference between current and new positions + newAniX -= curAniX; + newAniY -= curAniY; + + // move all pieces by the difference + MultiMoveRelXY(pMultiObj, newAniX, newAniY); +} + +/** + * Sets the x anim position of all pieces of a multi-part object. + * @param pMultiObj Multi-part object whose x position is to be changed + * @param newAniX New x animation position + */ + +void MultiSetAniX(OBJECT *pMultiObj, int newAniX) { + int curAniX, curAniY; // objects current animation position + + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // get master objects current animation position + GetAniPosition(pMultiObj, &curAniX, &curAniY); + + // calc x difference between current and new positions + newAniX -= curAniX; + curAniY = 0; + + // move all pieces by the difference + MultiMoveRelXY(pMultiObj, newAniX, curAniY); +} + +/** + * Sets the y anim position of all pieces of a multi-part object. + * @param pMultiObj Multi-part object whose x position is to be changed + * @param newAniX New y animation position + */ + +void MultiSetAniY(OBJECT *pMultiObj, int newAniY) { + int curAniX, curAniY; // objects current animation position + + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // get master objects current animation position + GetAniPosition(pMultiObj, &curAniX, &curAniY); + + // calc y difference between current and new positions + curAniX = 0; + newAniY -= curAniY; + + // move all pieces by the difference + MultiMoveRelXY(pMultiObj, curAniX, newAniY); +} + +/** + * Sets the Z position of all pieces of a multi-part object. + * @param pMultiObj Multi-part object to be adjusted + * @param newZ New Z order + */ + +void MultiSetZPosition(OBJECT *pMultiObj, int newZ) { + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // for all the objects that make up this multi-part + do { + // signal a change in the object + pMultiObj->flags |= DMA_CHANGED; + + // set the new z position + pMultiObj->zPos = newZ; + + // next obj in list + pMultiObj = pMultiObj->pSlave; + } + while (pMultiObj != NULL); +} + +/** + * Reshape a multi-part object. + * @param pMultiObj Multi-part object to re-shape + */ + +void MultiReshape(OBJECT *pMultiObj) { + SCNHANDLE hFrame; + + // validate object pointer + assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1); + + // get objects current anim frame + hFrame = pMultiObj->hShape; + + if (hFrame != 0 && hFrame != pMultiObj->hMirror) { + // a valid shape frame which is different from previous + + // get pointer to frame + const FRAME *pFrame = (const FRAME *)LockMem(hFrame); + + // update previous + pMultiObj->hMirror = hFrame; + + while (READ_LE_UINT32(pFrame) != 0 && pMultiObj != NULL) { + // a normal image - update the current object with this image + AnimateObject(pMultiObj, READ_LE_UINT32(pFrame)); + + // move to next image for this frame + pFrame++; + + // move to next part of object + pMultiObj = pMultiObj->pSlave; + } + + // null the remaining object parts + while (pMultiObj != NULL) { + // set a null image for this object part + AnimateObject(pMultiObj, 0); + + // move to next part of object + pMultiObj = pMultiObj->pSlave; + } + } else if (hFrame == 0) { + // update previous + pMultiObj->hMirror = hFrame; + + // null all the object parts + while (pMultiObj != NULL) { + // set a null image for this object part + AnimateObject(pMultiObj, 0); + + // move to next part of object + pMultiObj = pMultiObj->pSlave; + } + } +} + +/** + * Returns the left-most point of a multi-part object. + * @param pMulti Multi-part object + */ + +int MultiLeftmost(OBJECT *pMulti) { + int left; + + // validate object pointer + assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1); + + // init leftmost point to first object + left = fracToInt(pMulti->xPos); + + // for all the objects in this multi + while ((pMulti = pMulti->pSlave) != NULL) { + if (pMulti->hImg != 0) { + // non null object part + + if (fracToInt(pMulti->xPos) < left) + // this object is further left + left = fracToInt(pMulti->xPos); + } + } + + // return left-most point + return left; +} + +/** + * Returns the right-most point of a multi-part object. + * @param pMulti Multi-part object + */ + +int MultiRightmost(OBJECT *pMulti) { + int right; + + // validate object pointer + assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1); + + // init right-most point to first object + right = fracToInt(pMulti->xPos) + pMulti->width; + + // for all the objects in this multi + while ((pMulti = pMulti->pSlave) != NULL) { + if (pMulti->hImg != 0) { + // non null object part + + if (fracToInt(pMulti->xPos) + pMulti->width > right) + // this object is further right + right = fracToInt(pMulti->xPos) + pMulti->width; + } + } + + // return right-most point + return right - 1; +} + +/** + * Returns the highest point of a multi-part object. + * @param pMulti Multi-part object + */ + +int MultiHighest(OBJECT *pMulti) { + int highest; + + // validate object pointer + assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1); + + // init highest point to first object + highest = fracToInt(pMulti->yPos); + + // for all the objects in this multi + while ((pMulti = pMulti->pSlave) != NULL) { + if (pMulti->hImg != 0) { + // non null object part + + if (fracToInt(pMulti->yPos) < highest) + // this object is higher + highest = fracToInt(pMulti->yPos); + } + } + + // return highest point + return highest; +} + +/** + * Returns the lowest point of a multi-part object. + * @param pMulti Multi-part object + */ + +int MultiLowest(OBJECT *pMulti) { + int lowest; + + // validate object pointer + assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1); + + // init lowest point to first object + lowest = fracToInt(pMulti->yPos) + pMulti->height; + + // for all the objects in this multi + while ((pMulti = pMulti->pSlave) != NULL) { + if (pMulti->hImg != 0) { + // non null object part + + if (fracToInt(pMulti->yPos) + pMulti->height > lowest) + // this object is lower + lowest = fracToInt(pMulti->yPos) + pMulti->height; + } + } + + // return lowest point + return lowest - 1; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/multiobj.h b/engines/tinsel/multiobj.h new file mode 100644 index 0000000000..6d25600ea2 --- /dev/null +++ b/engines/tinsel/multiobj.h @@ -0,0 +1,124 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Multi-part object definitions + */ + +#ifndef TINSEL_MULTIOBJ_H // prevent multiple includes +#define TINSEL_MULTIOBJ_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +struct OBJECT; + +#include "common/pack-start.h" // START STRUCT PACKING + +/** + * multi-object initialisation structure (parallels OBJ_INIT struct) + */ +struct MULTI_INIT { + SCNHANDLE hMulFrame; //!< multi-objects shape - NULL terminated list of IMAGE structures + int32 mulFlags; //!< multi-objects flags + int32 mulID; //!< multi-objects id + int32 mulX; //!< multi-objects initial x ani position + int32 mulY; //!< multi-objects initial y ani position + int32 mulZ; //!< multi-objects initial z position +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + +/*----------------------------------------------------------------------*\ +|* Multi Object Function Prototypes *| +\*----------------------------------------------------------------------*/ + +OBJECT *MultiInitObject( // Initialise a multi-part object + const MULTI_INIT *pInitTbl); // pointer to multi-object initialisation table + +void MultiInsertObject( // Insert a multi-part object onto a object list + OBJECT *pObjList, // list to insert multi-part object onto + OBJECT *pInsObj); // head of multi-part object to insert + +void MultiDeleteObject( // Delete all the pieces of a multi-part object + OBJECT *pObjList, // list to delete multi-part object from + OBJECT *pMultiObj); // multi-part object to be deleted + +void MultiHideObject( // Hide a multi-part object + OBJECT *pMultiObj); // multi-part object to be hidden + +void MultiHorizontalFlip( // Hortizontally flip a multi-part object + OBJECT *pFlipObj); // head of multi-part object to flip + +void MultiVerticalFlip( // Vertically flip a multi-part object + OBJECT *pFlipObj); // head of multi-part object to flip + +void MultiAdjustXY( // Adjust coords of a multi-part object. Takes into account the orientation + OBJECT *pMultiObj, // multi-part object to be adjusted + int deltaX, // x adjustment + int deltaY); // y adjustment + +void MultiMoveRelXY( // Move multi-part object relative. Does not take into account the orientation + OBJECT *pMultiObj, // multi-part object to be moved + int deltaX, // x movement + int deltaY); // y movement + +void MultiSetAniXY( // Set the x & y anim position of a multi-part object + OBJECT *pMultiObj, // multi-part object whose position is to be changed + int newAniX, // new x animation position + int newAniY); // new y animation position + +void MultiSetAniX( // Set the x anim position of a multi-part object + OBJECT *pMultiObj, // multi-part object whose x position is to be changed + int newAniX); // new x animation position + +void MultiSetAniY( // Set the y anim position of a multi-part object + OBJECT *pMultiObj, // multi-part object whose y position is to be adjusted + int newAniY); // new y animation position + +void MultiSetZPosition( // Sets the z position of a multi-part object + OBJECT *pMultiObj, // multi-part object to be adjusted + int newZ); // new Z order + +void MultiMatchAniPoints( // Matches a multi-parts pos and orientation to be the same as a reference object + OBJECT *pMoveObj, // multi-part object to be moved + OBJECT *pRefObj); // multi-part object to match with + +void MultiReshape( // Reshape a multi-part object + OBJECT *pMultiObj); // multi-part object to re-shape + +int MultiLeftmost( // Returns the left-most point of a multi-part object + OBJECT *pMulti); // multi-part object + +int MultiRightmost( // Returns the right-most point of a multi-part object + OBJECT *pMulti); // multi-part object + +int MultiHighest( // Returns the highest point of a multi-part object + OBJECT *pMulti); // multi-part object + +int MultiLowest( // Returns the lowest point of a multi-part object + OBJECT *pMulti); // multi-part object + +} // end of namespace Tinsel + +#endif // TINSEL_MULTIOBJ_H diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp new file mode 100644 index 0000000000..8af905b0c8 --- /dev/null +++ b/engines/tinsel/music.cpp @@ -0,0 +1,554 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +// FIXME: This code is taken from MADE and may need more work (e.g. setVolume). + +// MIDI and digital music class + +#include "sound/audiostream.h" +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "sound/audiocd.h" +#include "common/config-manager.h" +#include "common/file.h" + +#include "tinsel/config.h" +#include "tinsel/sound.h" +#include "tinsel/music.h" + +namespace Tinsel { + +//--------------------------- Midi data ------------------------------------- + +// sound buffer structure used for MIDI data and samples +struct SOUND_BUFFER { + uint8 *pDat; // pointer to actual buffer + uint32 size; // size of the buffer +}; + +// get set when music driver is installed +//static MDI_DRIVER *mDriver; +//static HSEQUENCE mSeqHandle; + +// if non-zero this is the index position of the next MIDI sequence to play +static uint32 dwMidiIndex = 0; + +// MIDI buffer +static SOUND_BUFFER midiBuffer = { 0, 0 }; + +static SCNHANDLE currentMidi; +static bool currentLoop; + +const SCNHANDLE midiOffsetsGRAVersion[] = { + 4, 4534, 14298, 18828, 23358, 38888, 54418, 57172, 59926, 62450, + 62952, 67482, 72258, 74538, 79314, 87722, 103252, 115176, 127100, 127898, + 130256, 132614, 134972, 137330, 139688, 150196, 152554, 154912, 167422, 174762, + 182102, 194612, 198880, 199536, 206128, 206380, 216372, 226364, 235676, 244988, + 249098, 249606, 251160, 252714, 263116, 268706, 274296, 283562, 297986, 304566, + 312028, 313524, 319192, 324860, 331772, 336548, 336838, 339950, 343062, 346174, + 349286, 356246, 359358, 360434, 361510, 369966, 374366, 382822, 384202, 394946, + 396022, 396730, 399524, 401020, 403814, 418364, 419466, 420568, 425132, 433540, + 434384, 441504, 452132, 462760, 472804, 486772, 491302, 497722, 501260, 507680, + 509726, 521858, 524136, 525452, 533480, 538236, 549018, 559870, 564626, 565306, + 566734, 567616, 570144, 574102, 574900, 582518, 586350, 600736, 604734, 613812, + 616566, 619626, 623460, 627294, 631128, 634188, 648738, 663288, 667864, 681832, + 682048, 683014, 688908, 689124, 698888, 708652, 718416, 728180, 737944, 747708, + 752238, 765522, 766554, 772944, 774546, 776148, 776994, 781698, 786262, 789016, + 794630, 796422, 798998 +}; + +const SCNHANDLE midiOffsetsSCNVersion[] = { + 4, 4504, 11762, 21532, 26070, 28754, 33254, 40512, 56310, 72108, + 74864, 77620, 80152, 80662, 85200, 89982, 92268, 97050, 105466, 121264, + 133194, 145124, 145928, 148294, 150660, 153026, 155392, 157758, 168272, 170638, + 173004, 185522, 192866, 200210, 212728, 217000, 217662, 224254, 224756, 234754, + 244752, 245256, 245950, 255256, 264562, 268678, 269192, 270752, 272312, 282712, + 288312, 293912, 303186, 317624, 324210, 331680, 333208, 338884, 344560, 351478, + 356262, 356552, 359670, 362788, 365906, 369024, 376014, 379132, 380214, 381296, + 389758, 394164, 402626, 404012, 414762, 415844, 416552, 419352, 420880, 423680, + 438236, 439338, 440440, 445010, 453426, 454276, 461398, 472032, 482666, 492716, + 506690, 511226, 517654, 521198, 527626, 529676, 541814, 546210, 547532, 555562, + 560316, 571104, 581962, 586716, 587402, 588836, 589718, 592246, 596212, 597016, + 604636, 608474, 622862, 626860, 635944, 638700, 641456, 645298, 649140, 652982, + 655738, 670294, 684850, 689432, 703628, 703850, 704816, 706350, 706572, 716342, + 726112, 735882, 745652, 755422, 765192, 774962, 784732, 794502, 804272, 814042, + 823812, 832996, 846286, 847324, 853714, 855324, 856934, 857786, 862496, 867066, + 869822, 875436, 877234, 879818 +}; + +// TODO: finish this (currently unmapped tracks are 0) +const int enhancedAudioSCNVersion[] = { + 0, 0, 2, 0, 0, 0, 0, 3, 3, 4, + 4, 0, 0, 0, 0, 0, 0, 10, 3, 11, + 11, 0, 13, 13, 13, 13, 13, 0, 13, 13, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 24, 0, 0, 27, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 55, 56, 56, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 4, 83, 83, 83, 4, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 52, 4, + 0, 0, 0, 0 +}; + +int GetTrackNumber(SCNHANDLE hMidi) { + if (_vm->getFeatures() & GF_SCNFILES) { + for (int i = 0; i < ARRAYSIZE(midiOffsetsSCNVersion); i++) { + if (midiOffsetsSCNVersion[i] == hMidi) + return i; + } + } else { + for (int i = 0; i < ARRAYSIZE(midiOffsetsGRAVersion); i++) { + if (midiOffsetsGRAVersion[i] == hMidi) + return i; + } + } + + return -1; +} + +SCNHANDLE GetTrackOffset(int trackNumber) { + if (_vm->getFeatures() & GF_SCNFILES) { + assert(trackNumber < ARRAYSIZE(midiOffsetsSCNVersion)); + return midiOffsetsSCNVersion[trackNumber]; + } else { + assert(trackNumber < ARRAYSIZE(midiOffsetsGRAVersion)); + return midiOffsetsGRAVersion[trackNumber]; + } +} + +/** + * Plays the specified MIDI sequence through the sound driver. + * @param dwFileOffset File offset of MIDI sequence data + * @param bLoop Whether to loop the sequence + */ + +bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) { + currentMidi = dwFileOffset; + currentLoop = bLoop; + + if (volMidi != 0) { + SetMidiVolume(volMidi); + // Support for compressed music from the music enhancement project + AudioCD.stop(); + + int trackNumber = GetTrackNumber(dwFileOffset); + if (trackNumber >= 0) { +#if 0 + // TODO: GRA version + int track = enhancedAudioSCNVersion[trackNumber]; + if (track > 0) + AudioCD.play(track, -1, 0, 0); +#endif + } else { + warning("Unknown MIDI offset %d", dwFileOffset); + } + + if (AudioCD.isPlaying()) + return true; + } + + // set file offset for this sequence + dwMidiIndex = dwFileOffset; + + // the index and length of the last tune loaded + static uint32 dwLastMidiIndex; + static uint32 dwLastSeqLen; + + uint32 dwSeqLen = 0; // length of the sequence + + if (dwMidiIndex == 0) + return true; + + if (dwMidiIndex != dwLastMidiIndex) { + Common::File midiStream; + + // open MIDI sequence file in binary mode + if (!midiStream.open(MIDI_FILE)) + error("Cannot find file %s", MIDI_FILE); + + // update index of last tune loaded + dwLastMidiIndex = dwMidiIndex; + + // move to correct position in the file + midiStream.seek(dwMidiIndex, SEEK_SET); + + // read the length of the sequence + dwSeqLen = midiStream.readUint32LE(); + + // make sure buffer is large enough for this sequence + assert(dwSeqLen > 0 && dwSeqLen <= midiBuffer.size); + + // stop any currently playing tune + _vm->_music->stop(); + + // read the sequence + if (midiStream.read(midiBuffer.pDat, dwSeqLen) != dwSeqLen) + error("File %s is corrupt", MIDI_FILE); + + midiStream.close(); + + _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop); + + // Store the length + dwLastSeqLen = dwSeqLen; + } else { + // dwMidiIndex == dwLastMidiIndex + _vm->_music->stop(); + _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop); + } + + // allow another sequence to play + dwMidiIndex = 0; + + return true; +} + +/** + * Returns TRUE if a Midi tune is currently playing. + */ + +bool MidiPlaying(void) { + if (AudioCD.isPlaying()) return true; + return _vm->_music->isPlaying(); +} + +/** + * Stops any currently playing midi. + */ + +bool StopMidi(void) { + currentMidi = 0; + currentLoop = false; + + AudioCD.stop(); + _vm->_music->stop(); + return true; +} + + +/** + * Gets the volume of the MIDI music. + */ +int GetMidiVolume() { + return volMidi; +} + +/** + * Sets the volume of the MIDI music. + * @param vol New volume - 0..MAXMIDIVOL + */ +void SetMidiVolume(int vol) { + assert(vol >= 0 && vol <= MAXMIDIVOL); + + if (vol == 0 && volMidi == 0) { + // Nothing to do + } else if (vol == 0 && volMidi != 0) { + // Stop current midi sequence + AudioCD.stop(); + StopMidi(); + } else if (vol != 0 && volMidi == 0) { + // Perhaps restart last midi sequence + if (currentLoop) { + PlayMidiSequence(currentMidi, true); + _vm->_music->setVolume(vol); + } + } else if (vol != 0 && volMidi != 0) { + // Alter current volume + _vm->_music->setVolume(vol); + } + + volMidi = vol; +} + +/** + * Opens and inits all MIDI sequence files. + */ +void OpenMidiFiles(void) { + Common::File midiStream; + + // Demo version has no midi file + if (_vm->getFeatures() & GF_DEMO) + return; + + if (midiBuffer.pDat) + // already allocated + return; + + // open MIDI sequence file in binary mode + if (!midiStream.open(MIDI_FILE)) + error("Cannot find file %s", MIDI_FILE); + + // gen length of the largest sequence + midiBuffer.size = midiStream.readUint32LE(); + if (midiStream.ioFailed()) + error("File %s is corrupt", MIDI_FILE); + + if (midiBuffer.size) { + // allocate a buffer big enough for the largest MIDI sequence + if ((midiBuffer.pDat = (uint8 *)malloc(midiBuffer.size)) != NULL) { + // clear out the buffer + memset(midiBuffer.pDat, 0, midiBuffer.size); +// VMM_lock(midiBuffer.pDat, midiBuffer.size); + } else { + //mSeqHandle = NULL; + } + } + + midiStream.close(); +} + +void DeleteMidiBuffer() { + free(midiBuffer.pDat); + midiBuffer.pDat = NULL; +} + +MusicPlayer::MusicPlayer(MidiDriver *driver) : _parser(0), _driver(driver), _looping(false), _isPlaying(false) { + memset(_channel, 0, sizeof(_channel)); + _masterVolume = 0; + this->open(); + _xmidiParser = MidiParser::createParser_XMIDI(); +} + +MusicPlayer::~MusicPlayer() { + _driver->setTimerCallback(NULL, NULL); + stop(); + this->close(); + _xmidiParser->setMidiDriver(NULL); + delete _xmidiParser; +} + +void MusicPlayer::setVolume(int volume) { + Common::StackLock lock(_mutex); + + // FIXME: Could we simply change MAXMIDIVOL to match ScummVM's range? + volume = CLIP((255 * volume) / MAXMIDIVOL, 0, 255); + _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume); + + if (_masterVolume == volume) + return; + + _masterVolume = volume; + + for (int i = 0; i < 16; ++i) { + if (_channel[i]) { + _channel[i]->volume(_channelVolume[i] * _masterVolume / 255); + } + } +} + +int MusicPlayer::open() { + // Don't ever call open without first setting the output driver! + if (!_driver) + return 255; + + int ret = _driver->open(); + if (ret) + return ret; + + _driver->setTimerCallback(this, &onTimer); + return 0; +} + +void MusicPlayer::close() { + stop(); + if (_driver) + _driver->close(); + _driver = 0; +} + +void MusicPlayer::send(uint32 b) { + byte channel = (byte)(b & 0x0F); + if ((b & 0xFFF0) == 0x07B0) { + // Adjust volume changes by master volume + byte volume = (byte)((b >> 16) & 0x7F); + _channelVolume[channel] = volume; + volume = volume * _masterVolume / 255; + b = (b & 0xFF00FFFF) | (volume << 16); + } else if ((b & 0xFFF0) == 0x007BB0) { + //Only respond to All Notes Off if this channel + //has currently been allocated + if (_channel[b & 0x0F]) + return; + } + + if (!_channel[channel]) + _channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + + if (_channel[channel]) { + _channel[channel]->send(b); + + if ((b & 0xFFF0) == 0x0079B0) { + // We've just Reset All Controllers, so we need to + // re-adjust the volume. Otherwise, volume is reset to + // default whenever the music changes. + _channel[channel]->send(0x000007B0 | (((_channelVolume[channel] * _masterVolume) / 255) << 16) | channel); + } + } +} + +void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) { + switch (type) { + case 0x2F: // End of Track + if (_looping) + _parser->jumpToTick(0); + else + stop(); + break; + default: + //warning("Unhandled meta event: %02x", type); + break; + } +} + +void MusicPlayer::onTimer(void *refCon) { + MusicPlayer *music = (MusicPlayer *)refCon; + Common::StackLock lock(music->_mutex); + + if (music->_isPlaying) + music->_parser->onTimer(); +} + +void MusicPlayer::playXMIDI(byte *midiData, uint32 size, bool loop) { + if (_isPlaying) + return; + + stop(); + + // It seems like not all music (the main menu music, for instance) set + // all the instruments explicitly. That means the music will sound + // different, depending on which music played before it. This appears + // to be a genuine glitch in the original. For consistency, reset all + // instruments to the default one (piano). + + for (int i = 0; i < 16; i++) { + _driver->send(0xC0 | i, 0, 0); + } + + // Load XMID resource data + + if (_xmidiParser->loadMusic(midiData, size)) { + MidiParser *parser = _xmidiParser; + parser->setTrack(0); + parser->setMidiDriver(this); + parser->setTimerRate(getBaseTempo()); + parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1); + + _parser = parser; + + _looping = loop; + _isPlaying = true; + } +} + +void MusicPlayer::stop() { + Common::StackLock lock(_mutex); + + _isPlaying = false; + if (_parser) { + _parser->unloadMusic(); + _parser = NULL; + } +} + +void MusicPlayer::pause() { + setVolume(-1); + _isPlaying = false; +} + +void MusicPlayer::resume() { + setVolume(GetMidiVolume()); + _isPlaying = true; +} + +void CurrentMidiFacts(SCNHANDLE *pMidi, bool *pLoop) { + *pMidi = currentMidi; + *pLoop = currentLoop; +} + +void RestoreMidiFacts(SCNHANDLE Midi, bool Loop) { + AudioCD.stop(); + StopMidi(); + + currentMidi = Midi; + currentLoop = Loop; + + if (volMidi != 0 && Loop) { + PlayMidiSequence(currentMidi, true); + SetMidiVolume(volMidi); + } +} + +#if 0 +// Dumps all of the game's music in external XMIDI *.xmi files +void dumpMusic() { + Common::File midiFile; + Common::File outFile; + char outName[20]; + midiFile.open(MIDI_FILE); + int outFileSize = 0; + char buffer[20000]; + + int total = (_vm->getFeatures() & GF_SCNFILES) ? + ARRAYSIZE(midiOffsetsSCNVersion) : + ARRAYSIZE(midiOffsetsGRAVersion); + + for (int i = 0; i < total; i++) { + sprintf(outName, "track%03d.xmi", i + 1); + outFile.open(outName, Common::File::kFileWriteMode); + + if (_vm->getFeatures() & GF_SCNFILES) { + if (i < total - 1) + outFileSize = midiOffsetsSCNVersion[i + 1] - midiOffsetsSCNVersion[i] - 4; + else + outFileSize = midiFile.size() - midiOffsetsSCNVersion[i] - 4; + + midiFile.seek(midiOffsetsSCNVersion[i] + 4, SEEK_SET); + } else { + if (i < total - 1) + outFileSize = midiOffsetsGRAVersion[i + 1] - midiOffsetsGRAVersion[i] - 4; + else + outFileSize = midiFile.size() - midiOffsetsGRAVersion[i] - 4; + + midiFile.seek(midiOffsetsGRAVersion[i] + 4, SEEK_SET); + } + + assert(outFileSize < 20000); + midiFile.read(buffer, outFileSize); + outFile.write(buffer, outFileSize); + + outFile.close(); + } + + midiFile.close(); +} +#endif + +} // End of namespace Made diff --git a/engines/tinsel/music.h b/engines/tinsel/music.h new file mode 100644 index 0000000000..80456e2a76 --- /dev/null +++ b/engines/tinsel/music.h @@ -0,0 +1,118 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +// Music class + +#ifndef TINSEL_MUSIC_H +#define TINSEL_MUSIC_H + +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "common/mutex.h" + +namespace Tinsel { + +#define MAXMIDIVOL 127 + +bool PlayMidiSequence( // Plays the specified MIDI sequence through the sound driver + uint32 dwFileOffset, // handle of MIDI sequence data + bool bLoop); // Whether to loop the sequence + +bool MidiPlaying(void); // Returns TRUE if a Midi tune is currently playing + +bool StopMidi(void); // Stops any currently playing midi + +void SetMidiVolume( // Sets the volume of the MIDI music. Returns the old volume + int vol); // new volume - 0..MAXMIDIVOL + +int GetMidiVolume(); + +void OpenMidiFiles(); +void DeleteMidiBuffer(); + +void CurrentMidiFacts(SCNHANDLE *pMidi, bool *pLoop); +void RestoreMidiFacts(SCNHANDLE Midi, bool Loop); + +int GetTrackNumber(SCNHANDLE hMidi); +SCNHANDLE GetTrackOffset(int trackNumber); + +void dumpMusic(); + + +class MusicPlayer : public MidiDriver { +public: + MusicPlayer(MidiDriver *driver); + ~MusicPlayer(); + + bool isPlaying() { return _isPlaying; } + void setPlaying(bool playing) { _isPlaying = playing; } + + void setVolume(int volume); + int getVolume() { return _masterVolume; } + + void playXMIDI(byte *midiData, uint32 size, bool loop); + void stop(); + void pause(); + void resume(); + void setLoop(bool loop) { _looping = loop; } + + //MidiDriver interface implementation + int open(); + void close(); + void send(uint32 b); + + void metaEvent(byte type, byte *data, uint16 length); + + void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { } + + // The original sets the "sequence timing" to 109 Hz, whatever that + // means. The default is 120. + + uint32 getBaseTempo(void) { return _driver ? (109 * _driver->getBaseTempo()) / 120 : 0; } + + //Channel allocation functions + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + + MidiParser *_parser; + Common::Mutex _mutex; + +protected: + + static void onTimer(void *data); + + MidiChannel *_channel[16]; + MidiDriver *_driver; + MidiParser *_xmidiParser; + byte _channelVolume[16]; + + bool _isPlaying; + bool _looping; + byte _masterVolume; +}; + +} // End of namespace Made + +#endif diff --git a/engines/tinsel/object.cpp b/engines/tinsel/object.cpp new file mode 100644 index 0000000000..5ec0ec46e6 --- /dev/null +++ b/engines/tinsel/object.cpp @@ -0,0 +1,527 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * This file contains the Object Manager code. + */ + +#include "tinsel/object.h" +#include "tinsel/background.h" // for rcScreen definition +#include "tinsel/cliprect.h" // object clip rect defs +#include "tinsel/graphics.h" // low level interface +#include "tinsel/handle.h" + +#define OID_EFFECTS 0x2000 // generic special effects object id + +namespace Tinsel { + +// list of all objects +OBJECT *objectList = 0; + +// pointer to free object list +static OBJECT *pFreeObjects = 0; + +#ifdef DEBUG +// diagnostic object counters +static int numObj = 0; +static int maxObj = 0; +#endif + +void FreeObjectList(void) { + if (objectList) { + free(objectList); + objectList = NULL; + } +} + +/** + * Kills all objects and places them on the free list. + */ + +void KillAllObjects(void) { + int i; + +#ifdef DEBUG + // clear number of objects in use + numObj = 0; +#endif + + if (objectList == NULL) { + // first time - allocate memory for object list + objectList = (OBJECT *)calloc(NUM_OBJECTS, sizeof(OBJECT)); + + // make sure memory allocated + if (objectList == NULL) { + error("Cannot allocate memory for object data"); + } + } + + // place first object on free list + pFreeObjects = objectList; + + // link all other objects after first + for (i = 1; i < NUM_OBJECTS; i++) { + objectList[i - 1].pNext = objectList + i; + } + + // null the last object + objectList[NUM_OBJECTS - 1].pNext = NULL; +} + + +#ifdef DEBUG +/** + * Shows the maximum number of objects used at once. + */ + +void ObjectStats(void) { + printf("%i objects of %i used.\n", maxObj, NUM_OBJECTS); +} +#endif + +/** + * Allocate a object from the free list. + */ +OBJECT *AllocObject(void) { + OBJECT *pObj = pFreeObjects; // get a free object + + // check for no free objects + assert(pObj != NULL); + + // a free object exists + + // get link to next free object + pFreeObjects = pObj->pNext; + + // clear out object + memset(pObj, 0, sizeof(OBJECT)); + + // set default drawing mode and set changed bit + pObj->flags = DMA_WNZ | DMA_CHANGED; + +#ifdef DEBUG + // one more object in use + if (++numObj > maxObj) + maxObj = numObj; +#endif + + // return new object + return pObj; +} + +/** + * Copy one object to another. + * @param pDest Destination object + * @param pSrc Source object + */ +void CopyObject(OBJECT *pDest, OBJECT *pSrc) { + // save previous dimensions etc. + Common::Rect rcSave = pDest->rcPrev; + + // make a copy + memcpy(pDest, pSrc, sizeof(OBJECT)); + + // restore previous dimensions etc. + pDest->rcPrev = rcSave; + + // set changed flag in destination + pDest->flags |= DMA_CHANGED; + + // null the links + pDest->pNext = pDest->pSlave = NULL; +} + +/** + * Inserts an object onto the specified object list. The object + * lists are sorted in Z Y order. + * @param pObjList List to insert object onto + * @param pInsObj Object to insert + */ + +void InsertObject(OBJECT *pObjList, OBJECT *pInsObj) { + OBJECT *pPrev, *pObj; // object list traversal pointers + + // validate object pointer + assert(pInsObj >= objectList && pInsObj <= objectList + NUM_OBJECTS - 1); + + for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) { + // check Z order + if (pInsObj->zPos < pObj->zPos) { + // object Z is lower than list Z - insert here + break; + } else if (pInsObj->zPos == pObj->zPos) { + // Z values are the same - sort on Y + if (fracToDouble(pInsObj->yPos) <= fracToDouble(pObj->yPos)) { + // object Y is lower than or same as list Y - insert here + break; + } + } + } + + // insert obj between pPrev and pObj + pInsObj->pNext = pObj; + pPrev->pNext = pInsObj; +} + + +/** + * Deletes an object from the specified object list and places it + * on the free list. + * @param pObjList List to delete object from + * @param pDelObj Object to delete + */ +void DelObject(OBJECT *pObjList, OBJECT *pDelObj) { + OBJECT *pPrev, *pObj; // object list traversal pointers + + // validate object pointer + assert(pDelObj >= objectList && pDelObj <= objectList + NUM_OBJECTS - 1); + +#ifdef DEBUG + // one less object in use + --numObj; + assert(numObj >= 0); +#endif + + for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) { + if (pObj == pDelObj) { + // found object to delete + + if (IntersectRectangle(pDelObj->rcPrev, pDelObj->rcPrev, rcScreen)) { + // allocate a clipping rect for objects previous pos + AddClipRect(pDelObj->rcPrev); + } + + // make PREV next = OBJ next - removes OBJ from list + pPrev->pNext = pObj->pNext; + + // place free list in OBJ next + pObj->pNext = pFreeObjects; + + // add OBJ to top of free list + pFreeObjects = pObj; + + // delete objects palette + if (pObj->pPal) + FreePalette(pObj->pPal); + + // quit + return; + } + } + + // if we get to here - object has not been found on the list + error("DelObject(): formally 'assert(0)!'"); +} + + +/** + * Sort the specified object list in Z Y order. + * @param pObjList List to sort + */ +void SortObjectList(OBJECT *pObjList) { + OBJECT *pPrev, *pObj; // object list traversal pointers + OBJECT head; // temporary head of list - because pObjList is not usually a OBJECT + + // put at head of list + head.pNext = pObjList->pNext; + + // set head of list dummy OBJ Z Y values to lowest possible + head.yPos = intToFrac(MIN_INT16); + head.zPos = MIN_INT; + + for (pPrev = &head, pObj = head.pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) { + // check Z order + if (pObj->zPos < pPrev->zPos) { + // object Z is lower than previous Z + + // remove object from list + pPrev->pNext = pObj->pNext; + + // re-insert object on list + InsertObject(pObjList, pObj); + + // back to beginning of list + pPrev = &head; + pObj = head.pNext; + } else if (pObj->zPos == pPrev->zPos) { + // Z values are the same - sort on Y + if (fracToDouble(pObj->yPos) < fracToDouble(pPrev->yPos)) { + // object Y is lower than previous Y + + // remove object from list + pPrev->pNext = pObj->pNext; + + // re-insert object on list + InsertObject(pObjList, pObj); + + // back to beginning of list + pPrev = &head; + pObj = head.pNext; + } + } + } +} + +/** + * Returns the animation offsets of a image, dependent on the + * images orientation flags. + * @param hImg Iimage to get animation offset of + * @param flags Images current flags + * @param pAniX Gets set to new X animation offset + * @param pAniY Gets set to new Y animation offset + */ +void GetAniOffset(SCNHANDLE hImg, int flags, int *pAniX, int *pAniY) { + if (hImg) { + const IMAGE *pImg = (const IMAGE *)LockMem(hImg); + + // set ani X + *pAniX = FROM_LE_16(pImg->anioffX); + + // set ani Y + *pAniY = FROM_LE_16(pImg->anioffY); + + if (flags & DMA_FLIPH) { + // we are flipped horizontally + + // set ani X = -ani X + width - 1 + *pAniX = -*pAniX + FROM_LE_16(pImg->imgWidth) - 1; + } + + if (flags & DMA_FLIPV) { + // we are flipped vertically + + // set ani Y = -ani Y + height - 1 + *pAniY = -*pAniY + FROM_LE_16(pImg->imgHeight) - 1; + } + } else + // null image + *pAniX = *pAniY = 0; +} + + +/** + * Returns the x,y position of an objects animation point. + * @param pObj Pointer to object + * @param pPosX Gets set to objects X animation position + * @param pPosY Gets set to objects Y animation position + */ +void GetAniPosition(OBJECT *pObj, int *pPosX, int *pPosY) { + // validate object pointer + assert(pObj >= objectList && pObj <= objectList + NUM_OBJECTS - 1); + + // get the animation offset of the object + GetAniOffset(pObj->hImg, pObj->flags, pPosX, pPosY); + + // from animation offset and objects position - determine objects animation point + *pPosX += fracToInt(pObj->xPos); + *pPosY += fracToInt(pObj->yPos); +} + +/** + * Initialise a object using a OBJ_INIT structure to supply parameters. + * @param pInitTbl Pointer to object initialisation table + */ +OBJECT *InitObject(const OBJ_INIT *pInitTbl) { + // allocate a new object + OBJECT *pObj = AllocObject(); + + // make sure object created + assert(pObj != NULL); + + // set objects shape + pObj->hImg = pInitTbl->hObjImg; + + // set objects ID + pObj->oid = pInitTbl->objID; + + // set objects flags + pObj->flags = DMA_CHANGED | pInitTbl->objFlags; + + // set objects Z position + pObj->zPos = pInitTbl->objZ; + + // get pointer to image + if (pInitTbl->hObjImg) { + int aniX, aniY; // objects animation offsets + PPALQ pPalQ; // palette queue pointer + const IMAGE *pImg = (const IMAGE *)LockMem(pInitTbl->hObjImg); // handle to image + + // allocate a palette for this object + pPalQ = AllocPalette(FROM_LE_32(pImg->hImgPal)); + + // make sure palette allocated + assert(pPalQ != NULL); + + // assign palette to object + pObj->pPal = pPalQ; + + // set objects size + pObj->width = FROM_LE_16(pImg->imgWidth); + pObj->height = FROM_LE_16(pImg->imgHeight); + + // set objects bitmap definition + pObj->hBits = FROM_LE_32(pImg->hImgBits); + + // get animation offset of object + GetAniOffset(pObj->hImg, pInitTbl->objFlags, &aniX, &aniY); + + // set objects X position - subtract ani offset + pObj->xPos = intToFrac(pInitTbl->objX - aniX); + + // set objects Y position - subtract ani offset + pObj->yPos = intToFrac(pInitTbl->objY - aniY); + } else { // no image handle - null image + + // set objects X position + pObj->xPos = intToFrac(pInitTbl->objX); + + // set objects Y position + pObj->yPos = intToFrac(pInitTbl->objY); + } + + // return new object + return pObj; +} + +/** + * Give a object a new image and new orientation flags. + * @param pAniObj Object to be updated + * @param newflags Objects new flags + * @param hNewImg Objects new image + */ +void AnimateObjectFlags(OBJECT *pAniObj, int newflags, SCNHANDLE hNewImg) { + // validate object pointer + assert(pAniObj >= objectList && pAniObj <= objectList + NUM_OBJECTS - 1); + + if (pAniObj->hImg != hNewImg + || (pAniObj->flags & DMA_HARDFLAGS) != (newflags & DMA_HARDFLAGS)) { + // something has changed + + int oldAniX, oldAniY; // objects old animation offsets + int newAniX, newAniY; // objects new animation offsets + + // get objects old animation offsets + GetAniOffset(pAniObj->hImg, pAniObj->flags, &oldAniX, &oldAniY); + + // get objects new animation offsets + GetAniOffset(hNewImg, newflags, &newAniX, &newAniY); + + if (hNewImg) { + // get pointer to image + const IMAGE *pNewImg = (IMAGE *)LockMem(hNewImg); + + // setup new shape + pAniObj->width = FROM_LE_16(pNewImg->imgWidth); + pAniObj->height = FROM_LE_16(pNewImg->imgHeight); + + // set objects bitmap definition + pAniObj->hBits = FROM_LE_32(pNewImg->hImgBits); + } else { // null image + pAniObj->width = 0; + pAniObj->height = 0; + pAniObj->hBits = 0; + } + + // set objects flags and signal a change + pAniObj->flags = newflags | DMA_CHANGED; + + // set objects image + pAniObj->hImg = hNewImg; + + // adjust objects position - subtract new from old for difference + pAniObj->xPos += intToFrac(oldAniX - newAniX); + pAniObj->yPos += intToFrac(oldAniY - newAniY); + } +} + +/** + * Give an object a new image. + * @param pAniObj Object to animate + * @param hNewImg Objects new image + */ +void AnimateObject(OBJECT *pAniObj, SCNHANDLE hNewImg) { + // dont change the objects flags + AnimateObjectFlags(pAniObj, pAniObj->flags, hNewImg); +} + +/** + * Creates a rectangle object of the given dimensions and returns + * a pointer to the object. + * @param hPal Palette for the rectangle object + * @param colour Which colour offset from the above palette + * @param width Width of rectangle + * @param height Height of rectangle + */ +OBJECT *RectangleObject(SCNHANDLE hPal, int colour, int width, int height) { + // template for initialising the rectangle object + static const OBJ_INIT rectObj = {0, DMA_CONST, OID_EFFECTS, 0, 0, 0}; + PPALQ pPalQ; // palette queue pointer + + // allocate and init a new object + OBJECT *pRect = InitObject(&rectObj); + + // allocate a palette for this object + pPalQ = AllocPalette(hPal); + + // make sure palette allocated + assert(pPalQ != NULL); + + // assign palette to object + pRect->pPal = pPalQ; + + // set colour in the palette + pRect->constant = colour; + + // set rectangle width + pRect->width = width; + + // set rectangle height + pRect->height = height; + + // return pointer to rectangle object + return pRect; +} + +/** + * Creates a translucent rectangle object of the given dimensions + * and returns a pointer to the object. + * @param width Width of rectangle + * @param height Height of rectangle + */ +OBJECT *TranslucentObject(int width, int height) { + // template for initialising the rectangle object + static const OBJ_INIT rectObj = {0, DMA_TRANS, OID_EFFECTS, 0, 0, 0}; + + // allocate and init a new object + OBJECT *pRect = InitObject(&rectObj); + + // set rectangle width + pRect->width = width; + + // set rectangle height + pRect->height = height; + + // return pointer to rectangle object + return pRect; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/object.h b/engines/tinsel/object.h new file mode 100644 index 0000000000..01c56737e4 --- /dev/null +++ b/engines/tinsel/object.h @@ -0,0 +1,207 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Object Manager data structures + */ + +#ifndef TINSEL_OBJECT_H // prevent multiple includes +#define TINSEL_OBJECT_H + +#include "tinsel/dw.h" +#include "common/frac.h" +#include "common/rect.h" + +namespace Tinsel { + +struct PALQ; + +enum { + /** the maximum number of objects */ + NUM_OBJECTS = 256, + + // object flags + DMA_WNZ = 0x0001, //!< write non-zero data + DMA_CNZ = 0x0002, //!< write constant on non-zero data + DMA_CONST = 0x0004, //!< write constant on both zero & non-zero data + DMA_WA = 0x0008, //!< write all data + DMA_FLIPH = 0x0010, //!< flip object horizontally + DMA_FLIPV = 0x0020, //!< flip object vertically + DMA_CLIP = 0x0040, //!< clip object + DMA_TRANS = 0x0084, //!< translucent rectangle object + DMA_ABS = 0x0100, //!< position of object is absolute + DMA_CHANGED = 0x0200, //!< object has changed in some way since the last frame + DMA_USERDEF = 0x0400, //!< user defined flags start here + + /** flags that effect an objects appearance */ + DMA_HARDFLAGS = (DMA_WNZ | DMA_CNZ | DMA_CONST | DMA_WA | DMA_FLIPH | DMA_FLIPV | DMA_TRANS) +}; + +/** structure for image */ +struct IMAGE { + short imgWidth; //!< image width + short imgHeight; //!< image height + short anioffX; //!< image x animation offset + short anioffY; //!< image y animation offset + SCNHANDLE hImgBits; //!< image bitmap handle + SCNHANDLE hImgPal; //!< image palette handle +}; +typedef IMAGE *PIMAGE; + + +/** a multi-object animation frame is a list of multi-image handles */ +typedef uint32 FRAME; + + +// object structure +struct OBJECT { + OBJECT *pNext; //!< pointer to next object in list + OBJECT *pSlave; //!< pointer to slave object (multi-part objects) +// char *pOnDispList; //!< pointer to display list byte for background objects +// frac_t xVel; //!< x velocity of object +// frac_t yVel; //!< y velocity of object + frac_t xPos; //!< x position of object + frac_t yPos; //!< y position of object + int zPos; //!< z position of object + Common::Rect rcPrev; //!< previous screen coordinates of object bounding rectangle + int flags; //!< object flags - see above for list + PALQ *pPal; //!< objects palette Q position + int constant; //!< which colour in palette for monochrome objects + int width; //!< width of object + int height; //!< height of object + SCNHANDLE hBits; //!< image bitmap handle + SCNHANDLE hImg; //!< handle to object image definition + SCNHANDLE hShape; //!< objects current animation frame + SCNHANDLE hMirror; //!< objects previous animation frame + int oid; //!< object identifier +}; + +#include "common/pack-start.h" // START STRUCT PACKING + +// object initialisation structure +struct OBJ_INIT { + SCNHANDLE hObjImg; // objects shape - handle to IMAGE structure + int32 objFlags; // objects flags + int32 objID; // objects id + int32 objX; // objects initial x position + int32 objY; // objects initial y position + int32 objZ; // objects initial z position +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + + +/*----------------------------------------------------------------------*\ +|* Object Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void KillAllObjects(void); // kill all objects and place them on free list + +void FreeObjectList(void); // free the object list + +#ifdef DEBUG +void ObjectStats(void); // Shows the maximum number of objects used at once +#endif + +OBJECT *AllocObject(void); // allocate a object from the free list + +void FreeObject( // place a object back on the free list + OBJECT *pFreeObj); // object to free + +void CopyObject( // copy one object to another + OBJECT *pDest, // destination object + OBJECT *pSrc); // source object + +void InsertObject( // insert a object onto a sorted object list + OBJECT *pObjList, // list to insert object onto + OBJECT *pInsObj); // object to insert + +void DelObject( // delete a object from a object list and add to free list + OBJECT *pObjList, // list to delete object from + OBJECT *pDelObj); // object to delete + +void SortObjectList( // re-sort an object list + OBJECT *pObjList); // list to sort + +OBJECT *GetNextObject( // object list iterator - returns next obj in list + OBJECT *pObjList, // which object list + OBJECT *pStrtObj); // object to start from - when NULL will start from beginning of list + +OBJECT *FindObject( // Searches the specified obj list for a object matching the specified OID + OBJECT *pObjList, // object list to search + int oidDesired, // object identifer of object to find + int oidMask); // mask to apply to object identifiers before comparison + +void GetAniOffset( // returns the anim offsets of a image, takes into account orientation + SCNHANDLE hImg, // image to get animation offset of + int flags, // images current flags + int *pAniX, // gets set to new X animation offset + int *pAniY); // gets set to new Y animation offset + +void GetAniPosition( // Returns a objects x,y animation point + OBJECT *pObj, // pointer to object + int *pPosX, // gets set to objects X animation position + int *pPosY); // gets set to objects Y animation position + +OBJECT *InitObject( // Init a object using a OBJ_INIT struct + const OBJ_INIT *pInitTbl); // pointer to object initialisation table + +void AnimateObjectFlags( // Give a object a new image and new orientation flags + OBJECT *pAniObj, // object to be updated + int newflags, // objects new flags + SCNHANDLE hNewImg); // objects new image + +void AnimateObject( // give a object a new image + OBJECT *pAniObj, // object to animate + SCNHANDLE hNewImg); // objects new image + +void HideObject( // Hides a object by giving it a "NullImage" image pointer + OBJECT *pObj); // object to be hidden + +OBJECT *RectangleObject( // create a rectangle object of the given dimensions + SCNHANDLE hPal, // palette for the rectangle object + int colour, // which colour offset from the above palette + int width, // width of rectangle + int height); // height of rectangle + +OBJECT *TranslucentObject( // create a translucent rectangle object of the given dimensions + int width, // width of rectangle + int height); // height of rectangle + +void ResizeRectangle( // resizes a rectangle object + OBJECT *pRect, // rectangle object pointer + int width, // new width of rectangle + int height); // new height of rectangle + + +// FIXME: This does not belong here +struct FILM; +struct FREEL; +struct MULTI_INIT; +IMAGE *GetImageFromReel(const FREEL *pfreel, const MULTI_INIT **ppmi = 0); +IMAGE *GetImageFromFilm(SCNHANDLE hFilm, int reel, const FREEL **ppfr = 0, + const MULTI_INIT **ppmi = 0, const FILM **ppfilm = 0); + + +} // end of namespace Tinsel + +#endif // TINSEL_OBJECT_H diff --git a/engines/tinsel/palette.cpp b/engines/tinsel/palette.cpp new file mode 100644 index 0000000000..50a908afe3 --- /dev/null +++ b/engines/tinsel/palette.cpp @@ -0,0 +1,424 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Palette Allocator for IBM PC. + */ + +#include "tinsel/dw.h" // TBLUE1 definition +#include "tinsel/graphics.h" +#include "tinsel/handle.h" // LockMem definition +#include "tinsel/palette.h" // palette allocator structures etc. +#include "tinsel/tinsel.h" + +#include "common/system.h" + +namespace Tinsel { + +/** background colour */ +static COLORREF bgndColour = BLACK; + +/** palette allocator data */ +static PALQ palAllocData[NUM_PALETTES]; + + +/** video DAC transfer Q length */ +#define VDACQLENGTH (NUM_PALETTES+2) + +/** video DAC transfer Q */ +static VIDEO_DAC_Q vidDACdata[VDACQLENGTH]; + +/** video DAC transfer Q head pointer */ +static VIDEO_DAC_Q *pDAChead; + +/** colour index of the 4 colours used for the translucent palette */ +#define COL_HILIGHT TBLUE1 + +/** the translucent palette lookup table */ +uint8 transPalette[MAX_COLOURS]; // used in graphics.cpp + +#ifdef DEBUG +// diagnostic palette counters +static int numPals = 0; +static int maxPals = 0; +static int maxDACQ = 0; +#endif + +/** + * Transfer palettes in the palette Q to Video DAC. + */ +void PalettesToVideoDAC(void) { + PPALQ pPalQ; // palette Q iterator + PVIDEO_DAC_Q pDACtail = vidDACdata; // set tail pointer + bool needUpdate = false; + + // while Q is not empty + while (pDAChead != pDACtail) { + PALETTE *pPalette; // pointer to hardware palette + COLORREF *pColours; // pointer to list of RGB triples + +#ifdef DEBUG + // make sure palette does not overlap + assert(pDACtail->destDACindex + pDACtail->numColours <= MAX_COLOURS); +#else + // make sure palette does not overlap + if (pDACtail->destDACindex + pDACtail->numColours > MAX_COLOURS) + pDACtail->numColours = MAX_COLOURS - pDACtail->destDACindex; +#endif + + if (pDACtail->bHandle) { + // we are using a palette handle + + // get hardware palette pointer + pPalette = (PALETTE *)LockMem(pDACtail->pal.hRGBarray); + + // get RGB pointer + pColours = pPalette->palRGB; + } else { + // we are using a palette pointer + pColours = pDACtail->pal.pRGBarray; + } + + if (pDACtail->numColours > 0) + needUpdate = true; + + // update the system palette + g_system->setPalette((byte *)pColours, pDACtail->destDACindex, pDACtail->numColours); + + // update tail pointer + pDACtail++; + + } + + // reset video DAC transfer Q head pointer + pDAChead = vidDACdata; + + // clear all palette moved bits + for (pPalQ = palAllocData; pPalQ < palAllocData + NUM_PALETTES; pPalQ++) + pPalQ->posInDAC &= ~PALETTE_MOVED; + + if (needUpdate) + _vm->screen().update(); +} + +/** + * Commpletely reset the palette allocator. + */ +void ResetPalAllocator(void) { +#ifdef DEBUG + // clear number of palettes in use + numPals = 0; +#endif + + // wipe out the palette allocator data + memset(palAllocData, 0, sizeof(palAllocData)); + + // reset video DAC transfer Q head pointer + pDAChead = vidDACdata; +} + +#ifdef DEBUG +/** + * Shows the maximum number of palettes used at once. + */ +void PaletteStats(void) { + printf("%i palettes of %i used.\n", maxPals, NUM_PALETTES); + printf("%i DAC queue entries of %i used.\n", maxDACQ, VDACQLENGTH); +} +#endif + +/** + * Places a palette in the video DAC queue. + * @param posInDAC Position in video DAC + * @param numColours Number of colours in palette + * @param hPalette Handle to palette + */ +void UpdateDACqueueHandle(int posInDAC, int numColours, SCNHANDLE hPalette) { + // check Q overflow + assert(pDAChead < vidDACdata + VDACQLENGTH); + + pDAChead->destDACindex = posInDAC & ~PALETTE_MOVED; // set index in video DAC + pDAChead->numColours = numColours; // set number of colours + pDAChead->pal.hRGBarray = hPalette; // set handle of palette + pDAChead->bHandle = true; // we are using a palette handle + + // update head pointer + ++pDAChead; + +#ifdef DEBUG + if ((pDAChead-vidDACdata) > maxDACQ) + maxDACQ = pDAChead-vidDACdata; +#endif +} + +/** + * Places a palette in the video DAC queue. + * @param posInDAC Position in video DAC + * @param numColours, Number of colours in palette + * @param pColours List of RGB triples + */ +void UpdateDACqueue(int posInDAC, int numColours, COLORREF *pColours) { + // check Q overflow + assert(pDAChead < vidDACdata + NUM_PALETTES); + + pDAChead->destDACindex = posInDAC & ~PALETTE_MOVED; // set index in video DAC + pDAChead->numColours = numColours; // set number of colours + pDAChead->pal.pRGBarray = pColours; // set addr of palette + pDAChead->bHandle = false; // we are not using a palette handle + + // update head pointer + ++pDAChead; + +#ifdef DEBUG + if ((pDAChead-vidDACdata) > maxDACQ) + maxDACQ = pDAChead-vidDACdata; +#endif +} + +/** + * Allocate a palette. + * @param hNewPal Palette to allocate + */ +PPALQ AllocPalette(SCNHANDLE hNewPal) { + PPALQ pPrev, p; // walks palAllocData + int iDAC; // colour index in video DAC + PPALQ pNxtPal; // next PALQ struct in palette allocator + PALETTE *pNewPal; + + // get pointer to new palette + pNewPal = (PALETTE *)LockMem(hNewPal); + + // search all structs in palette allocator - see if palette already allocated + for (p = palAllocData; p < palAllocData + NUM_PALETTES; p++) { + if (p->hPal == hNewPal) { + // found the desired palette in palette allocator + p->objCount++; // update number of objects using palette + return p; // return palette queue position + } + } + + // search all structs in palette allocator - find a free slot + iDAC = FGND_DAC_INDEX; // init DAC index to first available foreground colour + + for (p = palAllocData; p < palAllocData + NUM_PALETTES; p++) { + if (p->hPal == 0) { + // found a free slot in palette allocator + p->objCount = 1; // init number of objects using palette + p->posInDAC = iDAC; // set palettes start pos in video DAC + p->hPal = hNewPal; // set hardware palette data + p->numColours = FROM_LE_32(pNewPal->numColours); // set number of colours in palette + +#ifdef DEBUG + // one more palette in use + if (++numPals > maxPals) + maxPals = numPals; +#endif + + // Q the change to the video DAC + UpdateDACqueueHandle(p->posInDAC, p->numColours, p->hPal); + + // move all palettes after this one down (if necessary) + for (pPrev = p, pNxtPal = pPrev + 1; pNxtPal < palAllocData + NUM_PALETTES; pNxtPal++) { + if (pNxtPal->hPal != 0) { + // palette slot is in use + if (pNxtPal->posInDAC >= pPrev->posInDAC + pPrev->numColours) + // no need to move palettes down + break; + + // move palette down - indicate change + pNxtPal->posInDAC = pPrev->posInDAC + + pPrev->numColours | PALETTE_MOVED; + + // Q the palette change in position to the video DAC + UpdateDACqueueHandle(pNxtPal->posInDAC, + pNxtPal->numColours, + pNxtPal->hPal); + + // update previous palette to current palette + pPrev = pNxtPal; + } + } + + // return palette pointer + return p; + } + + // set new DAC index + iDAC = p->posInDAC + p->numColours; + } + + // no free palettes + error("AllocPalette(): formally 'assert(0)!'"); +} + +/** + * Free a palette allocated with "AllocPalette". + * @param pFreePal Palette queue entry to free + */ +void FreePalette(PPALQ pFreePal) { + // validate palette Q pointer + assert(pFreePal >= palAllocData && pFreePal <= palAllocData + NUM_PALETTES - 1); + + // reduce the palettes object reference count + pFreePal->objCount--; + + // make sure palette has not been deallocated too many times + assert(pFreePal->objCount >= 0); + + if (pFreePal->objCount == 0) { + pFreePal->hPal = 0; // palette is no longer in use + +#ifdef DEBUG + // one less palette in use + --numPals; + assert(numPals >= 0); +#endif + } +} + +/** + * Find the specified palette. + * @param hSrchPal Hardware palette to search for + */ +PPALQ FindPalette(SCNHANDLE hSrchPal) { + PPALQ pPal; // palette allocator iterator + + // search all structs in palette allocator + for (pPal = palAllocData; pPal < palAllocData + NUM_PALETTES; pPal++) { + if (pPal->hPal == hSrchPal) + // found palette in palette allocator + return pPal; + } + + // palette not found + return NULL; +} + +/** + * Swaps the palettes at the specified palette queue position. + * @param pPalQ Palette queue position + * @param hNewPal New palette + */ +void SwapPalette(PPALQ pPalQ, SCNHANDLE hNewPal) { + // convert handle to palette pointer + PALETTE *pNewPal = (PALETTE *)LockMem(hNewPal); + + // validate palette Q pointer + assert(pPalQ >= palAllocData && pPalQ <= palAllocData + NUM_PALETTES - 1); + + if (pPalQ->numColours >= (int)FROM_LE_32(pNewPal->numColours)) { + // new palette will fit the slot + + // install new palette + pPalQ->hPal = hNewPal; + + // Q the change to the video DAC + UpdateDACqueueHandle(pPalQ->posInDAC, FROM_LE_32(pNewPal->numColours), hNewPal); + } else { + // # colours are different - will have to update all following palette entries + + PPALQ pNxtPalQ; // next palette queue position + + for (pNxtPalQ = pPalQ + 1; pNxtPalQ < palAllocData + NUM_PALETTES; pNxtPalQ++) { + if (pNxtPalQ->posInDAC >= pPalQ->posInDAC + pPalQ->numColours) + // no need to move palettes down + break; + + // move palette down + pNxtPalQ->posInDAC = pPalQ->posInDAC + + pPalQ->numColours | PALETTE_MOVED; + + // Q the palette change in position to the video DAC + UpdateDACqueueHandle(pNxtPalQ->posInDAC, + pNxtPalQ->numColours, + pNxtPalQ->hPal); + + // update previous palette to current palette + pPalQ = pNxtPalQ; + } + } +} + +/** + * Statless palette iterator. Returns the next palette in the list + * @param pStrtPal Palette to start from - when NULL will start from beginning of list + */ +PPALQ GetNextPalette(PPALQ pStrtPal) { + if (pStrtPal == NULL) { + // start of palette iteration - return 1st palette + return (palAllocData[0].objCount) ? palAllocData : NULL; + } + + // validate palette Q pointer + assert(pStrtPal >= palAllocData && pStrtPal <= palAllocData + NUM_PALETTES - 1); + + // return next active palette in list + while (++pStrtPal < palAllocData + NUM_PALETTES) { + if (pStrtPal->objCount) + // active palette found + return pStrtPal; + } + + // non found + return NULL; +} + +/** + * Sets the current background colour. + * @param colour Colour to set the background to + */ +void SetBgndColour(COLORREF colour) { + // update background colour struct + bgndColour = colour; + + // Q the change to the video DAC + UpdateDACqueue(BGND_DAC_INDEX, 1, &bgndColour); +} + +/** + * Builds the translucent palette from the current backgrounds palette. + * @param hPalette Handle to current background palette + */ +void CreateTranslucentPalette(SCNHANDLE hPalette) { + // get a pointer to the palette + PALETTE *pPal = (PALETTE *)LockMem(hPalette); + + // leave background colour alone + transPalette[0] = 0; + + for (uint i = 0; i < FROM_LE_32(pPal->numColours); i++) { + // get the RGB colour model values + uint8 red = GetRValue(pPal->palRGB[i]); + uint8 green = GetGValue(pPal->palRGB[i]); + uint8 blue = GetBValue(pPal->palRGB[i]); + + // calculate the Value field of the HSV colour model + unsigned val = (red > green) ? red : green; + val = (val > blue) ? val : blue; + + // map the Value field to one of the 4 colours reserved for the translucent palette + val /= 63; + transPalette[i + 1] = (uint8)((val == 0) ? 0 : val + COL_HILIGHT - 1); + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/palette.h b/engines/tinsel/palette.h new file mode 100644 index 0000000000..ed648950fd --- /dev/null +++ b/engines/tinsel/palette.h @@ -0,0 +1,157 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Palette Allocator Definitions + */ + +#ifndef TINSEL_PALETTE_H // prevent multiple includes +#define TINSEL_PALETTE_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +typedef uint32 COLORREF; + +#define RGB(r,g,b) ((COLORREF)TO_LE_32(((uint8)(r)|((uint16)(g)<<8))|(((uint32)(uint8)(b))<<16))) + +#define GetRValue(rgb) ((uint8)(FROM_LE_32(rgb))) +#define GetGValue(rgb) ((uint8)(((uint16)(FROM_LE_32(rgb))) >> 8)) +#define GetBValue(rgb) ((uint8)((FROM_LE_32(rgb))>>16)) + +enum { + MAX_COLOURS = 256, //!< maximum number of colours - for VGA 256 + BITS_PER_PIXEL = 8, //!< number of bits per pixel for VGA 256 + MAX_INTENSITY = 255, //!< the biggest value R, G or B can have + NUM_PALETTES = 3, //!< number of palettes + + // Discworld has some fixed apportioned bits in the palette. + BGND_DAC_INDEX = 0, //!< index of background colour in Video DAC + FGND_DAC_INDEX = 1, //!< index of first foreground colour in Video DAC + TBLUE1 = 228, //!< Blue used in translucent rectangles + TBLUE2 = 229, //!< Blue used in translucent rectangles + TBLUE3 = 230, //!< Blue used in translucent rectangles + TBLUE4 = 231, //!< Blue used in translucent rectangles + TALKFONT_COL = 233 +}; + +// some common colours + +#define BLACK (RGB(0, 0, 0)) +#define WHITE (RGB(MAX_INTENSITY, MAX_INTENSITY, MAX_INTENSITY)) +#define RED (RGB(MAX_INTENSITY, 0, 0)) +#define GREEN (RGB(0, MAX_INTENSITY, 0)) +#define BLUE (RGB(0, 0, MAX_INTENSITY)) +#define YELLOW (RGB(MAX_INTENSITY, MAX_INTENSITY, 0)) +#define MAGENTA (RGB(MAX_INTENSITY, 0, MAX_INTENSITY)) +#define CYAN (RGB(0, MAX_INTENSITY, MAX_INTENSITY)) + + +/** video DAC transfer Q structure */ +struct VIDEO_DAC_Q { + union { + SCNHANDLE hRGBarray; //!< handle of palette or + COLORREF *pRGBarray; //!< list of palette colours + } pal; + bool bHandle; //!< when set - use handle of palette + int destDACindex; //!< start index of palette in video DAC + int numColours; //!< number of colours in "hRGBarray" +}; +typedef VIDEO_DAC_Q *PVIDEO_DAC_Q; + +#include "common/pack-start.h" // START STRUCT PACKING + +/** hardware palette structure */ +struct PALETTE { + int32 numColours; //!< number of colours in the palette + COLORREF palRGB[MAX_COLOURS]; //!< actual palette colours +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + + +/** palette queue structure */ +struct PALQ { + SCNHANDLE hPal; //!< handle to palette data struct + int objCount; //!< number of objects using this palette + int posInDAC; //!< palette position in the video DAC + int numColours; //!< number of colours in the palette +}; +typedef PALQ *PPALQ; + + +#define PALETTE_MOVED 0x8000 // when this bit is set in the "posInDAC" + // field - the palette entry has moved + +// Translucent objects have NULL pPal +#define HasPalMoved(pPal) (((pPal) != NULL) && ((pPal)->posInDAC & PALETTE_MOVED)) + + +/*----------------------------------------------------------------------*\ +|* Palette Manager Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void ResetPalAllocator(void); // wipe out all palettes + +#ifdef DEBUG +void PaletteStats(void); // Shows the maximum number of palettes used at once +#endif + +void PalettesToVideoDAC(void); // Update the video DAC with palettes currently the the DAC queue + +void UpdateDACqueueHandle( + int posInDAC, // position in video DAC + int numColours, // number of colours in palette + SCNHANDLE hPalette); // handle to palette + +void UpdateDACqueue( // places a palette in the video DAC queue + int posInDAC, // position in video DAC + int numColours, // number of colours in palette + COLORREF *pColours); // list of RGB tripples + +PPALQ AllocPalette( // allocate a new palette + SCNHANDLE hNewPal); // palette to allocate + +void FreePalette( // free a palette allocated with "AllocPalette" + PPALQ pFreePal); // palette queue entry to free + +PPALQ FindPalette( // find a palette in the palette queue + SCNHANDLE hSrchPal); // palette to search for + +void SwapPalette( // swaps palettes at the specified palette queue position + PPALQ pPalQ, // palette queue position + SCNHANDLE hNewPal); // new palette + +PPALQ GetNextPalette( // returns the next palette in the queue + PPALQ pStrtPal); // queue position to start from - when NULL will start from beginning of queue + +COLORREF GetBgndColour(void); // returns current background colour + +void SetBgndColour( // sets current background colour + COLORREF colour); // colour to set the background to + +void CreateTranslucentPalette(SCNHANDLE BackPal); + +} // end of namespace Tinsel + +#endif // TINSEL_PALETTE_H diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp new file mode 100644 index 0000000000..22b7ca8275 --- /dev/null +++ b/engines/tinsel/pcode.cpp @@ -0,0 +1,597 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Virtual processor. + */ + +#include "tinsel/dw.h" +#include "tinsel/events.h" // 'POINTED' etc. +#include "tinsel/handle.h" // LockMem() +#include "tinsel/inventory.h" // for inventory id's +#include "tinsel/pcode.h" // opcodes etc. +#include "tinsel/scn.h" // FindChunk() +#include "tinsel/serializer.h" +#include "tinsel/tinlib.h" // Library routines + +#include "common/util.h" + +namespace Tinsel { + +//----------------- EXTERN FUNCTIONS -------------------- + +extern int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const PINT_CONTEXT pic, RESUME_STATE *pResumeState); + +//----------------- LOCAL DEFINES -------------------- + +/** list of all opcodes */ +enum OPCODE { + OP_HALT = 0, //!< end of program + OP_IMM = 1, //!< loads signed immediate onto stack + OP_ZERO = 2, //!< loads zero onto stack + OP_ONE = 3, //!< loads one onto stack + OP_MINUSONE = 4, //!< loads minus one onto stack + OP_STR = 5, //!< loads string offset onto stack + OP_FILM = 6, //!< loads film offset onto stack + OP_FONT = 7, //!< loads font offset onto stack + OP_PAL = 8, //!< loads palette offset onto stack + OP_LOAD = 9, //!< loads local variable onto stack + OP_GLOAD = 10, //!< loads global variable onto stack - long offset to variable + OP_STORE = 11, //!< pops stack and stores in local variable - long offset to variable + OP_GSTORE = 12, //!< pops stack and stores in global variable - long offset to variable + OP_CALL = 13, //!< procedure call + OP_LIBCALL = 14, //!< library procedure call - long offset to procedure + OP_RET = 15, //!< procedure return + OP_ALLOC = 16, //!< allocate storage on stack + OP_JUMP = 17, //!< unconditional jump - signed word offset + OP_JMPFALSE = 18, //!< conditional jump - signed word offset + OP_JMPTRUE = 19, //!< conditional jump - signed word offset + OP_EQUAL = 20, //!< tests top two items on stack for equality + OP_LESS, //!< tests top two items on stack + OP_LEQUAL, //!< tests top two items on stack + OP_NEQUAL, //!< tests top two items on stack + OP_GEQUAL, //!< tests top two items on stack + OP_GREAT = 25, //!< tests top two items on stack + OP_PLUS, //!< adds top two items on stack and replaces with result + OP_MINUS, //!< subs top two items on stack and replaces with result + OP_LOR, //!< logical or of top two items on stack and replaces with result + OP_MULT, //!< multiplies top two items on stack and replaces with result + OP_DIV = 30, //!< divides top two items on stack and replaces with result + OP_MOD, //!< divides top two items on stack and replaces with modulus + OP_AND, //!< bitwise ands top two items on stack and replaces with result + OP_OR, //!< bitwise ors top two items on stack and replaces with result + OP_EOR, //!< bitwise exclusive ors top two items on stack and replaces with result + OP_LAND = 35, //!< logical ands top two items on stack and replaces with result + OP_NOT, //!< logical nots top item on stack + OP_COMP, //!< complements top item on stack + OP_NEG, //!< negates top item on stack + OP_DUP, //!< duplicates top item on stack + OP_ESCON = 40, //!< start of escapable sequence + OP_ESCOFF = 41, //!< end of escapable sequence + OP_CIMM, //!< loads signed immediate onto stack (special to case statements) + OP_CDFILM //!< loads film offset onto stack but not in current scene +}; + +// modifiers for the above opcodes +#define OPSIZE8 0x40 //!< when this bit is set - the operand size is 8 bits +#define OPSIZE16 0x80 //!< when this bit is set - the operand size is 16 bits + +#define OPMASK 0x3F //!< mask to isolate the opcode + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int32 *pGlobals = 0; // global vars + +static int numGlobals = 0; // How many global variables to save/restore + +static PINT_CONTEXT icList = 0; + +/** + * Keeps the code array pointer up to date. + */ +void LockCode(PINT_CONTEXT ic) { + if (ic->GSort == GS_MASTER) + ic->code = (byte *)FindChunk(MASTER_SCNHANDLE, CHUNK_PCODE); + else + ic->code = (byte *)LockMem(ic->hCode); +} + +/** + * Find a free interpret context and allocate it to the calling process. + */ +static PINT_CONTEXT AllocateInterpretContext(GSORT gsort) { + PINT_CONTEXT pic; + int i; + + for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { + if (pic->GSort == GS_NONE) { + pic->pProc = CurrentProcess(); + pic->GSort = gsort; + return pic; + } +#ifdef DEBUG + else { + if (pic->pProc == CurrentProcess()) + error("Found unreleased interpret context"); + } +#endif + } + + error("Out of interpret contexts"); +} + +/** + * Normal release of an interpret context. + * Called from the end of Interpret(). + */ +static void FreeInterpretContextPi(PINT_CONTEXT pic) { + pic->GSort = GS_NONE; +} + +/** + * Free interpret context owned by a dying process. + * Ensures that interpret contexts don't get lost when an Interpret() + * call doesn't complete. + */ +void FreeInterpretContextPr(PROCESS *pProc) { + PINT_CONTEXT pic; + int i; + + for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { + if (pic->GSort != GS_NONE && pic->pProc == pProc) { + pic->GSort = GS_NONE; + break; + } + } +} + +/** + * Free all interpret contexts except for the master script's + */ +void FreeMostInterpretContexts(void) { + PINT_CONTEXT pic; + int i; + + for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { + if (pic->GSort != GS_MASTER) { + pic->GSort = GS_NONE; + } + } +} + +/** + * Free the master script's interpret context. + */ +void FreeMasterInterpretContext(void) { + PINT_CONTEXT pic; + int i; + + for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) { + if (pic->GSort == GS_MASTER) { + pic->GSort = GS_NONE; + return; + } + } +} + +/** + * Allocate and initialise an interpret context. + * Called from a process prior to Interpret(). + * @param gsort which sort of code + * @param hCode Handle to code to execute + * @param event Causal event + * @param hpoly Associated polygon (if any) + * @param actorId Associated actor (if any) + * @param pinvo Associated inventory object + */ +PINT_CONTEXT InitInterpretContext(GSORT gsort, SCNHANDLE hCode, USER_EVENT event, + HPOLYGON hpoly, int actorid, PINV_OBJECT pinvo) { + PINT_CONTEXT ic; + + ic = AllocateInterpretContext(gsort); + + // Previously parameters to Interpret() + ic->hCode = hCode; + LockCode(ic); + ic->event = event; + ic->hpoly = hpoly; + ic->actorid = actorid; + ic->pinvo = pinvo; + + // Previously local variables in Interpret() + ic->bHalt = false; // set to exit interpeter + ic->escOn = false; + ic->myescEvent = 0; // only initialised to prevent compiler warning! + ic->sp = 0; + ic->bp = ic->sp + 1; + ic->ip = 0; // start of code + + ic->resumeState = RES_NOT; + + return ic; +} + +/** + * Allocate and initialise an interpret context with restored data. + */ +PINT_CONTEXT RestoreInterpretContext(PINT_CONTEXT ric) { + PINT_CONTEXT ic; + + ic = AllocateInterpretContext(GS_NONE); // Sort will soon be overridden + + memcpy(ic, ric, sizeof(INT_CONTEXT)); + ic->pProc = CurrentProcess(); + ic->resumeState = RES_1; + + LockCode(ic); + + return ic; +} + +/** + * Allocates enough RAM to hold the global Glitter variables. + */ +void RegisterGlobals(int num) { + if (pGlobals == NULL) { + numGlobals = num; + + // Allocate RAM for pGlobals and make sure it's allocated + pGlobals = (int32 *)calloc(numGlobals, sizeof(int32)); + if (pGlobals == NULL) { + error("Cannot allocate memory for global data"); + } + + // Allocate RAM for interpret contexts and make sure it's allocated + icList = (PINT_CONTEXT)calloc(MAX_INTERPRET, sizeof(INT_CONTEXT)); + if (icList == NULL) { + error("Cannot allocate memory for interpret contexts"); + } + + SetResourceCallback(FreeInterpretContextPr); + } else { + // Check size is still the same + assert(numGlobals == num); + + memset(pGlobals, 0, numGlobals * sizeof(int32)); + memset(icList, 0, MAX_INTERPRET * sizeof(INT_CONTEXT)); + } +} + +void FreeGlobals(void) { + if (pGlobals) { + free(pGlobals); + pGlobals = NULL; + } + + if (icList) { + free(icList); + icList = NULL; + } +} + +/** + * (Un)serialize the global data for save/restore game. + */ +void syncGlobInfo(Serializer &s) { + for (int i = 0; i < numGlobals; i++) { + s.syncAsSint32LE(pGlobals[i]); + } +} + +/** + * (Un)serialize an interpreter context for save/restore game. + */ +void INT_CONTEXT::syncWithSerializer(Serializer &s) { + if (s.isLoading()) { + // Null out the pointer fields + pProc = NULL; + code = NULL; + pinvo = NULL; + } + // Write out used fields + s.syncAsUint32LE(GSort); + s.syncAsUint32LE(hCode); + s.syncAsUint32LE(event); + s.syncAsSint32LE(hpoly); + s.syncAsSint32LE(actorid); + + for (int i = 0; i < PCODE_STACK_SIZE; ++i) + s.syncAsSint32LE(stack[i]); + + s.syncAsSint32LE(sp); + s.syncAsSint32LE(bp); + s.syncAsSint32LE(ip); + s.syncAsUint32LE(bHalt); + s.syncAsUint32LE(escOn); + s.syncAsSint32LE(myescEvent); +} + +/** + * Return pointer to and size of global data for save/restore game. + */ +void SaveInterpretContexts(PINT_CONTEXT sICInfo) { + memcpy(sICInfo, icList, MAX_INTERPRET * sizeof(INT_CONTEXT)); +} + +/** + * Fetch (and sign extend, if necessary) a 8/16/32 bit value from the code + * stream and advance the instruction pointer accordingly. + */ +static int32 Fetch(byte opcode, byte *code, int &ip) { + int32 tmp; + if (opcode & OPSIZE8) { + // Fetch and sign extend a 8 bit value to 32 bits. + tmp = *(int8 *)(code + ip); + ip += 1; + } else if (opcode & OPSIZE16) { + // Fetch and sign extend a 16 bit value to 32 bits. + tmp = (int16)READ_LE_UINT16(code + ip); + ip += 2; + } else { + // Fetch a 32 bit value. + tmp = (int32)READ_LE_UINT32(code + ip); + ip += 4; + } + return tmp; +} + +/** + * Interprets the PCODE instructions in the code array. + */ +void Interpret(CORO_PARAM, PINT_CONTEXT ic) { + do { + int tmp, tmp2; + int ip = ic->ip; + byte opcode = ic->code[ip++]; + debug(7, " Opcode %d (-> %d)", opcode, opcode & OPMASK); + switch (opcode & OPMASK) { + case OP_HALT: // end of program + + ic->bHalt = true; + break; + + case OP_IMM: // loads immediate data onto stack + case OP_STR: // loads string handle onto stack + case OP_FILM: // loads film handle onto stack + case OP_CDFILM: // loads film handle onto stack + case OP_FONT: // loads font handle onto stack + case OP_PAL: // loads palette handle onto stack + + ic->stack[++ic->sp] = Fetch(opcode, ic->code, ip); + break; + + case OP_ZERO: // loads zero onto stack + ic->stack[++ic->sp] = 0; + break; + + case OP_ONE: // loads one onto stack + ic->stack[++ic->sp] = 1; + break; + + case OP_MINUSONE: // loads minus one onto stack + ic->stack[++ic->sp] = -1; + break; + + case OP_LOAD: // loads local variable onto stack + + ic->stack[++ic->sp] = ic->stack[ic->bp + Fetch(opcode, ic->code, ip)]; + break; + + case OP_GLOAD: // loads global variable onto stack + + tmp = Fetch(opcode, ic->code, ip); + assert(0 <= tmp && tmp < numGlobals); + ic->stack[++ic->sp] = pGlobals[tmp]; + break; + + case OP_STORE: // pops stack and stores in local variable + + ic->stack[ic->bp + Fetch(opcode, ic->code, ip)] = ic->stack[ic->sp--]; + break; + + case OP_GSTORE: // pops stack and stores in global variable + + tmp = Fetch(opcode, ic->code, ip); + assert(0 <= tmp && tmp < numGlobals); + pGlobals[tmp] = ic->stack[ic->sp--]; + break; + + case OP_CALL: // procedure call + + tmp = Fetch(opcode, ic->code, ip); + //assert(0 <= tmp && tmp < codeSize); // TODO: Verify jumps are not out of bounds + ic->stack[ic->sp + 1] = 0; // static link + ic->stack[ic->sp + 2] = ic->bp; // dynamic link + ic->stack[ic->sp + 3] = ip; // return address + ic->bp = ic->sp + 1; // set new base pointer + ip = tmp; // set ip to procedure address + break; + + case OP_LIBCALL: // library procedure or function call + + tmp = Fetch(opcode, ic->code, ip); + // NOTE: Interpret() itself is not using the coroutine facilities, + // but still accepts a CORO_PARAM, so from the outside it looks + // like a coroutine. In fact it may still acts as a kind of "proxy" + // for some underlying coroutine. To enable this, we just pass on + // 'coroParam' to CallLibraryRoutine(). If we then detect that + // coroParam was set to a non-zero value, this means that some + // coroutine code did run at some point, and we are now supposed + // to sleep or die -- hence, we 'return' if coroParam != 0. + // + // This works because Interpret() is fully re-entrant: If we return + // now and are later called again, then we will end up in the very + // same spot (i.e. here). + // + // The reasons we do it this way, instead of turning Interpret into + // a 'proper' coroutine are (1) we avoid implementation problems + // (CORO_INVOKE involves adding 'case' statements, but Interpret + // already has a huge switch/case, so that would not work out of the + // box), (2) we incurr less overhead, (3) it's easier to debug, + // (4) it's simply cool ;). + tmp2 = CallLibraryRoutine(coroParam, tmp, &ic->stack[ic->sp], ic, &ic->resumeState); + if (coroParam) + return; + ic->sp += tmp2; + LockCode(ic); + break; + + case OP_RET: // procedure return + + ic->sp = ic->bp - 1; // restore stack + ip = ic->stack[ic->sp + 3]; // return address + ic->bp = ic->stack[ic->sp + 2]; // restore previous base pointer + break; + + case OP_ALLOC: // allocate storage on stack + + ic->sp += Fetch(opcode, ic->code, ip); + break; + + case OP_JUMP: // unconditional jump + + ip = Fetch(opcode, ic->code, ip); + break; + + case OP_JMPFALSE: // conditional jump + + tmp = Fetch(opcode, ic->code, ip); + if (ic->stack[ic->sp--] == 0) { + // condition satisfied - do the jump + ip = tmp; + } + break; + + case OP_JMPTRUE: // conditional jump + + tmp = Fetch(opcode, ic->code, ip); + if (ic->stack[ic->sp--] != 0) { + // condition satisfied - do the jump + ip = tmp; + } + break; + + case OP_EQUAL: // tests top two items on stack for equality + case OP_LESS: // tests top two items on stack + case OP_LEQUAL: // tests top two items on stack + case OP_NEQUAL: // tests top two items on stack + case OP_GEQUAL: // tests top two items on stack + case OP_GREAT: // tests top two items on stack + case OP_LOR: // logical or of top two items on stack and replaces with result + case OP_LAND: // logical ands top two items on stack and replaces with result + + // pop one operand + ic->sp--; + assert(ic->sp >= 0); + tmp = ic->stack[ic->sp]; + tmp2 = ic->stack[ic->sp + 1]; + + // replace other operand with result of operation + switch (opcode) { + case OP_EQUAL: tmp = (tmp == tmp2); break; + case OP_LESS: tmp = (tmp < tmp2); break; + case OP_LEQUAL: tmp = (tmp <= tmp2); break; + case OP_NEQUAL: tmp = (tmp != tmp2); break; + case OP_GEQUAL: tmp = (tmp >= tmp2); break; + case OP_GREAT: tmp = (tmp > tmp2); break; + + case OP_LOR: tmp = (tmp || tmp2); break; + case OP_LAND: tmp = (tmp && tmp2); break; + } + + ic->stack[ic->sp] = tmp; + break; + + case OP_PLUS: // adds top two items on stack and replaces with result + case OP_MINUS: // subs top two items on stack and replaces with result + case OP_MULT: // multiplies top two items on stack and replaces with result + case OP_DIV: // divides top two items on stack and replaces with result + case OP_MOD: // divides top two items on stack and replaces with modulus + case OP_AND: // bitwise ands top two items on stack and replaces with result + case OP_OR: // bitwise ors top two items on stack and replaces with result + case OP_EOR: // bitwise exclusive ors top two items on stack and replaces with result + + // pop one operand + ic->sp--; + assert(ic->sp >= 0); + tmp = ic->stack[ic->sp]; + tmp2 = ic->stack[ic->sp + 1]; + + // replace other operand with result of operation + switch (opcode) { + case OP_PLUS: tmp += tmp2; break; + case OP_MINUS: tmp -= tmp2; break; + case OP_MULT: tmp *= tmp2; break; + case OP_DIV: tmp /= tmp2; break; + case OP_MOD: tmp %= tmp2; break; + case OP_AND: tmp &= tmp2; break; + case OP_OR: tmp |= tmp2; break; + case OP_EOR: tmp ^= tmp2; break; + } + ic->stack[ic->sp] = tmp; + break; + + case OP_NOT: // logical nots top item on stack + + ic->stack[ic->sp] = !ic->stack[ic->sp]; + break; + + case OP_COMP: // complements top item on stack + ic->stack[ic->sp] = ~ic->stack[ic->sp]; + break; + + case OP_NEG: // negates top item on stack + ic->stack[ic->sp] = -ic->stack[ic->sp]; + break; + + case OP_DUP: // duplicates top item on stack + ic->stack[ic->sp + 1] = ic->stack[ic->sp]; + ic->sp++; + break; + + case OP_ESCON: + ic->escOn = true; + ic->myescEvent = GetEscEvents(); + break; + + case OP_ESCOFF: + ic->escOn = false; + break; + + default: + error("Interpret() - Unknown opcode"); + } + + // check for stack under-overflow + assert(ic->sp >= 0 && ic->sp < PCODE_STACK_SIZE); + ic->ip = ip; + } while (!ic->bHalt); + + // make sure stack is unwound + assert(ic->sp == 0); + + FreeInterpretContextPi(ic); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/pcode.h b/engines/tinsel/pcode.h new file mode 100644 index 0000000000..1fc87e6e50 --- /dev/null +++ b/engines/tinsel/pcode.h @@ -0,0 +1,158 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Virtual processor definitions + */ + +#ifndef TINSEL_PCODE_H // prevent multiple includes +#define TINSEL_PCODE_H + +#include "tinsel/events.h" // for USER_EVENT +//#include "tinsel/inventory.h" // for PINV_OBJECT +#include "tinsel/polygons.h" // for PPOLYGON +#include "tinsel/sched.h" // for PROCESS + +namespace Tinsel { + +// forward declaration +class Serializer; +struct INV_OBJECT; + +enum RESUME_STATE { + RES_NOT, RES_1, RES_2 +}; + +enum { + PCODE_STACK_SIZE = 128 //!< interpeters stack size +}; + +enum GSORT { + GS_NONE, GS_ACTOR, GS_MASTER, GS_POLYGON, GS_INVENTORY, GS_SCENE +}; + +struct INT_CONTEXT { + + // Elements for interpret context management + PROCESS *pProc; //!< processes owning this context + GSORT GSort; //!< sort of this context + + // Previously parameters to Interpret() + SCNHANDLE hCode; //!< scene handle of the code to execute + byte *code; //!< pointer to the code to execute + USER_EVENT event; //!< causal event + HPOLYGON hpoly; //!< associated polygon (if any) + int actorid; //!< associated actor (if any) + INV_OBJECT *pinvo; //!< associated inventory object + + // Previously local variables in Interpret() + int32 stack[PCODE_STACK_SIZE]; //!< interpeters run time stack + int sp; //!< stack pointer + int bp; //!< base pointer + int ip; //!< instruction pointer + bool bHalt; //!< set to exit interpeter + bool escOn; + int myescEvent; //!< only initialised to prevent compiler warning! + + RESUME_STATE resumeState; + + void syncWithSerializer(Serializer &s); +}; +typedef INT_CONTEXT *PINT_CONTEXT; + + +/*----------------------------------------------------------------------*\ +|* Interpreter Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void Interpret(CORO_PARAM, INT_CONTEXT *ic); // Interprets the PCODE instructions in the code array + +PINT_CONTEXT InitInterpretContext( + GSORT gsort, + SCNHANDLE hCode, // code to execute + USER_EVENT event, // causal event + HPOLYGON hpoly, // associated polygon (if any) + int actorid, // associated actor (if any) + INV_OBJECT *pinvo); // associated inventory object + +INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric); + +void FreeMostInterpretContexts(void); +void FreeMasterInterpretContext(void); + +void SaveInterpretContexts(INT_CONTEXT *sICInfo); + +void RegisterGlobals(int num); +void FreeGlobals(void); + + +#define MAX_INTERPRET (NUM_PROCESS - 20) + +/*----------------------------------------------------------------------*\ +|* Library Procedure and Function codes parameter enums *| +\*----------------------------------------------------------------------*/ + +#define TAG_DEF 0 // For tagactor() +#define TAG_Q1TO3 1 // tag types +#define TAG_Q1TO4 2 // tag types + +#define CONV_DEF 0 // +#define CONV_BOTTOM 1 // conversation() parameter +#define CONV_END 2 // + +#define CONTROL_OFF 0 // control() +#define CONTROL_ON 1 // parameter +#define CONTROL_OFFV 2 // +#define CONTROL_OFFV2 3 // +#define CONTROL_STARTOFF 4 // + +#define NULL_ACTOR (-1) // For actor parameters +#define LEAD_ACTOR (-2) // + +#define RAND_NORM 0 // For random() frills +#define RAND_NORPT 1 // + +#define D_UP 1 +#define D_DOWN 0 + +#define TW_START 1 // topwindow() parameter +#define TW_END 2 // + +#define MIDI_DEF 0 +#define MIDI_LOOP 1 + +#define TRANS_DEF 0 +#define TRANS_CUT 1 +#define TRANS_FADE 2 + +#define FM_IN 0 // +#define FM_OUT 1 // fademidi() + +#define FG_ON 0 // +#define FG_OFF 1 // FrameGrab() + +#define ST_ON 0 // +#define ST_OFF 1 // SubTitles() + +} // end of namespace Tinsel + +#endif // TINSEL_PCODE_H diff --git a/engines/tinsel/pdisplay.cpp b/engines/tinsel/pdisplay.cpp new file mode 100644 index 0000000000..a51f13e62e --- /dev/null +++ b/engines/tinsel/pdisplay.cpp @@ -0,0 +1,649 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * CursorPositionProcess() + * TagProcess() + * PointProcess() + */ + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/font.h" +#include "tinsel/graphics.h" +#include "tinsel/multiobj.h" +#include "tinsel/object.h" +#include "tinsel/pcode.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/strres.h" +#include "tinsel/text.h" + +namespace Tinsel { + +//----------------- EXTERNAL GLOBAL DATA -------------------- + +#ifdef DEBUG +//extern int Overrun; // The overrun counter, in DOS_DW.C + +extern int newestString; // The overrun counter, in STRRES.C +#endif + + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern int BackgroundWidth(void); +extern int BackgroundHeight(void); + + + +//----------------- LOCAL DEFINES -------------------- + +#define LPOSX 295 // X-co-ord of lead actor's position display +#define CPOSX 24 // X-co-ord of cursor's position display +#define OPOSX SCRN_CENTRE_X // X-co-ord of overrun counter's display +#define SPOSX SCRN_CENTRE_X // X-co-ord of string numbner's display + +#define POSY 0 // Y-co-ord of these position displays + +#define ACTOR_TAG 0xffffffff + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static bool DispPath = false; +static bool bShowString = false; + +static int TaggedActor = 0; +static HPOLYGON hTaggedPolygon = NOPOLY; + +static enum { TAGS_OFF, TAGS_ON } TagsActive = TAGS_ON; + + +#ifdef DEBUG +/** + * Displays the cursor and lead actor's co-ordinates and the overrun + * counter. Also which path polygon the cursor is in, if required. + * + * This process is only started up if a Glitter showpos() call is made. + * Obviously, this is for testing purposes only... + */ +void CursorPositionProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + int prevsX, prevsY; // Last screen top left + int prevcX, prevcY; // Last displayed cursor position + int prevlX, prevlY; // Last displayed lead actor position +// int prevOver; // Last displayed overrun + int prevString; // Last displayed string number + + OBJECT *cpText; // cursor position text object pointer + OBJECT *cpathText; // cursor path text object pointer + OBJECT *rpText; // text object pointer +// OBJECT *opText; // text object pointer + OBJECT *spText; // string number text object pointer + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->prevsX = -1; + _ctx->prevsY = -1; + _ctx->prevcX = -1; + _ctx->prevcY = -1; + _ctx->prevlX = -1; + _ctx->prevlY = -1; +// _ctx->prevOver = -1; + _ctx->prevString = -1; + + _ctx->cpText = NULL; + _ctx->cpathText = NULL; + _ctx->rpText = NULL; +// _ctx->opText = NULL; + _ctx->spText = NULL; + + + int aniX, aniY; // cursor/lead actor position + int Loffset, Toffset; // Screen top left + + char PositionString[64]; // sprintf() things into here + + PMACTOR pActor; // Lead actor + + while (1) { + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + /*-----------------------------------*\ + | Cursor's position and path display. | + \*-----------------------------------*/ + GetCursorXY(&aniX, &aniY, false); + + // Change in cursor position? + if (aniX != _ctx->prevcX || aniY != _ctx->prevcY || + Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) { + // kill current text objects + if (_ctx->cpText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpText); + } + if (_ctx->cpathText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpathText); + _ctx->cpathText = NULL; + } + + // New text objects + sprintf(PositionString, "%d %d", aniX + Loffset, aniY + Toffset); + _ctx->cpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, + 0, CPOSX, POSY, hTagFontHandle(), TXT_CENTRE); + if (DispPath) { + HPOLYGON hp = InPolygon(aniX + Loffset, aniY + Toffset, PATH); + if (hp == NOPOLY) + sprintf(PositionString, "No path"); + else + sprintf(PositionString, "%d,%d %d,%d %d,%d %d,%d", + PolyCornerX(hp, 0), PolyCornerY(hp, 0), + PolyCornerX(hp, 1), PolyCornerY(hp, 1), + PolyCornerX(hp, 2), PolyCornerY(hp, 2), + PolyCornerX(hp, 3), PolyCornerY(hp, 3)); + _ctx->cpathText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, + 0, 4, POSY+ 10, hTagFontHandle(), 0); + } + + // update previous position + _ctx->prevcX = aniX; + _ctx->prevcY = aniY; + } + +#if 0 + /*------------------------*\ + | Overrun counter display. | + \*------------------------*/ + if (Overrun != _ctx->prevOver) { + // kill current text objects + if (_ctx->opText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->opText); + } + + sprintf(PositionString, "%d", Overrun); + _ctx->opText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, + 0, OPOSX, POSY, hTagFontHandle(), TXT_CENTRE); + + // update previous value + _ctx->prevOver = Overrun; + } +#endif + + /*----------------------*\ + | Lead actor's position. | + \*----------------------*/ + pActor = GetMover(LEAD_ACTOR); + if (pActor && pActor->MActorState == NORM_MACTOR) { + // get lead's animation position + GetActorPos(LEAD_ACTOR, &aniX, &aniY); + + // Change in position? + if (aniX != _ctx->prevlX || aniY != _ctx->prevlY || + Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) { + // Kill current text objects + if (_ctx->rpText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->rpText); + } + + // create new text object list + sprintf(PositionString, "%d %d", aniX, aniY); + _ctx->rpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, + 0, LPOSX, POSY, hTagFontHandle(), TXT_CENTRE); + + // update previous position + _ctx->prevlX = aniX; + _ctx->prevlY = aniY; + } + } + + /*-------------*\ + | String number | + \*-------------*/ + if (bShowString && newestString != _ctx->prevString) { + // kill current text objects + if (_ctx->spText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->spText); + } + + sprintf(PositionString, "String: %d", newestString); + _ctx->spText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString, + 0, SPOSX, POSY+10, hTalkFontHandle(), TXT_CENTRE); + + // update previous value + _ctx->prevString = newestString; + } + + // update previous playfield position + _ctx->prevsX = Loffset; + _ctx->prevsY = Toffset; + + CORO_SLEEP(1); // allow re-scheduling + } + CORO_END_CODE; +} +#endif + +/** + * Tag process keeps us updated as to which tagged actor is currently tagged + * (if one is). Tag process asks us for this information, as does User_Event(). + */ +static void SaveTaggedActor(int ano) { + TaggedActor = ano; +} + +/** + * Tag process keeps us updated as to which tagged actor is currently tagged + * (if one is). Tag process asks us for this information, as does User_Event(). + */ +int GetTaggedActor(void) { + return TaggedActor; +} + +/** + * Tag process keeps us updated as to which polygon is currently tagged + * (if one is). Tag process asks us for this information, as does User_Event(). + */ +static void SaveTaggedPoly(HPOLYGON hp) { + hTaggedPolygon = hp; +} + +HPOLYGON GetTaggedPoly(void) { + return hTaggedPolygon; +} + +/** + * Given cursor position and an actor number, ascertains whether the + * cursor is within the actor's tag area. + * Returns TRUE for a positive result, FALSE for negative. + * If TRUE, the mid-top co-ordinates of the actor's tag area are also + * returned. + */ +static bool InHotSpot(int ano, int aniX, int aniY, int *pxtext, int *pytext) { + int Top, Bot; // Top and bottom limits of active area + int left, right; // left and right of active area + int qrt = 0; // 1/4 of height (sometimes 1/2) + + // First check if within x-range + if (aniX > (left = GetActorLeft(ano)) && aniX < (right = GetActorRight(ano))) { + Top = GetActorTop(ano); + Bot = GetActorBottom(ano); + + // y-range varies according to tag-type + switch (TagType(ano)) { + case TAG_DEF: + // Next to bottom 1/4 of the actor's area + qrt = (Bot - Top) >> 1; // Half actor's height + Top += qrt; // Top = mid-height + + qrt = qrt >> 1; // Quarter height + Bot -= qrt; // Bot = 1/4 way up + break; + + case TAG_Q1TO3: + // Top 3/4 of the actor's area + qrt = (Bot - Top) >> 2; // 1/4 actor's height + Bot -= qrt; // Bot = 1/4 way up + break; + + case TAG_Q1TO4: + // All the actor's area + break; + + default: + error("illegal tag area type"); + } + + // Now check if within y-range + if (aniY >= Top && aniY <= Bot) { + if (TagType(ano) == TAG_Q1TO3) + *pytext = Top + qrt; + else + *pytext = Top; + *pxtext = (left + right) / 2; + return true; + } + } + return false; +} + +/** + * See if the cursor is over a tagged actor's hot-spot. If so, display + * the tag or, if tag already displayed, maintain the tag's position on + * the screen. + */ +static bool ActorTag(int curX, int curY, SCNHANDLE *pTag, OBJECT **ppText) { + static int Loffset = 0, Toffset = 0; // Values when tag was displayed + int nLoff, nToff; // new values, to keep tag in place + int ano; + int xtext, ytext; + bool newActor; + + // For each actor with a tag.... + FirstTaggedActor(); + while ((ano = NextTaggedActor()) != 0) { + if (InHotSpot(ano, curX, curY, &xtext, &ytext)) { + // Put up or maintain actor tag + if (*pTag != ACTOR_TAG) + newActor = true; + else if (ano != GetTaggedActor()) + newActor = true; // Different actor + else + newActor = false; // Same actor + + if (newActor) { + // Display actor's tag + + if (*ppText) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText); + + *pTag = ACTOR_TAG; + SaveTaggedActor(ano); // This actor tagged + SaveTaggedPoly(NOPOLY); // No tagged polygon + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + LoadStringRes(GetActorTag(ano), tBufferAddr(), TBUFSZ); + *ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), + 0, xtext - Loffset, ytext - Toffset, hTagFontHandle(), TXT_CENTRE); + assert(*ppText); // Actor tag string produced NULL text + MultiSetZPosition(*ppText, Z_TAG_TEXT); + } else { + // Maintain actor tag's position + + PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff); + if (nLoff != Loffset || nToff != Toffset) { + MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff); + Loffset = nLoff; + Toffset = nToff; + } + } + return true; + } + } + + // No tagged actor + if (*pTag == ACTOR_TAG) { + *pTag = 0; + SaveTaggedActor(0); + } + return false; +} + +/** + * Perhaps some comment in due course. + * + * Under control of PointProcess(), when the cursor is over a TAG or + * EXIT polygon, its pointState flag is set to POINTING. If its Glitter + * code contains a printtag() call, its tagState flag gets set to TAG_ON. + */ +static bool PolyTag(SCNHANDLE *pTag, OBJECT **ppText) { + static int Loffset = 0, Toffset = 0; // Values when tag was displayed + int nLoff, nToff; // new values, to keep tag in place + HPOLYGON hp; + bool newPoly; + int shift; + + int tagx, tagy; // Tag display co-ordinates + SCNHANDLE hTagtext; // Tag text + + // For each polgon with a tag.... + for (int i = 0; i < MAX_POLY; i++) { + hp = GetPolyHandle(i); + + // Added code for un-tagged tags + if (hp != NOPOLY && PolyPointState(hp) == POINTING && PolyTagState(hp) != TAG_ON) { + // This poly is entitled to be tagged + if (hp != GetTaggedPoly()) { + if (*ppText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText); + *ppText = NULL; + } + *pTag = POLY_TAG; + SaveTaggedActor(0); // No tagged actor + SaveTaggedPoly(hp); // This polygon tagged + } + return true; + } else if (hp != NOPOLY && PolyTagState(hp) == TAG_ON) { + // Put up or maintain polygon tag + if (*pTag != POLY_TAG) + newPoly = true; // A new polygon (no current) + else if (hp != GetTaggedPoly()) + newPoly = true; // Different polygon + else + newPoly = false; // Same polygon + + if (newPoly) { + if (*ppText) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText); + + *pTag = POLY_TAG; + SaveTaggedActor(0); // No tagged actor + SaveTaggedPoly(hp); // This polygon tagged + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + getPolyTagInfo(hp, &hTagtext, &tagx, &tagy); + + int strLen; + if (PolyTagHandle(hp) != 0) + strLen = LoadStringRes(PolyTagHandle(hp), tBufferAddr(), TBUFSZ); + else + strLen = LoadStringRes(hTagtext, tBufferAddr(), TBUFSZ); + + if (strLen == 0) + // No valid string returned, so leave ppText as NULL + ppText = NULL; + else { + // Handle displaying the tag text on-screen + *ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), + 0, tagx - Loffset, tagy - Toffset, + hTagFontHandle(), TXT_CENTRE); + assert(*ppText); // Polygon tag string produced NULL text + MultiSetZPosition(*ppText, Z_TAG_TEXT); + + + /* + * New feature: Don't go off the side of the background + */ + shift = MultiRightmost(*ppText) + Loffset + 2; + if (shift >= BackgroundWidth()) // Not off right + MultiMoveRelXY(*ppText, BackgroundWidth() - shift, 0); + shift = MultiLeftmost(*ppText) + Loffset - 1; + if (shift <= 0) // Not off left + MultiMoveRelXY(*ppText, -shift, 0); + shift = MultiLowest(*ppText) + Toffset; + if (shift > BackgroundHeight()) // Not off bottom + MultiMoveRelXY(*ppText, 0, BackgroundHeight() - shift); + } + } else { + PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff); + if (nLoff != Loffset || nToff != Toffset) { + MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff); + Loffset = nLoff; + Toffset = nToff; + } + } + return true; + } + } + + // No tagged polygon + if (*pTag == POLY_TAG) { + *pTag = 0; + SaveTaggedPoly(NOPOLY); + } + return false; +} + +/** + * Handle display of tagged actor and polygon tags. + * Tagged actor's get priority over polygons. + */ +void TagProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + OBJECT *pText; // text object pointer + SCNHANDLE Tag; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->pText = NULL; + _ctx->Tag = 0; + + SaveTaggedActor(0); // No tagged actor yet + SaveTaggedPoly(NOPOLY); // No tagged polygon yet + + while (1) { + if (TagsActive == TAGS_ON) { + int curX, curY; // cursor position + while (!GetCursorXYNoWait(&curX, &curY, true)) + CORO_SLEEP(1); + + if (!ActorTag(curX, curY, &_ctx->Tag, &_ctx->pText) + && !PolyTag(&_ctx->Tag, &_ctx->pText)) { + // Nothing tagged. Remove tag, if there is one + if (_ctx->pText) { + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + _ctx->pText = NULL; + } + } + } else { + SaveTaggedActor(0); + SaveTaggedPoly(NOPOLY); + + // Remove tag, if there is one + if (_ctx->pText) { + // kill current text objects + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + _ctx->pText = NULL; + _ctx->Tag = 0; + } + } + + CORO_SLEEP(1); // allow re-scheduling + } + + CORO_END_CODE; +} + +/** + * Called from PointProcess() as appropriate. + */ +static void enteringpoly(HPOLYGON hp) { + SetPolyPointState(hp, POINTING); + + RunPolyTinselCode(hp, POINTED, BE_NONE, false); +} + +/** + * Called from PointProcess() as appropriate. + */ +static void leavingpoly(HPOLYGON hp) { + SetPolyPointState(hp, NOT_POINTING); + + if (PolyTagState(hp) == TAG_ON) { + // Delete this tag entry + SetPolyTagState(hp, TAG_OFF); + } +} + +/** + * For TAG and EXIT polygons, monitor cursor entering and leaving. + * Maintain the polygons' pointState and tagState flags accordingly. + * Also run the polygon's Glitter code when the cursor enters. + */ +void PointProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + while (1) { + int aniX, aniY; // cursor/tagged actor position + while (!GetCursorXYNoWait(&aniX, &aniY, true)) + CORO_SLEEP(1); + + /*----------------------------------*\ + | For polygons of type TAG and EXIT. | + \*----------------------------------*/ + for (int i = 0; i < MAX_POLY; i++) { + HPOLYGON hp = GetPolyHandle(i); + + if (hp != NOPOLY && (PolyType(hp) == TAG || PolyType(hp) == EXIT)) { + if (PolyPointState(hp) == NOT_POINTING) { + if (IsInPolygon(aniX, aniY, hp)) { + enteringpoly(hp); + } + } else if (PolyPointState(hp) == POINTING) { + if (!IsInPolygon(aniX, aniY, hp)) { + leavingpoly(hp); + } + } + } + } + + // allow re-scheduling + CORO_SLEEP(1); + } + + CORO_END_CODE; +} + +void DisableTags(void) { + TagsActive = TAGS_OFF; +} + +void EnableTags(void) { + TagsActive = TAGS_ON; +} + +bool DisableTagsIfEnabled(void) { + if (TagsActive == TAGS_OFF) + return false; + else { + TagsActive = TAGS_OFF; + return true; + } +} + +/** + * For testing purposes only. + * Causes CursorPositionProcess() to display, or not, the path that the + * cursor is in. + */ +void TogglePathDisplay(void) { + DispPath ^= 1; // Toggle path display (XOR with true) +} + + +void setshowstring(void) { + bShowString = true; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/pid.h b/engines/tinsel/pid.h new file mode 100644 index 0000000000..c2af1a5fcb --- /dev/null +++ b/engines/tinsel/pid.h @@ -0,0 +1,72 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * List of all process identifiers + */ + +#ifndef TINSEL_PID_H // prevent multiple includes +#define TINSEL_PID_H + +namespace Tinsel { + +#define PID_DESTROY 0x8000 // process id of any process that is to be destroyed between scenes + +#define PID_EFFECTS (0x0010 | PID_DESTROY) // generic special effects process id +#define PID_FLASH (PID_EFFECTS + 1) // flash colour process +#define PID_CYCLE (PID_EFFECTS + 2) // cycle colour range process +#define PID_MORPH (PID_EFFECTS + 3) // morph process +#define PID_FADER (PID_EFFECTS + 4) // fader process +#define PID_FADE_BGND (PID_EFFECTS + 5) // fade background colour process + +#define PID_BACKGND (0x0020 | PID_DESTROY) // background update process id + +#define PID_MOUSE 0x0030 // mouse button checking process id + +#define PID_JOYSTICK 0x0040 // joystick button checking process id + +#define PID_KEYBOARD 0x0050 // keyboard scanning process + +#define PID_CURSOR 0x0060 // cursor process +#define PID_CUR_TRAIL (PID_CURSOR + 1) // cursor trail process + +#define PID_SCROLL (0x0070 | PID_DESTROY) // scroll process + +#define PID_INVENTORY 0x0080 // inventory process + +#define PID_POSITION (0x0090 | PID_DESTROY) // cursor position process + +#define PID_TAG (0x00A0 | PID_DESTROY) // tag process + +#define PID_TCODE (0x00B0 | PID_DESTROY) // tinsel code process + +#define PID_MASTER_SCR 0x00C0 // tinsel master script process + +#define PID_MACTOR (0x00D0 | PID_DESTROY) // moving actor process + +#define PID_REEL (0x00E0 | PID_DESTROY) // process for each film reel + +#define PID_MIDI (0x00F0 | PID_DESTROY) // process to poll MIDI sound driver + +} // end of namespace Tinsel + +#endif // TINSEL_PID_H diff --git a/engines/tinsel/play.cpp b/engines/tinsel/play.cpp new file mode 100644 index 0000000000..6daf851fd2 --- /dev/null +++ b/engines/tinsel/play.cpp @@ -0,0 +1,507 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Plays films within a scene, takes into account the actor in each 'column'. | + */ + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/handle.h" +#include "tinsel/multiobj.h" +#include "tinsel/object.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/timers.h" +#include "tinsel/tinlib.h" // stand() + +namespace Tinsel { + +/** + * Poke the background palette into an image. + */ +static void PokeInPalette(SCNHANDLE hMulFrame) { + const FRAME *pFrame; // Pointer to frame + PIMAGE pim; // Pointer to image + + // Could be an empty column + if (hMulFrame) { + pFrame = (const FRAME *)LockMem(hMulFrame); + + // get pointer to image + pim = (PIMAGE)LockMem(READ_LE_UINT32(pFrame)); // handle to image + + pim->hImgPal = TO_LE_32(BackPal()); + } +} + + +int32 NoNameFunc(int actorID, bool bNewMover) { + PMACTOR pActor; + int32 retval; + + pActor = GetMover(actorID); + + if (pActor != NULL && !bNewMover) { + // If no path, just use first path in the scene + if (pActor->hCpath == NOPOLY) + retval = getPolyZfactor(FirstPathPoly()); + else + retval = getPolyZfactor(pActor->hCpath); + } else { + switch (actorMaskType(actorID)) { + case ACT_DEFAULT: + retval = 0; + break; + case ACT_MASK: + retval = 0; + break; + case ACT_ALWAYS: + retval = 10; + break; + default: + retval = actorMaskType(actorID); + break; + } + } + + return retval; +} + +struct PPINIT { + SCNHANDLE hFilm; // The 'film' + short x; // } Co-ordinates from the play() + short y; // } - set to (-1, -1) if none. + short z; // normally 0, set if from restore + short speed; // Film speed + short actorid; // Set if called from an actor code block + bool splay; // Set if called from splay() + bool bTop; // Set if called from topplay() + short sf; // SlowFactor - only used for moving actors + short column; // Column number, first column = 0 + + bool escOn; + int myescEvent; +}; + + +/** + * - Don't bother if this reel is already playing for this actor. + * - If explicit co-ordinates, use these, If embedded co-ordinates, + * leave alone, otherwise use actor's current position. + * - Moving actors get hidden during this play, other actors get + * _ctx->replaced by this play. + * - Column 0 of a film gets its appropriate Z-position, slave columns + * get slightly bigger Z-positions, in column order. + * - Play proceeds until the script finishes, another reel starts up for + * this actor, or the actor gets killed. + * - If called from an splay(), moving actor's co-ordinates are updated + * after the play, any walk still in progress will go on from there. + */ +void PlayReel(CORO_PARAM, const PPINIT *ppi) { + CORO_BEGIN_CONTEXT; + OBJECT *pPlayObj; // Object + ANIM thisAnim; // Animation structure + + bool mActor; // Gets set if this is a moving actor + bool lifeNoMatter; + bool replaced; + + const FREEL *pfreel; // The 'column' to play + int stepCount; + int frameCount; + int reelActor; + CORO_END_CONTEXT(_ctx); + + static int firstColZ = 0; // Z-position of column zero + static int32 fColZfactor = 0; // Z-factor of column zero's actor + + CORO_BEGIN_CODE(_ctx); + + const MULTI_INIT *pmi; // MULTI_INIT structure + PMACTOR pActor; + bool bNewMover; // Gets set if a moving actor that isn't in scene yet + + const FILM *pfilm; + + _ctx->lifeNoMatter = false; + _ctx->replaced = false; + pActor = NULL; + bNewMover = false; + + pfilm = (const FILM *)LockMem(ppi->hFilm); + _ctx->pfreel = &pfilm->reels[ppi->column]; + + // Get the MULTI_INIT structure + pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(_ctx->pfreel->mobj)); + + // Save actor's ID + _ctx->reelActor = (int32)FROM_LE_32(pmi->mulID); + + /**** New (experimental? bit 5/1/95 ****/ + if (!actorAlive(_ctx->reelActor)) + return; + /**** Delete a bit down there if this stays ****/ + + updateActorEsc(_ctx->reelActor, ppi->escOn, ppi->myescEvent); + + // To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios + if (ppi->hFilm != getActorLatestFilm(_ctx->reelActor)) { + // This in not the last film scheduled for this actor + + // It may be the last non-talk film though + if (isActorTalking(_ctx->reelActor)) + setActorPlayFilm(_ctx->reelActor, ppi->hFilm); // Revert to this film after talk + + return; + } + if (isActorTalking(_ctx->reelActor)) { + // Note: will delete this and there'll be no need to store the talk film! + if (ppi->hFilm != getActorTalkFilm(_ctx->reelActor)) { + setActorPlayFilm(_ctx->reelActor, ppi->hFilm); // Revert to this film after talk + return; + } + } else { + setActorPlayFilm(_ctx->reelActor, ppi->hFilm); + } + + // If this reel is already playing for this actor, just forget it. + if (actorReel(_ctx->reelActor) == _ctx->pfreel) + return; + + // Poke in the background palette + PokeInPalette(FROM_LE_32(pmi->hMulFrame)); + + // Set up and insert the multi-object + _ctx->pPlayObj = MultiInitObject(pmi); + if (!ppi->bTop) + MultiInsertObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj); + else + MultiInsertObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj); + + // If co-ordinates are specified, use specified. + // Otherwise, use actor's position if there are not embedded co-ords. + // Add this first test for nth columns with offsets + // in plays with (x,y) + int tmpX, tmpY; + tmpX = ppi->x; + tmpY = ppi->y; + if (ppi->column != 0 && (pmi->mulX || pmi->mulY)) { + } else if (tmpX != -1 || tmpY != -1) { + MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY); + } else if (!pmi->mulX && !pmi->mulY) { + GetActorPos(_ctx->reelActor, &tmpX, &tmpY); + MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY); + } + + // If it's a moving actor, this hides the moving actor + // used to do this only if (actorid == 0) - I don't know why + _ctx->mActor = HideMovingActor(_ctx->reelActor, ppi->sf); + + // If it's a moving actor, get its MACTOR structure. + // If it isn't in the scene yet, get its task running - using + // stand() - to prevent a glitch at the end of the play. + if (_ctx->mActor) { + pActor = GetMover(_ctx->reelActor); + if (getMActorState(pActor) == NO_MACTOR) { + stand(_ctx->reelActor, MAGICX, MAGICY, 0); + bNewMover = true; + } + } + + // Register the fact that we're playing this for this actor + storeActorReel(_ctx->reelActor, _ctx->pfreel, ppi->hFilm, _ctx->pPlayObj, ppi->column, tmpX, tmpY); + + /**** Will get rid of this if the above is kept ****/ + // We may be temporarily resuscitating a dead actor + if (ppi->actorid == 0 && !actorAlive(_ctx->reelActor)) + _ctx->lifeNoMatter = true; + + InitStepAnimScript(&_ctx->thisAnim, _ctx->pPlayObj, FROM_LE_32(_ctx->pfreel->script), ppi->speed); + + // If first column, set Z position as per + // Otherwise, column 0's + column number + // N.B. It HAS been ensured that the first column gets here first + if (ppi->z != 0) { + MultiSetZPosition(_ctx->pPlayObj, ppi->z); + storeActorZpos(_ctx->reelActor, ppi->z); + } else if (ppi->bTop) { + if (ppi->column == 0) { + firstColZ = Z_TOPPLAY + actorMaskType(_ctx->reelActor); + MultiSetZPosition(_ctx->pPlayObj, firstColZ); + storeActorZpos(_ctx->reelActor, firstColZ); + } else { + MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column); + storeActorZpos(_ctx->reelActor, firstColZ + ppi->column); + } + } else if (ppi->column == 0) { + if (_ctx->mActor && !bNewMover) { + // If no path, just use first path in the scene + if (pActor->hCpath == NOPOLY) + fColZfactor = getPolyZfactor(FirstPathPoly()); + else + fColZfactor = getPolyZfactor(pActor->hCpath); + firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor); + } else { + switch (actorMaskType(_ctx->reelActor)) { + case ACT_DEFAULT: + fColZfactor = 0; + firstColZ = 2; + MultiSetZPosition(_ctx->pPlayObj, firstColZ); + break; + case ACT_MASK: + fColZfactor = 0; + firstColZ = MultiLowest(_ctx->pPlayObj); + MultiSetZPosition(_ctx->pPlayObj, firstColZ); + break; + case ACT_ALWAYS: + fColZfactor = 10; + firstColZ = 10000; + MultiSetZPosition(_ctx->pPlayObj, firstColZ); + break; + default: + fColZfactor = actorMaskType(_ctx->reelActor); + firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor); + if (firstColZ < 2) { + // This is an experiment! + firstColZ = 2; + MultiSetZPosition(_ctx->pPlayObj, firstColZ); + } + break; + } + } + storeActorZpos(_ctx->reelActor, firstColZ); + } else { + if (NoNameFunc(_ctx->reelActor, bNewMover) > fColZfactor) { + fColZfactor = NoNameFunc(_ctx->reelActor, bNewMover); + firstColZ = fColZfactor << 10; + } + MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column); + storeActorZpos(_ctx->reelActor, firstColZ + ppi->column); + } + + /* + * Play until the script finishes, + * another reel starts up for this actor, + * or the actor gets killed. + */ + _ctx->stepCount = 0; + _ctx->frameCount = 0; + do { + if (_ctx->stepCount++ == 0) { + _ctx->frameCount++; + storeActorSteps(_ctx->reelActor, _ctx->frameCount); + } + if (_ctx->stepCount == ppi->speed) + _ctx->stepCount = 0; + + if (StepAnimScript(&_ctx->thisAnim) == ScriptFinished) + break; + + int x, y; + GetAniPosition(_ctx->pPlayObj, &x, &y); + storeActorPos(_ctx->reelActor, x, y); + + CORO_SLEEP(1); + + if (actorReel(_ctx->reelActor) != _ctx->pfreel) { + _ctx->replaced = true; + break; + } + + if (actorEsc(_ctx->reelActor) && actorEev(_ctx->reelActor) != GetEscEvents()) + break; + + } while (_ctx->lifeNoMatter || actorAlive(_ctx->reelActor)); + + // Register the fact that we're NOT playing this for this actor + if (actorReel(_ctx->reelActor) == _ctx->pfreel) + storeActorReel(_ctx->reelActor, NULL, 0, NULL, 0, 0, 0); + + // Ditch the object + if (!ppi->bTop) + MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj); + else + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj); + + if (_ctx->mActor) { + if (!_ctx->replaced) + unHideMovingActor(_ctx->reelActor); // Restore moving actor + + // Update it's co-ordinates if this is an splay() + if (ppi->splay) + restoreMovement(_ctx->reelActor); + } + CORO_END_CODE; +} + +/** + * Run all animations that comprise the play film. + */ +static void playProcess(CORO_PARAM) { + // get the stuff copied to process when it was created + PPINIT *ppi = (PPINIT *)ProcessGetParamsSelf(); + + PlayReel(coroParam, ppi); +} + +// ******************************************************* + + +// To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios +void newestFilm(SCNHANDLE film, const FREEL *reel) { + const MULTI_INIT *pmi; // MULTI_INIT structure + + // Get the MULTI_INIT structure + pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(reel->mobj)); + + setActorLatestFilm((int32)FROM_LE_32(pmi->mulID), film); +} + +// ******************************************************* + +/** + * Start up a play process for each column in a film. + * + * NOTE: The processes are started in reverse order so that the first + * column's process kicks in first. + */ +void playFilm(SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, + int myescEvent, bool bTop) { + const FILM *pfilm = (const FILM *)LockMem(film); + PPINIT ppi; + + assert(film != 0); // Trying to play NULL film + + // Now allowed empty films! + if (pfilm->numreels == 0) + return; // Nothing to do! + + ppi.hFilm = film; + ppi.x = x; + ppi.y = y; + ppi.z = 0; + ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate)); + ppi.actorid = actorid; + ppi.splay = splay; + ppi.bTop = bTop; + ppi.sf = sfact; + ppi.escOn = escOn; + ppi.myescEvent = myescEvent; + + // Start display process for each reel in the film + for (int i = FROM_LE_32(pfilm->numreels) - 1; i >= 0; i--) { + newestFilm(film, &pfilm->reels[i]); + + ppi.column = i; + CoroutineInstall(PID_REEL, playProcess, &ppi, sizeof(ppi)); + } +} + +/** + * Start up a play process for each slave column in a film. + * Play the first column directly from the parent process. + */ +void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, + bool escOn, int myescEvent, bool bTop) { + CORO_BEGIN_CONTEXT; + PPINIT ppi; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + assert(film != 0); // Trying to play NULL film + const FILM *pfilm; + + pfilm = (const FILM *)LockMem(film); + + // Now allowed empty films! + if (pfilm->numreels == 0) + return; // Already played to completion! + + _ctx->ppi.hFilm = film; + _ctx->ppi.x = x; + _ctx->ppi.y = y; + _ctx->ppi.z = 0; + _ctx->ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate)); + _ctx->ppi.actorid = actorid; + _ctx->ppi.splay = splay; + _ctx->ppi.bTop = bTop; + _ctx->ppi.sf = sfact; + _ctx->ppi.escOn = escOn; + _ctx->ppi.myescEvent = myescEvent; + + // Start display process for each secondary reel in the film + for (int i = FROM_LE_32(pfilm->numreels) - 1; i > 0; i--) { + newestFilm(film, &pfilm->reels[i]); + + _ctx->ppi.column = i; + CoroutineInstall(PID_REEL, playProcess, &_ctx->ppi, sizeof(PPINIT)); + } + + newestFilm(film, &pfilm->reels[0]); + + _ctx->ppi.column = 0; + CORO_INVOKE_1(PlayReel, &_ctx->ppi); + + CORO_END_CODE; +} + +/** + * Start up a play process for a particular column in a film. + * + * NOTE: This is specifically for actors during a restore scene. + */ +void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y) { + const FILM *pfilm = (const FILM *)LockMem(film); + PPINIT ppi; + + ppi.hFilm = film; + ppi.x = x; + ppi.y = y; + ppi.z = z; + ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate)); + ppi.actorid = 0; + ppi.splay = false; + ppi.bTop = false; + ppi.sf = 0; + ppi.column = reelnum; + + // FIXME: The PlayReel play loop was previously breaking out, and then deleting objects, when + // returning to a scene because escOn and myescEvent were undefined. Need to make sure whether + // restored objects should have any particular combination of these two values + ppi.escOn = false; + ppi.myescEvent = GetEscEvents(); + + assert(pfilm->numreels); + + newestFilm(film, &pfilm->reels[reelnum]); + + // Start display process for the reel + CoroutineInstall(PID_REEL, playProcess, &ppi, sizeof(ppi)); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/polygons.cpp b/engines/tinsel/polygons.cpp new file mode 100644 index 0000000000..21047afcf5 --- /dev/null +++ b/engines/tinsel/polygons.cpp @@ -0,0 +1,1805 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + */ + +#include "tinsel/actors.h" +#include "tinsel/font.h" +#include "tinsel/handle.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/serializer.h" +#include "tinsel/token.h" + +#include "common/util.h" + +namespace Tinsel { + + +//----------------- LOCAL DEFINES -------------------- + +#define MAXONROUTE 40 + +typedef POLYGON *PPOLYGON; + + +#include "common/pack-start.h" // START STRUCT PACKING + +/** lineinfo struct - one per (node-1) in a node path */ +struct LINEINFO { + + int32 a; + int32 b; + int32 c; + + int32 a2; //!< a squared + int32 b2; //!< b squared + int32 a2pb2; //!< a squared + b squared + int32 ra2pb2; //!< root(a squared + b squared) + + int32 ab; + int32 ac; + int32 bc; +} PACKED_STRUCT; + +/** polygon struct - one per polygon */ +struct POLY { + int32 type; //!< type of polygon + int32 x[4], y[4]; // Polygon definition + + int32 tagx, tagy; // } For tagged polygons + SCNHANDLE hTagtext; // } i.e. EXIT, TAG, EFFECT + + int32 nodex, nodey; // EXIT, TAG, REFER + SCNHANDLE hFilm; //!< film reel handle for EXIT, TAG + + int32 reftype; //!< Type of REFER + + int32 id; // } EXIT and TAG + + int32 scale1, scale2; // } + int32 reel; // } PATH and NPATH + int32 zFactor; // } + + //The arrays now stored externally + int32 nodecount; //!<The number of nodes in this polygon + int32 pnodelistx,pnodelisty; //!<offset in chunk to this array if present + int32 plinelist; + + SCNHANDLE hScript; //!< handle of code segment for polygon events +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int MaxPolys = MAX_POLY; + +static POLYGON *Polys[MAX_POLY+1]; + +static PPOLYGON Polygons = 0; + +static SCNHANDLE pHandle = 0; // } Set at start of each scene +static int noofPolys = 0; // } + +static POLYGON extraBlock; // Used for dynamic blocking + +static int pathsOnRoute = 0; +static PPOLYGON RoutePaths[MAXONROUTE]; + +static PPOLYGON RouteEnd = 0; + +#ifdef DEBUG +int highestYet = 0; +#endif + + + +//----------------- LOCAL MACROS -------------------- + +// The str parameter is no longer used +#define CHECK_HP_OR(mvar, str) assert((mvar >= 0 && mvar <= noofPolys) || mvar == MAX_POLY); +#define CHECK_HP(mvar, str) assert(mvar >= 0 && mvar <= noofPolys); + +static HPOLYGON PolyIndex(PPOLYGON pp) { + for (int j = 0; j <= MAX_POLY; j++) { + if (Polys[j] == pp) + return j; + } + + error("PolyIndex(): polygon not found"); + return NOPOLY; +} + +/** + * Returns TRUE if the point is within the polygon supplied. + * + * Firstly, the point must be within the smallest imaginary rectangle + * which encloses the polygon. + * + * Then, from each corner of the polygon, if the point is within an + * imaginary rectangle enclosing the clockwise-going side from that + * corner, the gradient of a line from the corner to the point must be + * less than (or more negative than) the gradient of that side: + * + * If the corners' coordinates are designated (x1, y1) and (x2, y2), and + * the point in question's (xt, yt), then: + * gradient (x1,y1)->(x2,y2) > gradient (x1,y1)->(xt,yt) + * (y1-y2)/(x2-x1) > (y1-yt)/(xt-x1) + * (y1-y2)*(xt-x1) > (y1-yt)*(x2-x1) + * xt(y1-y2) -x1y1 + x1y2 > -yt(x2-x1) + y1x2 - x1y1 + * xt(y1-y2) + yt(x2-x1) > y1x2 - x1y2 + * + * If the point passed one of the four 'side tests', and failed none, + * then it must be within the polygon. If the point was not tested, it + * may be within the internal rectangle not covered by the above tests. + * + * Most polygons contain an internal rectangle which does not fall into + * any of the above side-related tests. Such a rectangle will always + * have two polygon corners above it and two corners to the left of it. + */ +bool IsInPolygon(int xt, int yt, HPOLYGON hp) { + PPOLYGON pp; + int i; + bool BeenTested = false; + int pl = 0, pa = 0; + + CHECK_HP_OR(hp, "Out of range polygon handle (1)"); + pp = Polys[hp]; + assert(pp != NULL); // Testing whether in a NULL polygon + + /* Is point within the external rectangle? */ + if (xt < pp->pleft || xt > pp->pright || yt < pp->ptop || yt > pp->pbottom) + return false; + + // For each corner/side + for (i = 0; i < 4; i++) { + // If within this side's 'testable' area + // i.e. within the width of the line in y direction of end of line + // or within the height of the line in x direction of end of line + if ((xt >= pp->lleft[i] && xt <= pp->lright[i] && ((yt > pp->cy[i]) == (pp->cy[(i+1)%4] > pp->cy[i]))) + || (yt >= pp->ltop[i] && yt <= pp->lbottom[i] && ((xt > pp->cx[i]) == (pp->cx[(i+1)%4] > pp->cx[i])))) { + if (((long)xt*pp->a[i] + (long)yt*pp->b[i]) < pp->c[i]) + return false; + else + BeenTested = true; + } + } + + if (BeenTested) { + // New dodgy code 29/12/94 + if (pp->polytype == BLOCKING) { + // For each corner/side + for (i = 0; i < 4; i++) { + // Pretend the corners of blocking polys are not in the poly. + if (xt == pp->cx[i] && yt == pp->cy[i]) + return false; + } + } + return true; + } else { + // Is point within the internal rectangle? + for (i = 0; i < 4; i++) { + if (pp->cx[i] < xt) + pl++; + if (pp->cy[i] < yt) + pa++; + } + + if (pa == 2 && pl == 2) + return true; + else + return false; + } +} + +/** + * Finds a polygon of the specified type containing the supplied point. + */ + +HPOLYGON InPolygon(int xt, int yt, PTYPE type) { + for (int j = 0; j <= MAX_POLY; j++) { + if (Polys[j] && Polys[j]->polytype == type) { + if (IsInPolygon(xt, yt, j)) + return j; + } + } + return NOPOLY; +} + +/** + * Given a blocking polygon, current co-ordinates of an actor, and the + * co-ordinates of where the actor is heading, works out which corner of + * the blocking polygon to head around. + */ + +void BlockingCorner(HPOLYGON hp, int *x, int *y, int tarx, int tary) { + PPOLYGON pp; + int i; + int xd, yd; // distance per axis + int ThisD, SmallestD = 1000; + int D1, D2; + int NearestToHere = 1000, NearestToTarget; + unsigned At = 10; // Corner already at + + int bcx[4], bcy[4]; // Bogus corners + + CHECK_HP_OR(hp, "Out of range polygon handle (2)"); + pp = Polys[hp]; + + // Work out a point outside each corner + for (i = 0; i < 4; i++) { + int next, prev; + + // X-direction + next = pp->cx[i] - pp->cx[(i+1)%4]; + prev = pp->cx[i] - pp->cx[(i+3)%4]; + if (next <= 0 && prev <= 0) + bcx[i] = pp->cx[i] - 4; // Both points to the right + else if (next >= 0 && prev >= 0) + bcx[i] = pp->cx[i] + 4; // Both points to the left + else + bcx[i] = pp->cx[i]; + + // Y-direction + next = pp->cy[i] - pp->cy[(i+1)%4]; + prev = pp->cy[i] - pp->cy[(i+3)%4]; + if (next <= 0 && prev <= 0) + bcy[i] = pp->cy[i] - 4; // Both points below + else if (next >= 0 && prev >= 0) + bcy[i] = pp->cy[i] + 4; // Both points above + else + bcy[i] = pp->cy[i]; + } + + // Find nearest corner to where we are, + // but not the one we're stood at. + + for (i = 0; i < 4; i++) { // For 4 corners +// ThisD = ABS(*x - pp->cx[i]) + ABS(*y - pp->cy[i]); + ThisD = ABS(*x - bcx[i]) + ABS(*y - bcy[i]); + if (ThisD < SmallestD) { + // Ignore this corner if it's not in a path + if (InPolygon(pp->cx[i], pp->cy[i], PATH) == NOPOLY || + InPolygon(bcx[i], bcy[i], PATH) == NOPOLY) + continue; + + // Are we stood at this corner? + if (ThisD > 4) { + // No - it's the nearest we've found yet. + NearestToHere = i; + SmallestD = ThisD; + } else { + // Stood at/next to this corner + At = i; + } + } + } + + // If we're not already at a corner, go to the nearest corner + + if (At == 10) { + // Not stood at a corner +// assert(NearestToHere != 1000); // At blocking corner, not found near corner! + // Better to give up than to assert fail! + if (NearestToHere == 1000) { + // Send it to where it is now + // i.e. leave x and y alone + } else { + *x = bcx[NearestToHere]; + *y = bcy[NearestToHere]; + } + } else { + // Already at a corner. Go to an adjacent corner. + // First, find out which adjacent corner is nearest the target. + xd = ABS(tarx - pp->cx[(At + 1) % 4]); + yd = ABS(tary - pp->cy[(At + 1) % 4]); + D1 = xd + yd; + xd = ABS(tarx - pp->cx[(At + 3) % 4]); + yd = ABS(tary - pp->cy[(At + 3) % 4]); + D2 = xd + yd; + NearestToTarget = (D2 > D1) ? (At + 1) % 4 : (At + 3) % 4; + if (NearestToTarget == NearestToHere) { + *x = bcx[NearestToHere]; + *y = bcy[NearestToHere]; + } else { + // Need to decide whether it's better to go to the nearest to + // here and then on to the target, or to the nearest to the + // target and on from there. + xd = ABS(pp->cx[At] - pp->cx[NearestToHere]); + D1 = xd; + xd = ABS(pp->cx[NearestToHere] - tarx); + D1 += xd; + + yd = ABS(pp->cy[At] - pp->cy[NearestToHere]); + D1 += yd; + yd = ABS(pp->cy[NearestToHere] - tary); + D1 += yd; + + xd = ABS(pp->cx[At] - pp->cx[NearestToTarget]); + D2 = xd; + xd = ABS(pp->cx[NearestToTarget] - tarx); + D2 += xd; + + yd = ABS(pp->cy[At] - pp->cy[NearestToTarget]); + D2 += yd; + yd = ABS(pp->cy[NearestToTarget] - tary); + D2 += yd; + + if (D2 > D1) { + *x = bcx[NearestToHere]; + *y = bcy[NearestToHere]; + } else { + *x = bcx[NearestToTarget]; + *y = bcy[NearestToTarget]; + } + } + } +} + + +/** + * Try do drop a perpendicular to each inter-node line from the point + * and remember the shortest (if any). + * Find which node is nearest to the point. + * The shortest of these gives the best point in the node path. +*/ +void FindBestPoint(HPOLYGON hp, int *x, int *y, int *pline) { + PPOLYGON pp; + + uint8 *pps; // Compiled polygon data + const POLY *ptp; // Compiled polygon data + int dropD; // length of perpendicular (i.e. distance of point from line) + int dropX, dropY; // (X, Y) where dropped perpendicular intersects the line + int d1, d2; // distance from perpendicular intersect to line's end nodes + int32 *nlistx, *nlisty; + + int shortestD = 10000; // Shortest distance found + int nearestL = -1; // Nearest line + int nearestN; // Nearest Node + + int h = *x; // For readability/conveniance + int k = *y; // - why aren't these #defines? + LINEINFO *llist; // Inter-node line structure + + CHECK_HP(hp, "Out of range polygon handle (3)"); + pp = Polys[hp]; + + // Pointer to polygon data + pps = LockMem(pHandle); // All polygons + ptp = (const POLY *)pps + pp->pIndex; // This polygon + + nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + llist = (LINEINFO *)(pps + (int)FROM_LE_32(ptp->plinelist)); + + // Look for fit of perpendicular to lines between nodes + for (int i = 0; i < (int)FROM_LE_32(ptp->nodecount) - 1; i++) { + const int32 a = (int)FROM_LE_32(llist[i].a); + const int32 b = (int)FROM_LE_32(llist[i].b); + const int32 c = (int)FROM_LE_32(llist[i].c); + +#if 1 + if (true) { + //printf("a %d, b %d, c %d, a^2+b^2 = %d\n", a, b, c, a*a+b*b); + + // TODO: If the comments of the LINEINFO struct are correct, then it contains mostly + // duplicate data, probably in an effort to safe CPU cycles. Even on the slowest devices + // we support, calculatin a product of two ints is not an issue. + // So we can just load & endian convert a,b,c, then replace stuff like + // (int)FROM_LE_32(line->ab) + // by simply a*b, which makes it easier to understand what the code does, too. + // Just in case there is some bugged data, I leave this code here for verifying it. + // Let's leave it in for some time. + // + // One bad thing: We use sqrt to compute a square root. Might not be a good idea, + // speed wise. Maybe we should take Vicent's fp_sqroot. But that's a problem for later. + + LINEINFO *line = &llist[i]; + int32 a2 = (int)FROM_LE_32(line->a2); //!< a squared + int32 b2 = (int)FROM_LE_32(line->b2); //!< b squared + int32 a2pb2 = (int)FROM_LE_32(line->a2pb2); //!< a squared + b squared + int32 ra2pb2 = (int)FROM_LE_32(line->ra2pb2); //!< root(a squared + b squared) + + int32 ab = (int)FROM_LE_32(line->ab); + int32 ac = (int)FROM_LE_32(line->ac); + int32 bc = (int)FROM_LE_32(line->bc); + + assert(a*a == a2); + assert(b*b == b2); + assert(a*b == ab); + assert(a*c == ac); + assert(b*c == bc); + + assert(a2pb2 == a*a + b*b); + assert(ra2pb2 == (int)sqrt((float)a*a + (float)b*b)); + } +#endif + + + if (a == 0 && b == 0) + continue; // Line is just a point! + + // X position of dropped perpendicular intersection with line + dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b); + + // X distances from intersection to end nodes + d1 = dropX - (int)FROM_LE_32(nlistx[i]); + d2 = dropX - (int)FROM_LE_32(nlistx[i+1]); + + // if both -ve or both +ve, no fit + if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0)) + continue; +//#if 0 + // Y position of sidweays perpendicular intersection with line + dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b); + + // Y distances from intersection to end nodes + d1 = dropY - (int)FROM_LE_32(nlisty[i]); + d2 = dropY - (int)FROM_LE_32(nlisty[i+1]); + + // if both -ve or both +ve, no fit + if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0)) + continue; +//#endif + dropD = ((a * h) + (b * k) + c) / (int)sqrt((float)a*a + (float)b*b); + dropD = ABS(dropD); + if (dropD < shortestD) { + shortestD = dropD; + nearestL = i; + } + } + + // Distance to nearest node + nearestN = NearestNodeWithin(hp, h, k); + dropD = ABS(h - (int)FROM_LE_32(nlistx[nearestN])) + ABS(k - (int)FROM_LE_32(nlisty[nearestN])); + + // Go to a node or a point on a line + if (dropD < shortestD) { + // A node is nearest + *x = (int)FROM_LE_32(nlistx[nearestN]); + *y = (int)FROM_LE_32(nlisty[nearestN]); + *pline = nearestN; + } else { + assert(nearestL != -1); + + // A point on a line is nearest + const int32 a = (int)FROM_LE_32(llist[nearestL].a); + const int32 b = (int)FROM_LE_32(llist[nearestL].b); + const int32 c = (int)FROM_LE_32(llist[nearestL].c); + dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b); + dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b); + *x = dropX; + *y = dropY; + *pline = nearestL; + } + + assert(IsInPolygon(*x, *y, hp)); // Nearest point is not in polygon(!) +} + +/** + * Returns TRUE if two paths are asdjacent. + */ +bool IsAdjacentPath(HPOLYGON hPath1, HPOLYGON hPath2) { + PPOLYGON pp1, pp2; + + CHECK_HP(hPath1, "Out of range polygon handle (4)"); + CHECK_HP(hPath2, "Out of range polygon handle (500)"); + + if (hPath1 == hPath2) + return true; + + pp1 = Polys[hPath1]; + pp2 = Polys[hPath2]; + + for (int j = 0; j < MAXADJ; j++) + if (pp1->adjpaths[j] == pp2) + return true; + + return false; +} + +static POLYGON *TryPath(POLYGON *last, POLYGON *whereto, POLYGON *current) { + PPOLYGON x; + + // For each path adjacent to this one + for (int j = 0; j < MAXADJ; j++) { + x = current->adjpaths[j]; // call the adj. path x + if (x == whereto) { + RoutePaths[pathsOnRoute++] = x; + return x; // Got there! + } + + if (x == NULL) + break; // no more adj. paths to look at + + if (x->tried) + continue; // don't double back + + if (x == last) + continue; // don't double back + + x->tried = true; + if (TryPath(current, whereto, x) != NULL) { + RoutePaths[pathsOnRoute++] = x; + assert(pathsOnRoute < MAXONROUTE); + return x; // Got there in this direction + } else + x->tried = false; + } + + return NULL; +} + + +/** + * Sort out the first path to head to for the imminent leg of a walk. + */ +static HPOLYGON PathOnTheWay(HPOLYGON from, HPOLYGON to) { + // TODO: Fingolfin says: This code currently uses DFS (depth first search), + // in the TryPath function, to compute a path between 'from' and 'to'. + // However, a BFS (breadth first search) might yield more natural results, + // at least in cases where there are multiple possible paths. + // There is a small risk of regressions caused by such a change, though. + // + // Also, the overhead of computing a DFS again and again could be avoided + // by computing a path matrix (like we do in the SCUMM engine). + int i; + PPOLYGON p; + + CHECK_HP(from, "Out of range polygon handle (501a)"); + CHECK_HP(to, "Out of range polygon handle (501b)"); + + if (IsAdjacentPath(from, to)) + return to; + + for (i = 0; i < MAX_POLY; i++) { // For each polygon.. + p = Polys[i]; + if (p && p->polytype == PATH) //...if it's a path + p->tried = false; + } + Polys[from]->tried = true; + pathsOnRoute = 0; + + p = TryPath(Polys[from], Polys[to], Polys[from]); + + assert(p != NULL); // Trying to find route between unconnected paths + + // Don't go a roundabout way to an adjacent path. + for (i = 0; i < pathsOnRoute; i++) { + CHECK_HP(PolyIndex(RoutePaths[i]), "Out of range polygon handle (502)"); + if (IsAdjacentPath(from, PolyIndex(RoutePaths[i]))) + return PolyIndex(RoutePaths[i]); + } + return PolyIndex(p); +} + +/** + * Indirect method of calling PathOnTheWay(), to put the burden of + * recursion onto the main stack. + */ +HPOLYGON getPathOnTheWay(HPOLYGON hFrom, HPOLYGON hTo) { + CHECK_HP(hFrom, "Out of range polygon handle (6)"); + CHECK_HP(hTo, "Out of range polygon handle (7)"); + + // Reuse already computed result + if (RouteEnd == Polys[hTo]) { + for (int i = 0; i < pathsOnRoute; i++) { + CHECK_HP(PolyIndex(RoutePaths[i]), "Out of range polygon handle (503)"); + if (IsAdjacentPath(hFrom, PolyIndex(RoutePaths[i]))) { + return PolyIndex(RoutePaths[i]); + } + } + } + + RouteEnd = Polys[hTo]; + return PathOnTheWay(hFrom, hTo); +} + + +/** + * Given a node path, work out which end node is nearest the given point. + */ + +int NearestEndNode(HPOLYGON hPath, int x, int y) { + PPOLYGON pp; + + int d1, d2; + uint8 *pps; // Compiled polygon data + const POLY *ptp; // Pointer to compiled polygon data + int32 *nlistx, *nlisty; + + CHECK_HP(hPath, "Out of range polygon handle (8)"); + pp = Polys[hPath]; + + pps = LockMem(pHandle); // All polygons + ptp = (const POLY *)pps + pp->pIndex; // This polygon + + nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + + const int nodecount = (int)FROM_LE_32(ptp->nodecount); + + d1 = ABS(x - (int)FROM_LE_32(nlistx[0])) + ABS(y - (int)FROM_LE_32(nlisty[0])); + d2 = ABS(x - (int)FROM_LE_32(nlistx[nodecount - 1])) + ABS(y - (int)FROM_LE_32(nlisty[nodecount - 1])); + + return (d2 > d1) ? 0 : nodecount - 1; +} + + +/** + * Given a start path and a destination path, find which pair of end + * nodes is nearest together. + * Return which node in the start path is part of the closest pair. + */ + +int NearEndNode(HPOLYGON hSpath, HPOLYGON hDpath) { + PPOLYGON pSpath, pDpath; + + int ns, nd; // 'top' nodes in each path + int dist, NearDist; + int NearNode; + uint8 *pps; // Compiled polygon data + const POLY *ps, *pd; // Pointer to compiled polygon data + int32 *snlistx, *snlisty; + int32 *dnlistx, *dnlisty; + + CHECK_HP(hSpath, "Out of range polygon handle (9)"); + CHECK_HP(hDpath, "Out of range polygon handle (10)"); + pSpath = Polys[hSpath]; + pDpath = Polys[hDpath]; + + pps = LockMem(pHandle); // All polygons + ps = (const POLY *)pps + pSpath->pIndex; // Start polygon + pd = (const POLY *)pps + pDpath->pIndex; // Dest polygon + + ns = (int)FROM_LE_32(ps->nodecount) - 1; + nd = (int)FROM_LE_32(pd->nodecount) - 1; + + snlistx = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelistx)); + snlisty = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelisty)); + dnlistx = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelistx)); + dnlisty = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelisty)); + + // start[0] to dest[0] + NearDist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[0])); + NearNode = 0; + + // start[0] to dest[top] + dist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[nd])); + if (dist < NearDist) + NearDist = dist; + + // start[top] to dest[0] + dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[0])); + if (dist < NearDist) { + NearDist = dist; + NearNode = ns; + } + + // start[top] to dest[top] + dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[nd])); + if (dist < NearDist) { + NearNode = ns; + } + + return NearNode; +} + +/** + * Given a follow nodes path and a co-ordinate, finds which node in the + * path is nearest to the co-ordinate. + */ +int NearestNodeWithin(HPOLYGON hNpath, int x, int y) { + int ThisDistance, SmallestDistance = 1000; + int NumNodes; // Number of nodes in this follow nodes path + int NearestYet = 0; // Number of nearest node + uint8 *pps; // Compiled polygon data + const POLY *ptp; // Pointer to compiled polygon data + int32 *nlistx, *nlisty; + + CHECK_HP(hNpath, "Out of range polygon handle (11)"); + + pps = LockMem(pHandle); // All polygons + ptp = (const POLY *)pps + Polys[hNpath]->pIndex; // This polygon + + nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + + NumNodes = (int)FROM_LE_32(ptp->nodecount); + + for (int i = 0; i < NumNodes; i++) { + ThisDistance = ABS(x - (int)FROM_LE_32(nlistx[i])) + ABS(y - (int)FROM_LE_32(nlisty[i])); + + if (ThisDistance < SmallestDistance) { + NearestYet = i; + SmallestDistance = ThisDistance; + } + } + + return NearestYet; +} + +/** + * Given a point and start and destination paths, find the nearest + * corner (if any) of the start path which is within the destination + * path. If there is no such corner, find the nearest corner of the + * destination path which falls within the source path. + */ +void NearestCorner(int *x, int *y, HPOLYGON hStartPoly, HPOLYGON hDestPoly) { + PPOLYGON psp, pdp; + int j; + int ncorn = 0; // nearest corner + HPOLYGON hNpath = NOPOLY; // path containing nearest corner + int ThisD, SmallestD = 1000; + + CHECK_HP(hStartPoly, "Out of range polygon handle (12)"); + CHECK_HP(hDestPoly, "Out of range polygon handle (13)"); + + psp = Polys[hStartPoly]; + pdp = Polys[hDestPoly]; + + // Nearest corner of start path in destination path. + + for (j = 0; j < 4; j++) { + if (IsInPolygon(psp->cx[j], psp->cy[j], hDestPoly)) { + ThisD = ABS(*x - psp->cx[j]) + ABS(*y - psp->cy[j]); + if (ThisD < SmallestD) { + hNpath = hStartPoly; + ncorn = j; + // Try to ignore it if virtually stood on it + if (ThisD > 4) + SmallestD = ThisD; + } + } + } + if (SmallestD == 1000) { + // Nearest corner of destination path in start path. + for (j = 0; j < 4; j++) { + if (IsInPolygon(pdp->cx[j], pdp->cy[j], hStartPoly)) { + ThisD = ABS(*x - pdp->cx[j]) + ABS(*y - pdp->cy[j]); + if (ThisD < SmallestD) { + hNpath = hDestPoly; + ncorn = j; + // Try to ignore it if virtually stood on it + if (ThisD > 4) + SmallestD = ThisD; + } + } + } + } + + if (hNpath != NOPOLY) { + *x = Polys[hNpath]->cx[ncorn]; + *y = Polys[hNpath]->cy[ncorn]; + } else + error("NearestCorner() failure"); +} + +bool IsPolyCorner(HPOLYGON hPath, int x, int y) { + CHECK_HP(hPath, "Out of range polygon handle (37)"); + + for (int i = 0; i < 4; i++) { + if (Polys[hPath]->cx[i] == x && Polys[hPath]->cy[i] == y) + return true; + } + return false; +} + +/** + * Given a path polygon and a Y co-ordinate, return a scale value. + */ +int GetScale(HPOLYGON hPath, int y) { + const POLY *ptp; // Pointer to compiled polygon data + int zones; // Number of different scales + int zlen; // Depth of each scale zone + int scale; + int top; + + // To try and fix some unknown potential bug + if (hPath == NOPOLY) + return SCALE_LARGE; + + CHECK_HP(hPath, "Out of range polygon handle (14)"); + + ptp = (const POLY *)LockMem(pHandle) + Polys[hPath]->pIndex; + + // Path is of a constant scale? + if (FROM_LE_32(ptp->scale2) == 0) + return FROM_LE_32(ptp->scale1); + + assert(FROM_LE_32(ptp->scale1) >= FROM_LE_32(ptp->scale2)); + + zones = FROM_LE_32(ptp->scale1) - FROM_LE_32(ptp->scale2) + 1; + zlen = (Polys[hPath]->pbottom - Polys[hPath]->ptop) / zones; + + scale = FROM_LE_32(ptp->scale1); + top = Polys[hPath]->ptop; + + do { + top += zlen; + if (y < top) + return scale; + } while (--scale); + + return FROM_LE_32(ptp->scale2); +} + +/** + * Give the co-ordinates of a node in a node path. + */ +void getNpathNode(HPOLYGON hNpath, int node, int *px, int *py) { + uint8 *pps; // Compiled polygon data + const POLY *ptp; // Pointer to compiled polygon data + int32 *nlistx, *nlisty; + + CHECK_HP(hNpath, "Out of range polygon handle (15)"); + assert(Polys[hNpath] != NULL && Polys[hNpath]->polytype == PATH && Polys[hNpath]->subtype == NODE); // must be given a node path! + + pps = LockMem(pHandle); // All polygons + ptp = (const POLY *)pps + Polys[hNpath]->pIndex; // This polygon + + nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty)); + + // Might have just walked to the node from above. + if (node == (int)FROM_LE_32(ptp->nodecount)) + node -= 1; + + *px = (int)FROM_LE_32(nlistx[node]); + *py = (int)FROM_LE_32(nlisty[node]); +} + +/** + * Get tag text handle and tag co-ordinates of a polygon. + */ + +void getPolyTagInfo(HPOLYGON hp, SCNHANDLE *hTagText, int *tagx, int *tagy) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (16)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + *tagx = (int)FROM_LE_32(pp->tagx); + *tagy = (int)FROM_LE_32(pp->tagy); + *hTagText = FROM_LE_32(pp->hTagtext); +} + +/** + * Get polygon's film reel handle. + */ + +SCNHANDLE getPolyFilm(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (17)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return FROM_LE_32(pp->hFilm); +} + +/** + * Get polygon's associated node. + */ + +void getPolyNode(HPOLYGON hp, int *px, int *py) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (18)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + *px = (int)FROM_LE_32(pp->nodex); + *py = (int)FROM_LE_32(pp->nodey); +} + +/** + * Get handle to polygon's glitter code. + */ + +SCNHANDLE getPolyScript(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (19)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return FROM_LE_32(pp->hScript); +} + +REEL getPolyReelType(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + // To try and fix some unknown potential bug (toyshop entrance) + if (hp == NOPOLY) + return REEL_ALL; + + CHECK_HP(hp, "Out of range polygon handle (20)"); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return (REEL)FROM_LE_32(pp->reel); +} + +int32 getPolyZfactor(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (21)"); + assert(Polys[hp] != NULL); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return (int)FROM_LE_32(pp->zFactor); +} + +int numNodes(HPOLYGON hp) { + const POLY *pp; // Pointer to compiled polygon data + + CHECK_HP(hp, "Out of range polygon handle (22)"); + assert(Polys[hp] != NULL); + + pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex; + + return (int)FROM_LE_32(pp->nodecount); +} + +// ************************************************************************* +// +// Code concerned with killing and reviving TAG and EXIT polygons. +// And code to enable this information to be saved and restored. +// +// ************************************************************************* + +struct TAGSTATE { + int tid; + bool enabled; +}; +typedef TAGSTATE *PTAGSTATE; + +#define MAX_SCENES 256 +#define MAX_TAGS 2048 +#define MAX_EXITS 512 + +static struct { + SCNHANDLE sid; + int nooftags; + int offset; +} SceneTags[MAX_SCENES], SceneExits[MAX_SCENES]; + +static TAGSTATE TagStates[MAX_TAGS]; +static TAGSTATE ExitStates[MAX_EXITS]; + +static int nextfreeT = 0, numScenesT = 0; +static int nextfreeE = 0, numScenesE = 0; + +static int currentTScene = 0; +static int currentEScene = 0; + +bool deadPolys[MAX_POLY]; // Currently just for dead blocks + +void RebootDeadTags(void) { + nextfreeT = numScenesT = 0; + nextfreeE = numScenesE = 0; + + memset(SceneTags, 0, sizeof(SceneTags)); + memset(SceneExits, 0, sizeof(SceneExits)); + memset(TagStates, 0, sizeof(TagStates)); + memset(ExitStates, 0, sizeof(ExitStates)); + memset(deadPolys, 0, sizeof(deadPolys)); +} + +/** + * (Un)serialize the dead tag and exit data for save/restore game. + */ +void syncPolyInfo(Serializer &s) { + int i; + + for (i = 0; i < MAX_SCENES; i++) { + s.syncAsUint32LE(SceneTags[i].sid); + s.syncAsSint32LE(SceneTags[i].nooftags); + s.syncAsSint32LE(SceneTags[i].offset); + } + + for (i = 0; i < MAX_SCENES; i++) { + s.syncAsUint32LE(SceneExits[i].sid); + s.syncAsSint32LE(SceneExits[i].nooftags); + s.syncAsSint32LE(SceneExits[i].offset); + } + + for (i = 0; i < MAX_TAGS; i++) { + s.syncAsUint32LE(TagStates[i].tid); + s.syncAsSint32LE(TagStates[i].enabled); + } + + for (i = 0; i < MAX_EXITS; i++) { + s.syncAsUint32LE(ExitStates[i].tid); + s.syncAsSint32LE(ExitStates[i].enabled); + } + + s.syncAsSint32LE(nextfreeT); + s.syncAsSint32LE(numScenesT); + s.syncAsSint32LE(nextfreeE); + s.syncAsSint32LE(numScenesE); +} + +/** + * This is all totally different to the way the rest of the way polygon + * data is stored and restored, more specifically, different to how dead + * tags and exits are handled, because of the piecemeal design-by-just- + * thought-of-this approach employed. + */ + +void SaveDeadPolys(bool *sdp) { + memcpy(sdp, deadPolys, MAX_POLY*sizeof(bool)); +} + +void RestoreDeadPolys(bool *sdp) { + memcpy(deadPolys, sdp, MAX_POLY*sizeof(bool)); +} + +/** + * Convert a BLOCKING to an EX_BLOCK poly. + */ +void DisableBlock(int blockno) { + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == BLOCKING && Polys[i]->polyID == blockno) { + Polys[i]->polytype = EX_BLOCK; + deadPolys[i] = true; + } + } +} + +/** + * Convert an EX_BLOCK to a BLOCKING poly. + */ +void EnableBlock(int blockno) { + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == EX_BLOCK && Polys[i]->polyID == blockno) { + Polys[i]->polytype = BLOCKING; + deadPolys[i] = false; + } + } +} + +/** + * Convert a TAG to an EX_TAG poly. + */ +void DisableTag(int tagno) { + PTAGSTATE pts; + + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == TAG && Polys[i]->polyID == tagno) { + Polys[i]->polytype = EX_TAG; + Polys[i]->tagState = TAG_OFF; + Polys[i]->pointState = NOT_POINTING; + } + } + + pts = &TagStates[SceneTags[currentTScene].offset]; + for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) { + if (pts->tid == tagno) { + pts->enabled = false; + break; + } + } +} + +/** + * Convert an EX_TAG to a TAG poly. + */ +void EnableTag(int tagno) { + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == EX_TAG && Polys[i]->polyID == tagno) { + Polys[i]->polytype = TAG; + } + } + + PTAGSTATE pts; + pts = &TagStates[SceneTags[currentTScene].offset]; + for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) { + if (pts->tid == tagno) { + pts->enabled = true; + break; + } + } +} + +/** + * Convert an EX_EXIT to a EXIT poly. + */ +void EnableExit(int exitno) { + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == EX_EXIT && Polys[i]->polyID == exitno) { + Polys[i]->polytype = EXIT; + } + } + + PTAGSTATE pts; + pts = &ExitStates[SceneExits[currentEScene].offset]; + for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) { + if (pts->tid == exitno) { + pts->enabled = true; + break; + } + } +} + +/** + * Convert a EXIT to an EX_EXIT poly. + */ +void DisableExit(int exitno) { + PTAGSTATE pts; + + for (int i = 0; i < MAX_POLY; i++) { + if (Polys[i] && Polys[i]->polytype == EXIT && Polys[i]->polyID == exitno) { + Polys[i]->polytype = EX_EXIT; + Polys[i]->tagState = TAG_OFF; + Polys[i]->pointState = NOT_POINTING; + } + } + + pts = &ExitStates[SceneExits[currentEScene].offset]; + for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) { + if (pts->tid == exitno) { + pts->enabled = false; + break; + } + } +} + +HPOLYGON FirstPathPoly(void) { + for (int i = 0; i < noofPolys; i++) { + if (Polys[i]->polytype == PATH) + return i; + } + error("FirstPathPoly() - no PATH polygons!"); + return NOPOLY; +} + +HPOLYGON GetPolyHandle(int i) { + assert(i >= 0 && i <= MAX_POLY); + + return (Polys[i] != NULL) ? i : NOPOLY; +} + +// ************************************************************************** +// +// Code called to initialise or wrap up a scene: +// +// ************************************************************************** + +/** + * Called at the start of a scene, when all polygons have been + * initialised, to work out which paths are adjacent to which. + */ +static int DistinctCorners(HPOLYGON hp1, HPOLYGON hp2) { + PPOLYGON pp1, pp2; + int i, j; + int retval = 0; + + CHECK_HP(hp1, "Out of range polygon handle (23)"); + CHECK_HP(hp2, "Out of range polygon handle (24)"); + pp1 = Polys[hp1]; + pp2 = Polys[hp2]; + + // Work out (how many of p1's corners is in p2) + (how many of p2's corners is in p1) + for (i = 0; i < 4; i++) { + if (IsInPolygon(pp1->cx[i], pp1->cy[i], hp2)) + retval++; + if (IsInPolygon(pp2->cx[i], pp2->cy[i], hp1)) + retval++; + } + + // Common corners only count once + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + if (pp1->cx[i] == pp2->cx[j] && pp1->cy[i] == pp2->cy[j]) + retval--; + } + } + return retval; +} + +static void SetPathAdjacencies() { + PPOLYGON p1, p2; // Polygon pointers + + // For each polygon.. + for (int i1 = 0; i1 < MAX_POLY-1; i1++) { + // Get polygon, but only carry on if it's a path + p1 = Polys[i1]; + if (!p1 || p1->polytype != PATH) + continue; + + // For each subsequent polygon.. + for (int i2 = i1 + 1; i2 < MAX_POLY; i2++) { + // Get polygon, but only carry on if it's a path + p2 = Polys[i2]; + if (!p2 || p2->polytype != PATH) + continue; + + int j = DistinctCorners(i1, i2); + + if (j >= 2) { + // Paths are adjacent + for (j = 0; j < MAXADJ; j++) + if (p1->adjpaths[j] == NULL) { + p1->adjpaths[j] = p2; + break; + } +#ifdef DEBUG + if (j > highestYet) + highestYet = j; +#endif + assert(j < MAXADJ); // Number of adjacent paths limit + for (j = 0; j < MAXADJ; j++) { + if (p2->adjpaths[j] == NULL) { + p2->adjpaths[j] = p1; + break; + } + } +#ifdef DEBUG + if (j > highestYet) + highestYet = j; +#endif + assert(j < MAXADJ); // Number of adjacent paths limit + } + } + } +} + +/** + * Ensure NPATH nodes are not inside another PATH/NPATH polygon. + * Only bother with end nodes for now. + */ +#ifdef DEBUG +void CheckNPathIntegrity() { + uint8 *pps; // Compiled polygon data + PPOLYGON rp; // Run-time polygon structure + HPOLYGON hp; + const POLY *cp; // Compiled polygon structure + int i, j; // Loop counters + int n; // Last node in current path + int32 *nlistx, *nlisty; + + pps = LockMem(pHandle); // All polygons + + for (i = 0; i < MAX_POLY; i++) { // For each polygon.. + rp = Polys[i]; + if (rp && rp->polytype == PATH && rp->subtype == NODE) { //...if it's a node path + // Get compiled polygon structure + cp = (const POLY *)pps + rp->pIndex; // This polygon + nlistx = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelistx)); + nlisty = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelisty)); + + n = (int)FROM_LE_32(cp->nodecount) - 1; // Last node + assert(n >= 1); // Node paths must have at least 2 nodes + + hp = PolyIndex(rp); + for (j = 0; j <= n; j++) { + if (!IsInPolygon((int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), hp)) { + sprintf(tBufferAddr(), "Node (%d, %d) is not in its own path (starting (%d, %d))", + (int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), rp->cx[0], rp->cy[0]); + error(tBufferAddr()); + } + } + + // Check end nodes are not in adjacent path + for (j = 0; j < MAXADJ; j++) { // For each adjacent path + if (rp->adjpaths[j] == NULL) + break; + + if (IsInPolygon((int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), PolyIndex(rp->adjpaths[j]))) { + sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))", + (int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]); + error(tBufferAddr()) + } + if (IsInPolygon((int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), PolyIndex(rp->adjpaths[j]))) { + sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))", + (int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]); + error(tBufferAddr()) + } + } + } + } +} +#endif + +/** + * Called at the start of a scene, nobbles TAG polygons which should be dead. + */ +static void SetExBlocks() { + for (int i = 0; i < MAX_POLY; i++) { + if (deadPolys[i]) { + if (Polys[i] && Polys[i]->polytype == BLOCKING) + Polys[i]->polytype = EX_BLOCK; +#ifdef DEBUG + else + error("Impossible message!"); +#endif + } + } +} + +/** + * Called at the start of a scene, nobbles TAG polygons which should be dead. + */ +static void SetExTags(SCNHANDLE ph) { + PTAGSTATE pts; + int i, j; + + for (i = 0; i < numScenesT; i++) { + if (SceneTags[i].sid == ph) { + currentTScene = i; + + pts = &TagStates[SceneTags[i].offset]; + for (j = 0; j < SceneTags[i].nooftags; j++, pts++) { + if (!pts->enabled) + DisableTag(pts->tid); + } + return; + } + } + i = numScenesT++; + currentTScene = i; + assert(numScenesT < MAX_SCENES); // Dead tag remembering: scene limit + + SceneTags[i].sid = ph; + SceneTags[i].offset = nextfreeT; + SceneTags[i].nooftags = 0; + + for (j = 0; j < MAX_POLY; j++) { + if (Polys[j] && Polys[j]->polytype == TAG) { + TagStates[nextfreeT].tid = Polys[j]->polyID; + TagStates[nextfreeT].enabled = true; + nextfreeT++; + assert(nextfreeT < MAX_TAGS); // Dead tag remembering: tag limit + SceneTags[i].nooftags++; + } + } +} + +/** + * Called at the start of a scene, nobbles EXIT polygons which should be dead. + */ +#ifdef DEBUG +void SetExExits(SCNHANDLE ph) { +#else +static void SetExExits(SCNHANDLE ph) { +#endif + PTAGSTATE pts; + int i, j; + + for (i = 0; i < numScenesE; i++) { + if (SceneExits[i].sid == ph) { + currentEScene = i; + + pts = &ExitStates[SceneExits[i].offset]; + for (j = 0; j < SceneExits[i].nooftags; j++, pts++) { + if (!pts->enabled) + DisableExit(pts->tid); + } + return; + } + } + + i = numScenesE++; + currentEScene = i; + assert(numScenesE < MAX_SCENES); // Dead exit remembering: scene limit + + SceneExits[i].sid = ph; + SceneExits[i].offset = nextfreeE; + SceneExits[i].nooftags = 0; + + for (j = 0; j < MAX_POLY; j++) { + if (Polys[j] && Polys[j]->polytype == EXIT) { + ExitStates[nextfreeE].tid = Polys[j]->polyID; + ExitStates[nextfreeE].enabled = true; + nextfreeE++; + assert(nextfreeE < MAX_EXITS); // Dead exit remembering: exit limit + SceneExits[i].nooftags++; + } + } +} + +/** + * Works out some fixed numbers for a polygon. + */ +static void FiddlyBit(POLYGON *p) { + int t1, t2; // General purpose temp. variables + + // Enclosing external rectangle + t1 = p->cx[0] > p->cx[1] ? p->cx[0] : p->cx[1]; + t2 = p->cx[2] > p->cx[3] ? p->cx[2] : p->cx[3]; + p->pright = (short)(t1 > t2 ? t1 : t2); + + t1 = p->cx[0] < p->cx[1] ? p->cx[0] : p->cx[1]; + t2 = p->cx[2] < p->cx[3] ? p->cx[2] : p->cx[3]; + p->pleft = (short)(t1 < t2 ? t1 : t2); + + t1 = p->cy[0] > p->cy[1] ? p->cy[0] : p->cy[1]; + t2 = p->cy[2] > p->cy[3] ? p->cy[2] : p->cy[3]; + p->pbottom = (short)(t1 > t2 ? t1 : t2); + + t1 = p->cy[0] < p->cy[1] ? p->cy[0] : p->cy[1]; + t2 = p->cy[2] < p->cy[3] ? p->cy[2] : p->cy[3]; + p->ptop = (short)(t1 < t2 ? t1 : t2); + + // Rectangles enclosing each side and each side's magic numbers + for (t1 = 0; t1 < 4; t1++) { + p->lright[t1] = p->cx[t1] > p->cx[(t1+1)%4] ? p->cx[t1] : p->cx[(t1+1)%4]; + p->lleft[t1] = p->cx[t1] < p->cx[(t1+1)%4] ? p->cx[t1] : p->cx[(t1+1)%4]; + + p->ltop[t1] = p->cy[t1] < p->cy[(t1+1)%4] ? p->cy[t1] : p->cy[(t1+1)%4]; + p->lbottom[t1] = p->cy[t1] > p->cy[(t1+1)%4] ? p->cy[t1] : p->cy[(t1+1)%4]; + + p->a[t1] = p->cy[t1] - p->cy[(t1+1)%4]; + p->b[t1] = p->cx[(t1+1)%4] - p->cx[t1]; + p->c[t1] = (long)p->cy[t1]*p->cx[(t1+1)%4] - (long)p->cx[t1]*p->cy[(t1+1)%4]; + } +} + +/** + * Calculate a point approximating to the centre of a polygon. + * Not very sophisticated. + */ +#ifdef DEBUG +void PseudoCentre(POLYGON *p) { +#else +static void PseudoCentre(POLYGON *p) { +#endif + p->pcentrex = (p->cx[0] + p->cx[1] + p->cx[2] + p->cx[3])/4; + p->pcentrey = (p->cy[0] + p->cy[1] + p->cy[2] + p->cy[3])/4; + + if (!IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))) { + int i, top = 0, bot = 0; + + for (i = p->ptop; i <= p->pbottom; i++) { + if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) { + top = i; + break; + } + } + for (i = p->pbottom; i >= p->ptop; i--) { + if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) { + bot = i; + break; + } + } + p->pcentrex = (top+bot)/2; + } +#ifdef DEBUG + // assert(IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))); // Pseudo-centre is not in path + if (!IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))) { + sprintf(tBufferAddr(), "Pseudo-centre is not in path (starting (%d, %d)) - polygon reversed?", + p->cx[0], p->cy[0]); + error(tBufferAddr()); + } +#endif +} + +/** + * Allocate a POLYGON structure. + */ +static POLYGON *GetPolyEntry(PTYPE type, const POLY *pp, int pno) { + for (int i = 0; i < MaxPolys; i++) { + if (!Polys[i]) { + POLYGON *p = Polys[i] = &Polygons[i]; + memset(p, 0, sizeof(POLYGON)); + + p->polytype = type; // Polygon type + p->pIndex = pno; + p->tagState = TAG_OFF; + p->pointState = NOT_POINTING; + p->polyID = FROM_LE_32(pp->id); // Identifier + + for (int j = 0; j < 4; j++) { // Polygon definition + p->cx[j] = (short)FROM_LE_32(pp->x[j]); + p->cy[j] = (short)FROM_LE_32(pp->y[j]); + } + + return p; + } + } + + error("Exceeded MaxPolys"); + return NULL; +} + +/** + * Initialise an EXIT polygon. + */ +static void InitExit(const POLY *pp, int pno) { + FiddlyBit(GetPolyEntry(EXIT, pp, pno)); +} + +/** + * Initialise a PATH or NPATH polygon. + */ +static void InitPath(const POLY *pp, bool NodePath, int pno) { + POLYGON *p; + + p = GetPolyEntry(PATH, pp, pno); // Obtain a slot + + if (NodePath) { + p->subtype = NODE; + } else { + p->subtype = NORMAL; + } + + // Clear out ajacent path pointers + memset(p->adjpaths, 0, MAXADJ*sizeof(PPOLYGON)); + + FiddlyBit(p); + PseudoCentre(p); +} + + +/** + * Initialise a BLOCKING polygon. + */ +static void InitBlock(const POLY *pp, int pno) { + FiddlyBit(GetPolyEntry(BLOCKING, pp, pno)); +} + +/** + * Initialise an extra BLOCKING polygon related to a moving actor. + * The width of the polygon depends on the width of the actor which is + * trying to walk through the actor you first thought of. + * This is for dynamic blocking. + */ +HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta) { + int caX, caY; // Calling actor co-ords + int taX, taY; // Test actor co-ords + int left, right; + + GetMActorPosition(ca, &caX, &caY); // Calling actor co-ords + GetMActorPosition(ta, &taX, &taY); // Test actor co-ords + + left = GetMActorLeft(ta) - (GetMActorRight(ca) - caX); + right = GetMActorRight(ta) + (caX - GetMActorLeft(ca)); + + memset(&extraBlock, 0, sizeof(extraBlock)); + + // The 3s on the y co-ordinates used to be 10s + extraBlock.cx[0] = (short)(left - 2); + extraBlock.cy[0] = (short)(taY - 3); + extraBlock.cx[1] = (short)(right + 2); + extraBlock.cy[1] = (short)(taY - 3); + extraBlock.cx[2] = (short)(right + 2); + extraBlock.cy[2] = (short)(taY + 3); + extraBlock.cx[3] = (short)(left - 2); + extraBlock.cy[3] = (short)(taY + 3); + + FiddlyBit(&extraBlock); // Is this necessary? + + Polys[MAX_POLY] = &extraBlock; + return MAX_POLY; +} + +/** + * Initialise an EFFECT polygon. + */ +static void InitEffect(const POLY *pp, int pno) { + FiddlyBit(GetPolyEntry(EFFECT, pp, pno)); +} + + +/** + * Initialise a REFER polygon. + */ +static void InitRefer(const POLY *pp, int pno) { + POLYGON *p = GetPolyEntry(REFER, pp, pno); // Obtain a slot + + p->subtype = FROM_LE_32(pp->reftype); // Refer type + + FiddlyBit(p); +} + + +/** + * Initialise a TAG polygon. + */ +static void InitTag(const POLY *pp, int pno) { + FiddlyBit(GetPolyEntry(TAG, pp, pno)); +} + + +/** + * Called at the start of a scene to initialise the polys in that scene. + */ +void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart) { + const POLY *pp; // Pointer to compiled data polygon structure + + pHandle = ph; + noofPolys = numPoly; + + if (Polygons == NULL) { + // first time - allocate memory for process list + Polygons = (PPOLYGON)calloc(MaxPolys, sizeof(POLYGON)); + + // make sure memory allocated + if (Polygons == NULL) { + error("Cannot allocate memory for polygon data"); + } + } + + for (int i = 0; i < noofPolys; i++) { + if (Polys[i]) { + Polys[i]->pointState = NOT_POINTING; + Polys[i] = NULL; + } + } + + memset(RoutePaths, 0, sizeof(RoutePaths)); + + if (!bRestart) + memset(deadPolys, 0, sizeof(deadPolys)); + + pp = (const POLY *)LockMem(ph); + for (int i = 0; i < numPoly; i++, pp++) { + switch (FROM_LE_32(pp->type)) { + case POLY_PATH: + InitPath(pp, false, i); + break; + + case POLY_NPATH: + InitPath(pp, true, i); + break; + + case POLY_BLOCK: + InitBlock(pp, i); + break; + + case POLY_REFER: + InitRefer(pp, i); + break; + + case POLY_EFFECT: + InitEffect(pp, i); + break; + + case POLY_EXIT: + InitExit(pp, i); + break; + + case POLY_TAG: + InitTag(pp, i); + break; + + default: + error("Unknown polygon type"); + } + } + SetPathAdjacencies(); // Paths need to know the facts +#ifdef DEBUG + CheckNPathIntegrity(); +#endif + SetExTags(ph); // Some tags may have been killed + SetExExits(ph); // Some exits may have been killed + + if (bRestart) + SetExBlocks(); // Some blocks may have been killed +} + +/** + * Called at the end of a scene to ditch all polygons. + */ +void DropPolygons() { + pathsOnRoute = 0; + memset(RoutePaths, 0, sizeof(RoutePaths)); + RouteEnd = NULL; + + for (int i = 0; i < noofPolys; i++) { + if (Polys[i]) { + Polys[i]->pointState = NOT_POINTING; + Polys[i] = NULL; + } + } + noofPolys = 0; + free(Polygons); + Polygons = NULL; +} + + + +PTYPE PolyType(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (25)"); + + return Polys[hp]->polytype; +} + +int PolySubtype(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (26)"); + + return Polys[hp]->subtype; +} + +int PolyCentreX(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (27)"); + + return Polys[hp]->pcentrex; +} + +int PolyCentreY(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (28)"); + + return Polys[hp]->pcentrey; +} + +int PolyCornerX(HPOLYGON hp, int n) { + CHECK_HP(hp, "Out of range polygon handle (29)"); + + return Polys[hp]->cx[n]; +} + +int PolyCornerY(HPOLYGON hp, int n) { + CHECK_HP(hp, "Out of range polygon handle (30)"); + + return Polys[hp]->cy[n]; +} + +PSTATE PolyPointState(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (31)"); + + return Polys[hp]->pointState; +} + +TSTATE PolyTagState(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (32)"); + + return Polys[hp]->tagState; +} + +SCNHANDLE PolyTagHandle(HPOLYGON hp) { + CHECK_HP(hp, "Out of range polygon handle (33)"); + + return Polys[hp]->oTagHandle; +} + +void SetPolyPointState(HPOLYGON hp, PSTATE ps) { + CHECK_HP(hp, "Out of range polygon handle (34)"); + + Polys[hp]->pointState = ps; +} + +void SetPolyTagState(HPOLYGON hp, TSTATE ts) { + CHECK_HP(hp, "Out of range polygon handle (35)"); + + Polys[hp]->tagState = ts; +} + +void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th) { + CHECK_HP(hp, "Out of range polygon handle (36)"); + + Polys[hp]->oTagHandle = th; +} + +void MaxPolygons(int numPolys) { + assert(numPolys <= MAX_POLY); + + MaxPolys = numPolys; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/polygons.h b/engines/tinsel/polygons.h new file mode 100644 index 0000000000..464aa4e124 --- /dev/null +++ b/engines/tinsel/polygons.h @@ -0,0 +1,186 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Definition of POLYGON structure and functions in POLYGONS.C + */ + +#ifndef TINSEL_POLYGONS_H // prevent multiple includes +#define TINSEL_POLYGONS_H + +#include "tinsel/dw.h" // for SCNHANDLE +#include "tinsel/scene.h" // for PPOLY and REEL + +namespace Tinsel { + +// Note 7/10/94, with adjacency reduction ANKHMAP max is 3, UNSEEN max is 4 +// so reduced this back to 6 (from 12) for now. +#define MAXADJ 6 // Max number of known adjacent paths + + +// Polygon Types +enum PTYPE { + TEST, PATH, EXIT, BLOCKING, + EFFECT, REFER, TAG, EX_TAG, EX_EXIT, EX_BLOCK +}; + +// subtype +#define NORMAL 0 +#define NODE 1 // For paths + +// tagState +enum TSTATE { + NO_TAG, TAG_OFF, TAG_ON +}; + +// pointState +enum PSTATE { + NO_POINT, NOT_POINTING, POINTING +}; + + + +struct POLYGON { + + PTYPE polytype; // Polygon type + + int subtype; // refer type in REFER polygons + // NODE/NORMAL in PATH polygons + + int pIndex; // Index into compiled polygon data + + /* + * Data duplicated from compiled polygon data + */ + short cx[4]; // Corners (clockwise direction) + short cy[4]; + int polyID; + + /* For TAG and EXIT (and EFFECT in future?) polygons only */ + TSTATE tagState; + PSTATE pointState; + SCNHANDLE oTagHandle; // Override tag. + + /* For Path polygons only */ + bool tried; + + /* + * Internal derived data for speed and conveniance + * set up by FiddlyBit() + */ + short ptop; // + short pbottom; // Enclosing external rectangle + short pleft; // + short pright; // + + short ltop[4]; // + short lbottom[4]; // Rectangles enclosing each side + short lleft[4]; // + short lright[4]; // + + int a[4]; // y1-y2 } + int b[4]; // x2-x1 } See IsInPolygon() + long c[4]; // y1x2 - x1y2 } + + /* + * Internal derived data for speed and conveniance + * set up by PseudoCentre() + */ + int pcentrex; // Pseudo-centre + int pcentrey; // + + /** + * List of adjacent polygons. For Path polygons only. + * set up by SetPathAdjacencies() + */ + POLYGON *adjpaths[MAXADJ]; + +}; + + +enum { + NOPOLY = -1 +}; + + + +/*-------------------------------------------------------------------------*/ + +bool IsInPolygon(int xt, int yt, HPOLYGON p); +HPOLYGON InPolygon(int xt, int yt, PTYPE type); +void BlockingCorner(HPOLYGON poly, int *x, int *y, int tarx, int tary); +void FindBestPoint(HPOLYGON path, int *x, int *y, int *line); +bool IsAdjacentPath(HPOLYGON path1, HPOLYGON path2); +HPOLYGON getPathOnTheWay(HPOLYGON from, HPOLYGON to); +int NearestEndNode(HPOLYGON path, int x, int y); +int NearEndNode(HPOLYGON spath, HPOLYGON dpath); +int NearestNodeWithin(HPOLYGON npath, int x, int y); +void NearestCorner(int *x, int *y, HPOLYGON spath, HPOLYGON dpath); +bool IsPolyCorner(HPOLYGON hPath, int x, int y); +int GetScale(HPOLYGON path, int y); +void getNpathNode(HPOLYGON npath, int node, int *px, int *py); +void getPolyTagInfo(HPOLYGON p, SCNHANDLE *hTagText, int *tagx, int *tagy); +SCNHANDLE getPolyFilm(HPOLYGON p); +void getPolyNode(HPOLYGON p, int *px, int *py); +SCNHANDLE getPolyScript(HPOLYGON p); +REEL getPolyReelType(HPOLYGON p); +int32 getPolyZfactor(HPOLYGON p); +int numNodes(HPOLYGON pp); +void RebootDeadTags(void); +void DisableBlock(int blockno); +void EnableBlock(int blockno); +void DisableTag(int tagno); +void EnableTag(int tagno); +void DisableExit(int exitno); +void EnableExit(int exitno); +HPOLYGON FirstPathPoly(void); +HPOLYGON GetPolyHandle(int i); +void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart); +void DropPolygons(void); + + +void SaveDeadPolys(bool *sdp); +void RestoreDeadPolys(bool *sdp); + +/*-------------------------------------------------------------------------*/ + +PTYPE PolyType(HPOLYGON hp); // ->type +int PolySubtype(HPOLYGON hp); // ->subtype +int PolyCentreX(HPOLYGON hp); // ->pcentrex +int PolyCentreY(HPOLYGON hp); // ->pcentrey +int PolyCornerX(HPOLYGON hp, int n); // ->cx[n] +int PolyCornerY(HPOLYGON hp, int n); // ->cy[n] +PSTATE PolyPointState(HPOLYGON hp); // ->pointState +TSTATE PolyTagState(HPOLYGON hp); // ->tagState +SCNHANDLE PolyTagHandle(HPOLYGON hp); // ->oTagHandle + +void SetPolyPointState(HPOLYGON hp, PSTATE ps); // ->pointState +void SetPolyTagState(HPOLYGON hp, TSTATE ts); // ->tagState +void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th);// ->oTagHandle + +void MaxPolygons(int maxPolys); + +/*-------------------------------------------------------------------------*/ + +} // end of namespace Tinsel + +#endif /* TINSEL_POLYGONS_H */ diff --git a/engines/tinsel/rince.cpp b/engines/tinsel/rince.cpp new file mode 100644 index 0000000000..1ecf43f821 --- /dev/null +++ b/engines/tinsel/rince.cpp @@ -0,0 +1,708 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Should really be called "moving actors.c" + */ + +#include "tinsel/actors.h" +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/move.h" +#include "tinsel/multiobj.h" // multi-part object defintions etc. +#include "tinsel/object.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/timers.h" +#include "tinsel/token.h" + +#include "common/util.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +static MACTOR Movers[MAX_MOVERS]; + + +/** + * RebootMovers + */ +void RebootMovers(void) { + memset(Movers, 0, sizeof(Movers)); +} + +/** + * Given an actor number, return pointer to its moving actor structure, + * if it is a moving actor. + */ +PMACTOR GetMover(int ano) { + int i; + + // Slot 0 is reserved for lead actor + if (ano == LeadId() || ano == LEAD_ACTOR) + return &Movers[0]; + + for (i = 1; i < MAX_MOVERS; i++) + if (Movers[i].actorID == ano) + return &Movers[i]; + + return NULL; +} + +/** + * Register an actor as being a moving one. + */ +PMACTOR SetMover(int ano) { + int i; + + // Slot 0 is reserved for lead actor + if (ano == LeadId() || ano == LEAD_ACTOR) { + Movers[0].actorToken = TOKEN_LEAD; + Movers[0].actorID = LeadId(); + return &Movers[0]; + } + + // Check it hasn't already been declared + for (i = 1; i < MAX_MOVERS; i++) { + if (Movers[i].actorID == ano) { + // Actor is already a moving actor + return &Movers[i]; + } + } + + // Find an empty slot + for (i = 1; i < MAX_MOVERS; i++) + if (!Movers[i].actorID) { + Movers[i].actorToken = TOKEN_LEAD + i; + Movers[i].actorID = ano; + return &Movers[i]; + } + + error("Too many moving actors"); +} + +/** + * Given an index, returns the associated moving actor. + * + * At the time of writing, used by the effect process. + */ +PMACTOR GetLiveMover(int index) { + assert(index >= 0 && index < MAX_MOVERS); // out of range + + if (Movers[index].MActorState == NORM_MACTOR) + return &Movers[index]; + else + return NULL; +} + +bool IsMAinEffectPoly(int index) { + assert(index >= 0 && index < MAX_MOVERS); // out of range + + return Movers[index].InEffect; +} + +void SetMAinEffectPoly(int index, bool tf) { + assert(index >= 0 && index < MAX_MOVERS); // out of range + + Movers[index].InEffect = tf; +} + +/** + * Remove a moving actor from the current scene. + */ +void KillMActor(PMACTOR pActor) { + if (pActor->MActorState == NORM_MACTOR) { + pActor->MActorState = NO_MACTOR; + MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), pActor->actorObj); + pActor->actorObj = NULL; + assert(CurrentProcess() != pActor->pProc); + ProcessKill(pActor->pProc); + } +} + +/** + * getMActorState + */ +MAS getMActorState(PMACTOR pActor) { + return pActor->MActorState; +} + +/** + * If the actor's object exists, move it behind the background. + * MultiHideObject() is deliberately not used, as StepAnimScript() calls + * cause the object to re-appear. + */ +void hideMActor(PMACTOR pActor, int sf) { + assert(pActor); // Hiding null moving actor + + pActor->aHidden = true; + pActor->SlowFactor = sf; + + if (pActor->actorObj) + MultiSetZPosition(pActor->actorObj, -1); +} + +/** + * getMActorHideState + */ +bool getMActorHideState(PMACTOR pActor) { + if (pActor) + return pActor->aHidden; + else + return false; +} + +/** + * unhideMActor + */ +void unhideMActor(PMACTOR pActor) { + assert(pActor); // unHiding null moving actor + + pActor->aHidden = false; + + // Make visible on the screen + if (pActor->actorObj) { + // If no path, just use first path in the scene + if (pActor->hCpath != NOPOLY) + MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath)); + else + MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly())); + } +} + +/** + * Get it into our heads that there's nothing doing. + * Called at the end of a scene. + */ +void DropMActors(void) { + for (int i = 0; i < MAX_MOVERS; i++) { + Movers[i].MActorState = NO_MACTOR; + Movers[i].objx = 0; + Movers[i].objy = 0; + Movers[i].actorObj = NULL; // No moving actor objects + + Movers[i].hCpath = NOPOLY; // No moving actor path + } +} + + +/** + * Reposition a moving actor. + */ +void MoveMActor(PMACTOR pActor, int x, int y) { + int z; + int node; + HPOLYGON hPath; + + assert(pActor); // Moving null moving actor + assert(pActor->actorObj); + + pActor->objx = x; + pActor->objy = y; + MultiSetAniXY(pActor->actorObj, x, y); + + hPath = InPolygon(x, y, PATH); + if (hPath != NOPOLY) { + pActor->hCpath = hPath; + if (PolySubtype(hPath) == NODE) { + node = NearestNodeWithin(hPath, x, y); + getNpathNode(hPath, node, &pActor->objx, &pActor->objy); + pActor->hFnpath = hPath; + pActor->line = node; + pActor->npstatus = GOING_UP; + } else { + pActor->hFnpath = NOPOLY; + pActor->npstatus = NOT_IN; + } + + z = GetScale(hPath, pActor->objy); + pActor->scale = z; + SetMActorStanding(pActor); + } else { + pActor->bNoPath = true; + + pActor->hFnpath = NOPOLY; // Ain't in one + pActor->npstatus = NOT_IN; + + // Ensure legal reel and scale + if (pActor->dirn < 0 || pActor->dirn > 3) + pActor->dirn = FORWARD; + if (pActor->scale < 0 || pActor->scale > TOTAL_SCALES) + pActor->scale = 1; + } +} + +/** + * Get position of a moving actor. + */ +void GetMActorPosition(PMACTOR pActor, int *paniX, int *paniY) { + assert(pActor); // Getting null moving actor's position + + if (pActor->actorObj != NULL) + GetAniPosition(pActor->actorObj, paniX, paniY); + else { + *paniX = 0; + *paniY = 0; + } +} + +/** + * Moving actor's mid-top position. + */ +void GetMActorMidTopPosition(PMACTOR pActor, int *aniX, int *aniY) { + assert(pActor); // Getting null moving actor's mid-top position + assert(pActor->actorObj); // Getting null moving actor's mid-top position + + *aniX = (MultiLeftmost(pActor->actorObj) + MultiRightmost(pActor->actorObj))/2; + *aniY = MultiHighest(pActor->actorObj); +} + +/** + * Moving actor's left-most co-ordinate. + */ +int GetMActorLeft(PMACTOR pActor) { + assert(pActor); // Getting null moving actor's leftmost position + assert(pActor->actorObj); // Getting null moving actor's leftmost position + + return MultiLeftmost(pActor->actorObj); +} + +/** + * Moving actor's right-most co-ordinate. + */ +int GetMActorRight(PMACTOR pActor) { + assert(pActor); // Getting null moving actor's rightmost position + assert(pActor->actorObj); // Getting null moving actor's rightmost position + + return MultiRightmost(pActor->actorObj); +} + +/** + * See if moving actor is stood within a polygon. + */ +bool MActorIsInPolygon(PMACTOR pActor, HPOLYGON hp) { + assert(pActor); // Checking if null moving actor is in polygon + assert(pActor->actorObj); // Checking if null moving actor is in polygon + + int aniX, aniY; + GetAniPosition(pActor->actorObj, &aniX, &aniY); + + return IsInPolygon(aniX, aniY, hp); +} + +/** + * Change which reel is playing for a moving actor. + */ +void AlterMActor(PMACTOR pActor, SCNHANDLE film, AR_FUNCTION fn) { + const FILM *pfilm; + + assert(pActor->actorObj); // Altering null moving actor's animation script + + if (fn == AR_POPREEL) { + film = pActor->pushedfilm; // Use the saved film + } + if (fn == AR_PUSHREEL) { + // Save the one we're replacing + pActor->pushedfilm = (pActor->TagReelRunning) ? pActor->lastfilm : 0; + } + + if (film == 0) { + if (pActor->TagReelRunning) { + // Revert to 'normal' actor + SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true); + pActor->TagReelRunning = false; + } + } else { + pActor->lastfilm = film; // Remember this one + + pfilm = (const FILM *)LockMem(film); + assert(pfilm != NULL); + + InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + pActor->scount = 0; + + // If no path, just use first path in the scene + if (pActor->hCpath != NOPOLY) + MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath)); + else + MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly())); + + if (fn == AR_WALKREEL) { + pActor->TagReelRunning = false; + pActor->walkReel = true; + } else { + pActor->TagReelRunning = true; + pActor->walkReel = false; + +#ifdef DEBUG + assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished! +#else + StepAnimScript(&pActor->actorAnim); // 04/01/95 +#endif + } + + // Hang on, we may not want him yet! 04/01/95 + if (pActor->aHidden) + MultiSetZPosition(pActor->actorObj, -1); + } +} + +/** + * Return the actor's direction. + */ +DIRREEL GetMActorDirection(PMACTOR pActor) { + return pActor->dirn; +} + +/** + * Return the actor's scale. + */ +int GetMActorScale(PMACTOR pActor) { + return pActor->scale; +} + +/** + * Point actor in specified derection + */ +void SetMActorDirection(PMACTOR pActor, DIRREEL dirn) { + pActor->dirn = dirn; +} + +/** + * MAmoving + */ +bool MAmoving(PMACTOR pActor) { + return pActor->bMoving; +} + +/** + * Return an actor's walk ticket. + */ +int GetActorTicket(PMACTOR pActor) { + return pActor->ticket; +} + +/** + * Get actor to adopt its appropriate standing reel. + */ +void SetMActorStanding(PMACTOR pActor) { + assert(pActor->actorObj); + AlterMActor(pActor, pActor->StandReels[pActor->scale-1][pActor->dirn], AR_NORMAL); +} + +/** + * Get actor to adopt its appropriate walking reel. + */ +void SetMActorWalkReel(PMACTOR pActor, DIRREEL reel, int scale, bool force) { + SCNHANDLE whichReel; + const FILM *pfilm; + + // Kill off any play that may be going on for this actor + // and restore the real actor + storeActorReel(pActor->actorID, NULL, 0, NULL, 0, 0, 0); + unhideMActor(pActor); + + // Don't do it if using a special walk reel + if (pActor->walkReel) + return; + + if (force || pActor->scale != scale || pActor->dirn != reel) { + assert(reel >= 0 && reel <= 3 && scale > 0 && scale <= TOTAL_SCALES); // out of range scale or reel + + // If scale change and both are regular scales + // and there's a scaling reel in the right direction + if (pActor->scale != scale + && scale <= NUM_MAINSCALES && pActor->scale <= NUM_MAINSCALES + && (whichReel = ScalingReel(pActor->actorID, pActor->scale, scale, reel)) != 0) { +// error("Cripes!"); + ; // Use what is now in 'whichReel' + } else { + whichReel = pActor->WalkReels[scale-1][reel]; + assert(whichReel); // no reel + } + + pfilm = (const FILM *)LockMem(whichReel); + assert(pfilm != NULL); // no film + + InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), 1); + + // Synchronised walking reels + SkipFrames(&pActor->actorAnim, pActor->scount); + + pActor->scale = scale; + pActor->dirn = reel; + } +} + +/** + * Sort some stuff out at actor start-up time. + */ +static void InitialPathChecks(PMACTOR pActor, int xpos, int ypos) { + HPOLYGON hPath; + int node; + int z; + + pActor->objx = xpos; + pActor->objy = ypos; + + /*-------------------------------------- + | If Actor is in a follow nodes path, | + | position it at the nearest node. | + --------------------------------------*/ + hPath = InPolygon(xpos, ypos, PATH); + + if (hPath != NOPOLY) { + pActor->hCpath = hPath; + if (PolySubtype(hPath) == NODE) { + node = NearestNodeWithin(hPath, xpos, ypos); + getNpathNode(hPath, node, &pActor->objx, &pActor->objy); + pActor->hFnpath = hPath; + pActor->line = node; + pActor->npstatus = GOING_UP; + } + + z = GetScale(hPath, pActor->objy); + } else { + pActor->bNoPath = true; + + z = GetScale(FirstPathPoly(), pActor->objy); + } + SetMActorWalkReel(pActor, FORWARD, z, false); +} + +/** + * Clear everything out at actor start-up time. + */ +static void InitMActor(PMACTOR pActor) { + + pActor->objx = pActor->objy = 0; + pActor->targetX = pActor->targetY = -1; + pActor->ItargetX = pActor->ItargetY = -1; + pActor->hIpath = NOPOLY; + pActor->UtargetX = pActor->UtargetY = -1; + pActor->hUpath = NOPOLY; + pActor->hCpath = NOPOLY; + + pActor->over = false; + pActor->InDifficulty = NO_PROB; + + pActor->hFnpath = NOPOLY; + pActor->npstatus = NOT_IN; + pActor->line = 0; + + pActor->Tline = 0; + + pActor->TagReelRunning = false; + + if (pActor->dirn != FORWARD || pActor->dirn != AWAY + || pActor->dirn != LEFTREEL || pActor->dirn != RIGHTREEL) + pActor->dirn = FORWARD; + + if (pActor->scale < 0 || pActor->scale > TOTAL_SCALES) + pActor->scale = 1; + + pActor->scount = 0; + + pActor->fromx = pActor->fromy = 0; + + pActor->bMoving = false; + pActor->bNoPath = false; + pActor->bIgPath = false; + pActor->walkReel = false; + + pActor->actorObj = NULL; + + pActor->lastfilm = 0; + pActor->pushedfilm = 0; + + pActor->InEffect = false; + pActor->aHidden = false; // 20/2/95 +} + +static void MActorProcessHelper(int X, int Y, int id, PMACTOR pActor) { + const FILM *pfilm; + const MULTI_INIT *pmi; + const FRAME *pFrame; + PIMAGE pim; + + + assert(BackPal()); // Can't start actor without a background palette + assert(pActor->WalkReels[0][FORWARD]); // Starting actor process without walk reels + + InitMActor(pActor); + InitialPathChecks(pActor, X, Y); + + pfilm = (const FILM *)LockMem(pActor->WalkReels[0][FORWARD]); + pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfilm->reels[0].mobj)); + +//--- + pFrame = (const FRAME *)LockMem(FROM_LE_32(pmi->hMulFrame)); + + // get pointer to image + pim = (PIMAGE)LockMem(READ_LE_UINT32(pFrame)); // handle to image + pim->hImgPal = TO_LE_32(BackPal()); +//--- + pActor->actorObj = MultiInitObject(pmi); + +/**/ assert(pActor->actorID == id); + pActor->actorID = id; + + // add it to display list + MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pActor->actorObj); + storeActorReel(id, NULL, 0, pActor->actorObj, 0, 0, 0); + + InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate)); + pActor->scount = 0; + + MultiSetAniXY(pActor->actorObj, pActor->objx, pActor->objy); + + // If no path, just use first path in the scene + if (pActor->hCpath != NOPOLY) + MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath)); + else + MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly())); + + // Make him the right size + SetMActorStanding(pActor); + +//**** if added 18/11/94, am + if (X != MAGICX && Y != MAGICY) { + hideMActor(pActor, 0); // Allows a play to come in before this appears + pActor->aHidden = false; // ...but don't stay hidden + } + + pActor->MActorState = NORM_MACTOR; +} + +/** + * Moving actor process - 1 per moving actor in current scene. + */ +void MActorProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + PMACTOR pActor = *(PMACTOR *)ProcessGetParamsSelf(); + + CORO_BEGIN_CODE(_ctx); + + while (1) { + if (pActor->TagReelRunning) { + if (!pActor->aHidden) +#ifdef DEBUG + assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished! +#else + StepAnimScript(&pActor->actorAnim); +#endif + } else + DoMoveActor(pActor); + + CORO_SLEEP(1); // allow rescheduling + + } + + CORO_END_CODE; +} + +void MActorProcessCreate(int X, int Y, int id, PMACTOR pActor) { + MActorProcessHelper(X, Y, id, pActor); + pActor->pProc = CoroutineInstall(PID_MACTOR, MActorProcess, &pActor, sizeof(PMACTOR)); +} + + +/** + * Check for moving actor collision. + */ +PMACTOR InMActorBlock(PMACTOR pActor, int x, int y) { + int caX; // Calling actor's pos'n + int caL, caR; // Calling actor's left and right + int taX, taY; // Test actor's pos'n + int taL, taR; // Test actor's left and right + + caX = pActor->objx; + if (pActor->hFnpath != NOPOLY || bNoBlocking) + return NULL; + + caL = GetMActorLeft(pActor) + x - caX; + caR = GetMActorRight(pActor) + x - caX; + + for (int i = 0; i < MAX_MOVERS; i++) { + if (pActor == &Movers[i] || Movers[i].MActorState == NO_MACTOR) + continue; + + // At around the same height? + GetMActorPosition(&Movers[i], &taX, &taY); + if (Movers[i].hFnpath != NOPOLY) + continue; + + if (ABS(y - taY) > 2) // 2 was 8 + continue; + + // To the left? + taL = GetMActorLeft(&Movers[i]); + if (caR <= taL) + continue; + + // To the right? + taR = GetMActorRight(&Movers[i]); + if (caL >= taR) + continue; + + return &Movers[i]; + } + return NULL; +} + +/** + * Copies key information for savescn.c to store away. + */ +void SaveMovers(SAVED_MOVER *sMoverInfo) { + for (int i = 0; i < MAX_MOVERS; i++) { + sMoverInfo[i].MActorState= Movers[i].MActorState; + sMoverInfo[i].actorID = Movers[i].actorID; + sMoverInfo[i].objx = Movers[i].objx; + sMoverInfo[i].objy = Movers[i].objy; + sMoverInfo[i].lastfilm = Movers[i].lastfilm; + + memcpy(sMoverInfo[i].WalkReels, Movers[i].WalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); + memcpy(sMoverInfo[i].StandReels, Movers[i].StandReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); + memcpy(sMoverInfo[i].TalkReels, Movers[i].TalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); + } +} + +void RestoreAuxScales(SAVED_MOVER *sMoverInfo) { + for (int i = 0; i < MAX_MOVERS; i++) { + memcpy(Movers[i].WalkReels, sMoverInfo[i].WalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); + memcpy(Movers[i].StandReels, sMoverInfo[i].StandReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); + memcpy(Movers[i].TalkReels, sMoverInfo[i].TalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE)); + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/rince.h b/engines/tinsel/rince.h new file mode 100644 index 0000000000..a3ed5bd845 --- /dev/null +++ b/engines/tinsel/rince.h @@ -0,0 +1,209 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Should really be called "moving actors.h" + */ + +#ifndef TINSEL_RINCE_H // prevent multiple includes +#define TINSEL_RINCE_H + +#include "tinsel/anim.h" // for ANIM +#include "tinsel/scene.h" // for TFTYPE + +namespace Tinsel { + +struct OBJECT; +struct PROCESS; + +enum NPS {NOT_IN, GOING_UP, GOING_DOWN, LEAVING, ENTERING}; + +enum IND {NO_PROB, TRY_CENTRE, TRY_CORNER, TRY_NEXTCORNER}; + +enum MAS {NO_MACTOR, NORM_MACTOR}; + +enum DIRREEL{ LEFTREEL, RIGHTREEL, FORWARD, AWAY }; + +enum { + NUM_MAINSCALES = 5, + NUM_AUXSCALES = 5, + TOTAL_SCALES = NUM_MAINSCALES + NUM_AUXSCALES +}; + +struct MACTOR { + + int objx; /* Co-ordinates object */ + int objy; + + int targetX, targetY; + int ItargetX, ItargetY; /* Intermediate destination */ + HPOLYGON hIpath; + int UtargetX, UtargetY; /* Ultimate destination */ + HPOLYGON hUpath; + + HPOLYGON hCpath; + + bool over; + int ticket; + + IND InDifficulty; + +/* For use in 'follow nodes' polygons */ + HPOLYGON hFnpath; + NPS npstatus; + int line; + + int Tline; // NEW + + bool TagReelRunning; + + + /* Used internally */ + DIRREEL dirn; // Current reel + int scale; // Current scale + int scount; // Step count for walking reel synchronisation + + unsigned fromx; + unsigned fromy; + + bool bMoving; // Set this to TRUE during a walk + + bool bNoPath; + bool bIgPath; + bool walkReel; + + OBJECT *actorObj; // Actor's object + ANIM actorAnim; // Actor's animation script + + SCNHANDLE lastfilm; // } Used by AlterActor() + SCNHANDLE pushedfilm; // } + + int actorID; + int actorToken; + + SCNHANDLE WalkReels[TOTAL_SCALES][4]; + SCNHANDLE StandReels[TOTAL_SCALES][4]; + SCNHANDLE TalkReels[TOTAL_SCALES][4]; + + MAS MActorState; + + bool aHidden; + int SlowFactor; // Slow down movement while hidden + + bool stop; + + /* NOTE: If effect polys can overlap, this needs improving */ + bool InEffect; + + PROCESS *pProc; +}; +typedef MACTOR *PMACTOR; + +//--------------------------------------------------------------------------- + + +void MActorProcessCreate(int X, int Y, int id, MACTOR *pActor); + + +enum AR_FUNCTION { AR_NORMAL, AR_PUSHREEL, AR_POPREEL, AR_WALKREEL }; + + +MACTOR *GetMover(int ano); +MACTOR *SetMover(int ano); +void KillMActor(MACTOR *pActor); +MACTOR *GetLiveMover(int index); + +MAS getMActorState(MACTOR *psActor); + +void hideMActor(MACTOR *pActor, int sf); +bool getMActorHideState(MACTOR *pActor); +void unhideMActor(MACTOR *pActor); +void DropMActors(void); +void MoveMActor(MACTOR *pActor, int x, int y); + +void GetMActorPosition(MACTOR *pActor, int *aniX, int *aniY); +void GetMActorMidTopPosition(MACTOR *pActor, int *aniX, int *aniY); +int GetMActorLeft(MACTOR *pActor); +int GetMActorRight(MACTOR *pActor); + +bool MActorIsInPolygon(MACTOR *pActor, HPOLYGON hPoly); +void AlterMActor(MACTOR *actor, SCNHANDLE film, AR_FUNCTION fn); +DIRREEL GetMActorDirection(MACTOR *pActor); +int GetMActorScale(MACTOR *pActor); +void SetMActorDirection(MACTOR *pActor, DIRREEL dirn); +void SetMActorStanding(MACTOR *actor); +void SetMActorWalkReel(MACTOR *actor, DIRREEL reel, int scale, bool force); + +MACTOR *InMActorBlock(MACTOR *pActor, int x, int y); + +void RebootMovers(void); + +bool IsMAinEffectPoly(int index); +void SetMAinEffectPoly(int index, bool tf); + +bool MAmoving(MACTOR *pActor); + +int GetActorTicket(MACTOR *pActor); + +/*----------------------------------------------------------------------*/ + +struct SAVED_MOVER { + + MAS MActorState; + int actorID; + int objx; + int objy; + SCNHANDLE lastfilm; + + SCNHANDLE WalkReels[TOTAL_SCALES][4]; + SCNHANDLE StandReels[TOTAL_SCALES][4]; + SCNHANDLE TalkReels[TOTAL_SCALES][4]; + +}; + +void SaveMovers(SAVED_MOVER *sMoverInfo); +void RestoreAuxScales(SAVED_MOVER *sMoverInfo); + +/*----------------------------------------------------------------------*/ + +/* +* Dodgy bit... +* These functions are now in MAREELS.C, but I can't be bothered to +* create a new header file. +*/ +SCNHANDLE GetMactorTalkReel(MACTOR *pAactor, TFTYPE dirn); + +void setscalingreels(int actor, int scale, int direction, + SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away); +SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRREEL reel); +void RebootScalingReels(void); + +enum { + MAGICX = -101, + MAGICY = -102 +}; + +/*----------------------------------------------------------------------*/ + +} // end of namespace Tinsel + +#endif /* TINSEL_RINCE_H */ diff --git a/engines/tinsel/saveload.cpp b/engines/tinsel/saveload.cpp new file mode 100644 index 0000000000..5cb149eb37 --- /dev/null +++ b/engines/tinsel/saveload.cpp @@ -0,0 +1,472 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Save and restore scene and game. + */ + +#include "tinsel/actors.h" +#include "tinsel/dw.h" +#include "tinsel/inventory.h" +#include "tinsel/rince.h" +#include "tinsel/savescn.h" +#include "tinsel/serializer.h" +#include "tinsel/timers.h" +#include "tinsel/tinlib.h" +#include "tinsel/tinsel.h" + +#include "common/savefile.h" + +namespace Tinsel { + + +/** + * The current savegame format version. + * Our save/load system uses an elaborate scheme to allow us to modify the + * savegame while keeping full backward compatibility, in the sense that newer + * ScummVM versions always are able to load old savegames. + * In order to achieve that, we store a version in the savegame files, and whenever + * the savegame layout is modified, the version is incremented. + * + * This roughly works by marking each savegame entry with a range of versions + * for which it is valid; the save/load code iterates over all entries, but + * only saves/loads those which are valid for the version of the savegame + * which is being loaded/saved currently. + */ +#define CURRENT_VER 1 +// TODO: Not yet used + +/** + * An auxillary macro, used to specify savegame versions. We use this instead + * of just writing the raw version, because this way they stand out more to + * the reading eye, making it a bit easier to navigate through the code. + */ +#define VER(x) x + + + + +//----------------- EXTERN FUNCTIONS -------------------- + +// in DOS_DW.C +extern void syncSCdata(Serializer &s); + +// in DOS_MAIN.C +//char HardDriveLetter(void); + +// in PCODE.C +extern void syncGlobInfo(Serializer &s); + +// in POLYGONS.C +extern void syncPolyInfo(Serializer &s); + +// in SAVESCN.CPP +extern void RestoreScene(SAVED_DATA *sd, bool bFadeOut); + +//----------------- LOCAL DEFINES -------------------- + +struct SaveGameHeader { + uint32 id; + uint32 size; + uint32 ver; + char desc[SG_DESC_LEN]; + struct tm dateTime; +}; + +enum { + SAVEGAME_ID = 0x44575399, // = 'DWSc' = "DiscWorld ScummVM" + SAVEGAME_HEADER_SIZE = 4 + 4 + 4 + SG_DESC_LEN + 7 +}; + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int numSfiles = 0; +static SFILES savedFiles[MAX_SFILES]; + +static bool NeedLoad = true; + +static SAVED_DATA *srsd = 0; +static int RestoreGameNumber = 0; +static char *SaveSceneName = 0; +static const char *SaveSceneDesc = 0; +static int *SaveSceneSsCount = 0; +static char *SaveSceneSsData = 0; // points to 'SAVED_DATA ssdata[MAX_NEST]' + +static SRSTATE SRstate = SR_IDLE; + +//------------- SAVE/LOAD SUPPORT METHODS ---------------- + +static void syncTime(Serializer &s, struct tm &t) { + s.syncAsUint16LE(t.tm_year); + s.syncAsByte(t.tm_mon); + s.syncAsByte(t.tm_mday); + s.syncAsByte(t.tm_hour); + s.syncAsByte(t.tm_min); + s.syncAsByte(t.tm_sec); + if (s.isLoading()) { + t.tm_wday = 0; + t.tm_yday = 0; + t.tm_isdst = 0; + } +} + +static bool syncSaveGameHeader(Serializer &s, SaveGameHeader &hdr) { + s.syncAsUint32LE(hdr.id); + s.syncAsUint32LE(hdr.size); + s.syncAsUint32LE(hdr.ver); + + s.syncBytes((byte *)hdr.desc, SG_DESC_LEN); + + syncTime(s, hdr.dateTime); + + int tmp = hdr.size - s.bytesSynced(); + // Perform sanity check + if (tmp < 0 || hdr.id != SAVEGAME_ID || hdr.ver > CURRENT_VER || hdr.size > 1024) + return false; + // Skip over any extra bytes + while (tmp-- > 0) { + byte b = 0; + s.syncAsByte(b); + } + return true; +} + +static void syncSavedMover(Serializer &s, SAVED_MOVER &sm) { + SCNHANDLE *pList[3] = { (SCNHANDLE *)&sm.WalkReels, (SCNHANDLE *)&sm.StandReels, (SCNHANDLE *)&sm.TalkReels }; + + s.syncAsUint32LE(sm.MActorState); + s.syncAsSint32LE(sm.actorID); + s.syncAsSint32LE(sm.objx); + s.syncAsSint32LE(sm.objy); + s.syncAsUint32LE(sm.lastfilm); + + for (int pIndex = 0; pIndex < 3; ++pIndex) { + SCNHANDLE *p = pList[pIndex]; + for (int i = 0; i < TOTAL_SCALES * 4; ++i) + s.syncAsUint32LE(*p++); + } +} + +static void syncSavedActor(Serializer &s, SAVED_ACTOR &sa) { + s.syncAsUint16LE(sa.actorID); + s.syncAsUint16LE(sa.z); + s.syncAsUint32LE(sa.bAlive); + s.syncAsUint32LE(sa.presFilm); + s.syncAsUint16LE(sa.presRnum); + s.syncAsUint16LE(sa.presX); + s.syncAsUint16LE(sa.presY); +} + +extern void syncAllActorsAlive(Serializer &s); + +static void syncNoScrollB(Serializer &s, NOSCROLLB &ns) { + s.syncAsSint32LE(ns.ln); + s.syncAsSint32LE(ns.c1); + s.syncAsSint32LE(ns.c2); +} + +static void syncSavedData(Serializer &s, SAVED_DATA &sd) { + s.syncAsUint32LE(sd.SavedSceneHandle); + s.syncAsUint32LE(sd.SavedBgroundHandle); + for (int i = 0; i < MAX_MOVERS; ++i) + syncSavedMover(s, sd.SavedMoverInfo[i]); + for (int i = 0; i < MAX_SAVED_ACTORS; ++i) + syncSavedActor(s, sd.SavedActorInfo[i]); + + s.syncAsSint32LE(sd.NumSavedActors); + s.syncAsSint32LE(sd.SavedLoffset); + s.syncAsSint32LE(sd.SavedToffset); + for (int i = 0; i < MAX_INTERPRET; ++i) + sd.SavedICInfo[i].syncWithSerializer(s); + for (int i = 0; i < MAX_POLY; ++i) + s.syncAsUint32LE(sd.SavedDeadPolys[i]); + s.syncAsUint32LE(sd.SavedControl); + s.syncAsUint32LE(sd.SavedMidi); + s.syncAsUint32LE(sd.SavedLoop); + s.syncAsUint32LE(sd.SavedNoBlocking); + + // SavedNoScrollData + for (int i = 0; i < MAX_VNOSCROLL; ++i) + syncNoScrollB(s, sd.SavedNoScrollData.NoVScroll[i]); + for (int i = 0; i < MAX_HNOSCROLL; ++i) + syncNoScrollB(s, sd.SavedNoScrollData.NoHScroll[i]); + s.syncAsUint32LE(sd.SavedNoScrollData.NumNoV); + s.syncAsUint32LE(sd.SavedNoScrollData.NumNoH); +} + + +/** + * Called when saving a game to a new file. + * Generates a new, unique, filename. + */ +static char *NewName(void) { + static char result[FNAMELEN]; + int i; + int ano = 1; // Allocated number + + while (1) { + Common::String fname = _vm->getSavegameFilename(ano); + strcpy(result, fname.c_str()); + + for (i = 0; i < numSfiles; i++) + if (!strcmp(savedFiles[i].name, result)) + break; + + if (i == numSfiles) + break; + ano++; + } + + return result; +} + +/** + * Interrogate the current DOS directory for saved game files. + * Store the file details, ordered by time, in savedFiles[] and return + * the number of files found). + */ +int getList(void) { + // No change since last call? + // TODO/FIXME: Just always reload this data? Be careful about slow downs!!! + if (!NeedLoad) + return numSfiles; + + int i; + + const Common::String pattern = _vm->getSavegamePattern(); + Common::StringList files = _vm->getSaveFileMan()->listSavefiles(pattern.c_str()); + + numSfiles = 0; + + for (Common::StringList::const_iterator file = files.begin(); file != files.end(); ++file) { + if (numSfiles >= MAX_SFILES) + break; + + const Common::String &fname = *file; + Common::InSaveFile *f = _vm->getSaveFileMan()->openForLoading(fname.c_str()); + if (f == NULL) { + continue; + } + + // Try to load save game header + Serializer s(f, 0); + SaveGameHeader hdr; + bool validHeader = syncSaveGameHeader(s, hdr); + delete f; + if (!validHeader) { + continue; // Invalid header, or savegame too new -> skip it + // TODO: In SCUMM, we still show an entry for the save, but with description + // "incompatible version". + } + + i = numSfiles; +#ifndef DISABLE_SAVEGAME_SORTING + for (i = 0; i < numSfiles; i++) { + if (difftime(mktime(&hdr.dateTime), mktime(&savedFiles[i].dateTime)) > 0) { + Common::copy_backward(&savedFiles[i], &savedFiles[numSfiles], &savedFiles[numSfiles + 1]); + break; + } + } +#endif + + strncpy(savedFiles[i].name, fname.c_str(), FNAMELEN); + strncpy(savedFiles[i].desc, hdr.desc, SG_DESC_LEN); + savedFiles[i].dateTime = hdr.dateTime; + + ++numSfiles; + } + + // Next getList() needn't do its stuff again + NeedLoad = false; + + return numSfiles; +} + + +char *ListEntry(int i, letype which) { + if (i == -1) + i = numSfiles; + + assert(i >= 0); + + if (i < numSfiles) + return which == LE_NAME ? savedFiles[i].name : savedFiles[i].desc; + else + return NULL; +} + +static void DoSync(Serializer &s) { + int sg; + + syncSavedData(s, *srsd); + syncGlobInfo(s); // Glitter globals + syncInvInfo(s); // Inventory data + + // Held object + if (s.isSaving()) + sg = WhichItemHeld(); + s.syncAsSint32LE(sg); + if (s.isLoading()) + HoldItem(sg); + + syncTimerInfo(s); // Timer data + syncPolyInfo(s); // Dead polygon data + syncSCdata(s); // Hook Scene and delayed scene + + s.syncAsSint32LE(*SaveSceneSsCount); + + if (*SaveSceneSsCount != 0) { + SAVED_DATA *sdPtr = (SAVED_DATA *)SaveSceneSsData; + for (int i = 0; i < *SaveSceneSsCount; ++i, ++sdPtr) + syncSavedData(s, *sdPtr); + } + + syncAllActorsAlive(s); +} + +/** + * DoRestore + */ +static bool DoRestore(void) { + Common::InSaveFile *f; + uint32 id; + + f = _vm->getSaveFileMan()->openForLoading(savedFiles[RestoreGameNumber].name); + if (f == NULL) { + return false; + } + + Serializer s(f, 0); + SaveGameHeader hdr; + if (!syncSaveGameHeader(s, hdr)) { + delete f; // Invalid header, or savegame too new -> skip it + return false; + } + + DoSync(s); + + id = f->readSint32LE(); + if (id != (uint32)0xFEEDFACE) + error("Incompatible saved game"); + + bool failed = f->ioFailed(); + + delete f; + + return !failed; +} + +/** + * DoSave + */ +static void DoSave(void) { + Common::OutSaveFile *f; + const char *fname; + + // Next getList() must do its stuff again + NeedLoad = true; + + if (SaveSceneName == NULL) + SaveSceneName = NewName(); + if (SaveSceneDesc[0] == 0) + SaveSceneDesc = "unnamed"; + + fname = SaveSceneName; + + f = _vm->getSaveFileMan()->openForSaving(fname); + if (f == NULL) + return; + + Serializer s(0, f); + + // Write out a savegame header + SaveGameHeader hdr; + hdr.id = SAVEGAME_ID; + hdr.size = SAVEGAME_HEADER_SIZE; + hdr.ver = CURRENT_VER; + memcpy(hdr.desc, SaveSceneDesc, SG_DESC_LEN); + g_system->getTimeAndDate(hdr.dateTime); + if (!syncSaveGameHeader(s, hdr) || f->ioFailed()) { + goto save_failure; + } + + DoSync(s); + + // Write out the special Id for Discworld savegames + f->writeUint32LE(0xFEEDFACE); + if (f->ioFailed()) + goto save_failure; + + f->finalize(); + delete f; + return; + +save_failure: + delete f; + _vm->getSaveFileMan()->removeSavefile(fname); +} + +/** + * ProcessSRQueue + */ +void ProcessSRQueue(void) { + switch (SRstate) { + case SR_DORESTORE: + if (DoRestore()) { + RestoreScene(srsd, false); + } + SRstate = SR_IDLE; + break; + + case SR_DOSAVE: + DoSave(); + SRstate = SR_IDLE; + break; + default: + break; + } +} + + +void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) { + assert(SRstate == SR_IDLE); + + SaveSceneName = name; + SaveSceneDesc = desc; + SaveSceneSsCount = pSsCount; + SaveSceneSsData = (char *)pSsData; + srsd = sd; + SRstate = SR_DOSAVE; +} + +void RequestRestoreGame(int num, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) { + assert(num >= 0); + + RestoreGameNumber = num; + SaveSceneSsCount = pSsCount; + SaveSceneSsData = (char *)pSsData; + srsd = sd; + SRstate = SR_DORESTORE; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/savescn.cpp b/engines/tinsel/savescn.cpp new file mode 100644 index 0000000000..fa6e921a56 --- /dev/null +++ b/engines/tinsel/savescn.cpp @@ -0,0 +1,335 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Save and restore scene and game. + */ + + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/dw.h" +#include "tinsel/faders.h" // FadeOutFast() +#include "tinsel/graphics.h" // ClearScreen() +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/music.h" +#include "tinsel/pid.h" +#include "tinsel/rince.h" +#include "tinsel/savescn.h" +#include "tinsel/sched.h" +#include "tinsel/scroll.h" +#include "tinsel/sound.h" +#include "tinsel/tinlib.h" +#include "tinsel/token.h" + +namespace Tinsel { + +//----------------- EXTERN FUNCTIONS -------------------- + +// in BG.C +extern void startupBackground(SCNHANDLE bfilm); +extern SCNHANDLE GetBgroundHandle(void); +extern void SetDoFadeIn(bool tf); + +// In DOS_DW.C +void RestoreMasterProcess(PINT_CONTEXT pic); + +// in EVENTS.C (declared here and not in events.h because of strange goings-on) +void RestoreProcess(PINT_CONTEXT pic); + +// in PLAY.C +extern void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y); + +// in SCENE.C +extern SCNHANDLE GetSceneHandle(void); +extern void NewScene(SCNHANDLE scene, int entry); + + + + +//----------------- LOCAL DEFINES -------------------- + +enum { + RS_COUNT = 5, // Restore scene count + + MAX_NEST = 4 +}; + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static bool ASceneIsSaved = false; + +static int savedSceneCount = 0; + +//static SAVED_DATA s_ssData[MAX_NEST]; +static SAVED_DATA *s_ssData = 0; +static SAVED_DATA sgData; + +static SAVED_DATA *s_rsd = 0; + +static int s_restoreSceneCount = 0; + +static bool bNoFade = false; + +//----------------- FORWARD REFERENCES -------------------- + + + +void InitialiseSs(void) { + if (s_ssData == NULL) { + s_ssData = (SAVED_DATA *)calloc(MAX_NEST, sizeof(SAVED_DATA)); + if (s_ssData == NULL) { + error("Cannot allocate memory for scene changes"); + } + } else + savedSceneCount = 0; +} + +void FreeSs(void) { + if (s_ssData) { + free(s_ssData); + s_ssData = NULL; + } +} + +/** + * Save current scene. + * @param sd Pointer to the scene data + */ +void SaveScene(SAVED_DATA *sd) { + sd->SavedSceneHandle = GetSceneHandle(); + sd->SavedBgroundHandle = GetBgroundHandle(); + SaveMovers(sd->SavedMoverInfo); + sd->NumSavedActors = SaveActors(sd->SavedActorInfo); + PlayfieldGetPos(FIELD_WORLD, &sd->SavedLoffset, &sd->SavedToffset); + SaveInterpretContexts(sd->SavedICInfo); + SaveDeadPolys(sd->SavedDeadPolys); + sd->SavedControl = TestToken(TOKEN_CONTROL); + CurrentMidiFacts(&sd->SavedMidi, &sd->SavedLoop); + sd->SavedNoBlocking = bNoBlocking; + GetNoScrollData(&sd->SavedNoScrollData); + + ASceneIsSaved = true; +} + +/** + * Initiate restoration of the saved scene. + * @param sd Pointer to the scene data + * @param bFadeOut Flag to perform a fade out + */ +void RestoreScene(SAVED_DATA *sd, bool bFadeOut) { + s_rsd = sd; + + if (bFadeOut) + s_restoreSceneCount = RS_COUNT + COUNTOUT_COUNT; // Set restore scene count + else + s_restoreSceneCount = RS_COUNT; // Set restore scene count +} + +/** + * Checks that all non-moving actors are playing the same reel as when + * the scene was saved. + * Also 'stand' all the moving actors at their saved positions. + */ +void sortActors(SAVED_DATA *rsd) { + for (int i = 0; i < rsd->NumSavedActors; i++) { + ActorsLife(rsd->SavedActorInfo[i].actorID, rsd->SavedActorInfo[i].bAlive); + + // Should be playing the same reel. + if (rsd->SavedActorInfo[i].presFilm != 0) { + if (!actorAlive(rsd->SavedActorInfo[i].actorID)) + continue; + + playThisReel(rsd->SavedActorInfo[i].presFilm, rsd->SavedActorInfo[i].presRnum, rsd->SavedActorInfo[i].z, + rsd->SavedActorInfo[i].presX, rsd->SavedActorInfo[i].presY); + } + } + + RestoreAuxScales(rsd->SavedMoverInfo); + for (int i = 0; i < MAX_MOVERS; i++) { + if (rsd->SavedMoverInfo[i].MActorState == NORM_MACTOR) + stand(rsd->SavedMoverInfo[i].actorID, rsd->SavedMoverInfo[i].objx, + rsd->SavedMoverInfo[i].objy, rsd->SavedMoverInfo[i].lastfilm); + } +} + + +//--------------------------------------------------------------------------- + +void ResumeInterprets(SAVED_DATA *rsd) { + // Master script only affected on restore game, not restore scene + if (rsd == &sgData) { + KillMatchingProcess(PID_MASTER_SCR, -1); + FreeMasterInterpretContext(); + } + + for (int i = 0; i < MAX_INTERPRET; i++) { + switch (rsd->SavedICInfo[i].GSort) { + case GS_NONE: + break; + + case GS_INVENTORY: + if (rsd->SavedICInfo[i].event != POINTED) { + RestoreProcess(&rsd->SavedICInfo[i]); + } + break; + + case GS_MASTER: + // Master script only affected on restore game, not restore scene + if (rsd == &sgData) + RestoreMasterProcess(&rsd->SavedICInfo[i]); + break; + + case GS_ACTOR: + RestoreActorProcess(rsd->SavedICInfo[i].actorid, &rsd->SavedICInfo[i]); + break; + + case GS_POLYGON: + case GS_SCENE: + RestoreProcess(&rsd->SavedICInfo[i]); + break; + } + } +} + +/** + * Do restore scene + * @param n Id + */ +static int DoRestoreScene(SAVED_DATA *rsd, int n) { + switch (n) { + case RS_COUNT + COUNTOUT_COUNT: + // Trigger pre-load and fade and start countdown + FadeOutFast(NULL); + break; + + case RS_COUNT: + _vm->_sound->stopAllSamples(); + ClearScreen(0L); + RestoreDeadPolys(rsd->SavedDeadPolys); + NewScene(rsd->SavedSceneHandle, NO_ENTRY_NUM); + SetDoFadeIn(!bNoFade); + bNoFade = false; + startupBackground(rsd->SavedBgroundHandle); + KillScroll(); + PlayfieldSetPos(FIELD_WORLD, rsd->SavedLoffset, rsd->SavedToffset); + bNoBlocking = rsd->SavedNoBlocking; + RestoreNoScrollData(&rsd->SavedNoScrollData); +/* + break; + + case RS_COUNT - 1: +*/ + sortActors(rsd); + break; + + case 2: + break; + + case 1: + RestoreMidiFacts(rsd->SavedMidi, rsd->SavedLoop); + if (rsd->SavedControl) + control(CONTROL_ON); // TOKEN_CONTROL was free + ResumeInterprets(rsd); + } + + return n - 1; +} + +/** + * Restore game + * @param num num + */ +void RestoreGame(int num) { + KillInventory(); + + RequestRestoreGame(num, &sgData, &savedSceneCount, s_ssData); + + // Actual restoring is performed by ProcessSRQueue +} + +/** + * Save game + * @param name Name of savegame + * @param desc Description of savegame + */ +void SaveGame(char *name, char *desc) { + // Get current scene data + SaveScene(&sgData); + + RequestSaveGame(name, desc, &sgData, &savedSceneCount, s_ssData); + + // Actual saving is performed by ProcessSRQueue +} + + +//--------------------------------------------------------------------------------- + +bool IsRestoringScene() { + if (s_restoreSceneCount) { + s_restoreSceneCount = DoRestoreScene(s_rsd, s_restoreSceneCount); + } + + return s_restoreSceneCount ? true : false; +} + +/** + * Please Restore Scene + */ +void PleaseRestoreScene(bool bFade) { + // only called by restore_scene PCODE + if (s_restoreSceneCount == 0) { + assert(savedSceneCount >= 1); // No saved scene to restore + + if (ASceneIsSaved) + RestoreScene(&s_ssData[--savedSceneCount], bFade); + if (!bFade) + bNoFade = true; + } +} + +/** + * Please Save Scene + */ +void PleaseSaveScene(CORO_PARAM) { + // only called by save_scene PCODE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + assert(savedSceneCount < MAX_NEST); // nesting limit reached + + // Don't save the same thing multiple times! + // FIXME/TODO: Maybe this can be changed to an assert? + if (savedSceneCount && s_ssData[savedSceneCount-1].SavedSceneHandle == GetSceneHandle()) + CORO_KILL_SELF(); + + SaveScene(&s_ssData[savedSceneCount++]); + + CORO_END_CODE; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/savescn.h b/engines/tinsel/savescn.h new file mode 100644 index 0000000000..a999c9bffa --- /dev/null +++ b/engines/tinsel/savescn.h @@ -0,0 +1,103 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Should really be called "moving actors.h" + */ + +#ifndef TINSEL_SAVESCN_H +#define TINSEL_SAVESCN_H + +#include <time.h> // for time_t struct + +#include "tinsel/actors.h" // SAVED_ACTOR +#include "tinsel/dw.h" // SCNHANDLE +#include "tinsel/rince.h" // SAVED_MOVER +#include "tinsel/pcode.h" // INT_CONTEXT +#include "tinsel/scroll.h" // SCROLLDATA + +namespace Tinsel { + +enum { + SG_DESC_LEN = 40, // Max. saved game description length + MAX_SFILES = 30, + + // FIXME: Save file names in ScummVM can be longer than 8.3, overflowing the + // name field in savedFiles. Raising it to 256 as a preliminary fix. + FNAMELEN = 256 // 8.3 +}; + +struct SFILES { + char name[FNAMELEN]; + char desc[SG_DESC_LEN + 2]; + struct tm dateTime; +}; + +struct SAVED_DATA { + SCNHANDLE SavedSceneHandle; // Scene handle + SCNHANDLE SavedBgroundHandle; // Background handle + SAVED_MOVER SavedMoverInfo[MAX_MOVERS]; // Moving actors + SAVED_ACTOR SavedActorInfo[MAX_SAVED_ACTORS]; // } Actors + int NumSavedActors; // } + int SavedLoffset, SavedToffset; // Screen offsets + INT_CONTEXT SavedICInfo[MAX_INTERPRET]; // Interpret contexts + bool SavedDeadPolys[MAX_POLY]; + bool SavedControl; + SCNHANDLE SavedMidi; // } + bool SavedLoop; // } Midi + bool SavedNoBlocking; + SCROLLDATA SavedNoScrollData; +}; + + +enum SRSTATE { + SR_IDLE, SR_DORESTORE, SR_DONERESTORE, + SR_DOSAVE, SR_DONESAVE, SR_ABORTED +}; + +void PleaseRestoreScene(bool bFade); +void PleaseSaveScene(CORO_PARAM); + +bool IsRestoringScene(); + + +enum letype{ + LE_NAME, LE_DESC +}; + +char *ListEntry(int i, letype which); +int getList(void); + +void RestoreGame(int num); +void SaveGame(char *name, char *desc); + +void ProcessSRQueue(void); + +void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *ssCount, SAVED_DATA *ssData); +void RequestRestoreGame(int num, SAVED_DATA *sd, int *ssCount, SAVED_DATA *ssData); + +void InitialiseSs(void); +void FreeSs(void); + +} // end of namespace Tinsel + +#endif /* TINSEL_SAVESCN_H */ diff --git a/engines/tinsel/scene.cpp b/engines/tinsel/scene.cpp new file mode 100644 index 0000000000..006efd7b25 --- /dev/null +++ b/engines/tinsel/scene.cpp @@ -0,0 +1,308 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Starts up new scenes. + */ + +#include "tinsel/actors.h" +#include "tinsel/anim.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/film.h" +#include "tinsel/move.h" +#include "tinsel/rince.h" +#include "tinsel/sched.h" +#include "tinsel/scn.h" +#include "tinsel/scroll.h" +#include "tinsel/sound.h" // stopAllSamples() +#include "tinsel/object.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" // process IDs +#include "tinsel/token.h" + + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern void DropBackground(void); + +// in EFFECT.C +extern void EffectPolyProcess(CORO_PARAM); + +// in PDISPLAY.C +#ifdef DEBUG +extern void CursorPositionProcess(CORO_PARAM); +#endif +extern void TagProcess(CORO_PARAM); +extern void PointProcess(CORO_PARAM); +extern void EnableTags(void); + + +//----------------- LOCAL DEFINES -------------------- + +#include "common/pack-start.h" // START STRUCT PACKING + +/** scene structure - one per scene */ +struct SCENE_STRUC { + int32 numEntrance; //!< number of entrances in this scene + int32 numPoly; //!< number of various polygons in this scene + int32 numActor; //!< number of actors in this scene + int32 defRefer; //!< Default refer direction + SCNHANDLE hSceneScript; //!< handle to scene script + SCNHANDLE hEntrance; //!< handle to table of entrances + SCNHANDLE hPoly; //!< handle to table of polygons + SCNHANDLE hActor; //!< handle to table of actors +} PACKED_STRUCT; + +/** entrance structure - one per entrance */ +struct ENTRANCE_STRUC { + int32 eNumber; //!< entrance number + SCNHANDLE hScript; //!< handle to entrance script +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + + +//----------------- LOCAL GLOBAL DATA -------------------- + +#ifdef DEBUG +static bool ShowPosition = false; // Set when showpos() has been called +#endif + +static SCNHANDLE SceneHandle = 0; // Current scene handle - stored in case of Save_Scene() + + +/** + * Started up for scene script and entrance script. + */ +static void SceneTinselProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + PINT_CONTEXT pic; + CORO_END_CONTEXT(_ctx); + + // get the stuff copied to process when it was created + SCNHANDLE *ss = (SCNHANDLE *)ProcessGetParamsSelf(); + assert(*ss); // Must have some code to run + + CORO_BEGIN_CODE(_ctx); + + _ctx->pic = InitInterpretContext(GS_SCENE, READ_LE_UINT32(ss), NOEVENT, NOPOLY, 0, NULL); + CORO_INVOKE_1(Interpret, _ctx->pic); + + CORO_END_CODE; +} + +/** + * Get the SCENE_STRUC + * Initialise polygons for the scene + * Initialise the actors for this scene + * Run the appropriate entrance code (if any) + * Get the default refer type + */ +static void LoadScene(SCNHANDLE scene, int entry) { + const SCENE_STRUC *ss; + const ENTRANCE_STRUC *es; + uint i; + + // Scene structure + SceneHandle = scene; // Save scene handle in case of Save_Scene() + + LockMem(SceneHandle); // Make sure scene is loaded + LockScene(SceneHandle); // Prevent current scene from being discarded + + ss = (const SCENE_STRUC *)FindChunk(scene, CHUNK_SCENE); + assert(ss != NULL); + + // Initialise all the polygons for this scene + InitPolygons(FROM_LE_32(ss->hPoly), FROM_LE_32(ss->numPoly), (entry == NO_ENTRY_NUM)); + + // Initialise the actors for this scene + StartActors(FROM_LE_32(ss->hActor), FROM_LE_32(ss->numActor), (entry != NO_ENTRY_NUM)); + + if (entry != NO_ENTRY_NUM) { + + // Run the appropriate entrance code (if any) + es = (const ENTRANCE_STRUC *)LockMem(FROM_LE_32(ss->hEntrance)); + for (i = 0; i < FROM_LE_32(ss->numEntrance); i++, es++) { + if (FROM_LE_32(es->eNumber) == (uint)entry) { + if (es->hScript) + CoroutineInstall(PID_TCODE, SceneTinselProcess, &es->hScript, sizeof(es->hScript)); + break; + } + } + + if (i == FROM_LE_32(ss->numEntrance)) + error("Non-existant scene entry number"); + + if (ss->hSceneScript) + CoroutineInstall(PID_TCODE, SceneTinselProcess, &ss->hSceneScript, sizeof(ss->hSceneScript)); + } + + // Default refer type + SetDefaultRefer(FROM_LE_32(ss->defRefer)); +} + + +/** + * Wrap up the last scene. + */ +void EndScene(void) { + if (SceneHandle != 0) { + UnlockScene(SceneHandle); + SceneHandle = 0; + } + + KillInventory(); // Close down any open inventory + + DropPolygons(); // No polygons + DropNoScrolls(); // No no-scrolls + DropBackground(); // No background + DropMActors(); // No moving actors + DropCursor(); // No cursor + DropActors(); // No actor reels running + FreeAllTokens(); // No-one has tokens + FreeMostInterpretContexts(); // Only master script still interpreting + + _vm->_sound->stopAllSamples(); // Kill off any still-running sample + + // init the palette manager + ResetPalAllocator(); + + // init the object manager + KillAllObjects(); + + // kill all destructable process + KillMatchingProcess(PID_DESTROY, PID_DESTROY); +} + +/** + * + */ +void PrimeBackground(void) +{ + // structure for playfields + static PLAYFIELD playfield[] = { + { // FIELD WORLD + NULL, // no modules + NULL, // display list + 0, // init field x + 0, // init field y + 0, // x vel + 0, // y vel + Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // clip rect + false // moved flag + }, + { // FIELD STATUS + NULL, // no modules + NULL, // display list + 0, // init field x + 0, // init field y + 0, // x vel + 0, // y vel + Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // clip rect + false // moved flag + } + }; + + // structure for background + static BACKGND backgnd = { + BLACK, // sky colour + Common::Point(0, 0), // initial world pos + Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // scroll limits + 0, // no background update process + NULL, // no x scroll table + NULL, // no y scroll table + 2, // 2 playfields + playfield, // playfield pointer + false // no auto-erase + }; + + InitBackground(&backgnd); +} + +/** + * Start up the standard stuff for the next scene. + */ + +void PrimeScene(void) { + + bNoBlocking = false; + + RestartCursor(); // Restart the cursor + EnableTags(); // Next scene with tags enabled + + CoroutineInstall(PID_SCROLL, ScrollProcess, NULL, 0); + CoroutineInstall(PID_SCROLL, EffectPolyProcess, NULL, 0); + +#ifdef DEBUG + if (ShowPosition) + CoroutineInstall(PID_POSITION, CursorPositionProcess, NULL, 0); +#endif + + CoroutineInstall(PID_TAG, TagProcess, NULL, 0); + CoroutineInstall(PID_TAG, PointProcess, NULL, 0); + + // init the current background + PrimeBackground(); +} + +/** + * Wrap up the last scene and start up the next scene. + */ + +void NewScene(SCNHANDLE scene, int entry) { + EndScene(); // Wrap up the last scene. + + PrimeScene(); // Start up the standard stuff for the next scene. + + LoadScene(scene, entry); +} + +#ifdef DEBUG +/** + * Sets the ShowPosition flag, causing the cursor position process to be + * created in each scene. + */ + +void setshowpos(void) { + ShowPosition = true; +} +#endif + +/** + * Return the current scene handle. + */ + +SCNHANDLE GetSceneHandle(void) { + return SceneHandle; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/scene.h b/engines/tinsel/scene.h new file mode 100644 index 0000000000..baecac40b9 --- /dev/null +++ b/engines/tinsel/scene.h @@ -0,0 +1,79 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Scene parsing defines + */ + +#ifndef TINSEL_SCENE_H +#define TINSEL_SCENE_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +enum { + MAX_NODES = 32, //!< maximum nodes in a Node Path + MAX_NOSCROLL = 16, //!< maximum number of NoScroll commands in a scene + MAX_ENTRANCE = 25, //!< maximum number of entrances in a scene + MAX_POLY = 256, //!< maximum number of polygons in a scene + MAX_ACTOR = 32 //!< maximum number of actors in a scene +}; + +/** reference direction */ +enum REFTYPE { + REF_DEFAULT, REF_UP, REF_DOWN, REF_LEFT, REF_RIGHT, REF_POINT +}; + +enum TFTYPE { + TF_NONE, TF_UP, TF_DOWN, TF_LEFT, TF_RIGHT, TF_BOGUS +}; + +/** different actor masks */ +enum MASK_TYPE{ + ACT_DEFAULT, + ACT_MASK = -1, + ACT_ALWAYS = -2 +}; + +/** different types of polygon */ +enum POLY_TYPE { + POLY_PATH, POLY_NPATH, POLY_BLOCK, POLY_REFER, POLY_EFFECT, + POLY_EXIT, POLY_TAG +}; + +/** different scales */ +enum SCALE { + SCALE_DEFAULT, SCALE_LARGE, SCALE_MEDIUM, SCALE_SMALL, + SCALE_COMPACT, SCALE_TINY, + SCALE_AUX1, SCALE_AUX2, SCALE_AUX3, + SCALE_AUX4, SCALE_AUX5 +}; + +/** different reels */ +enum REEL { + REEL_DEFAULT, REEL_ALL, REEL_HORIZ, REEL_VERT +}; + +} // end of namespace Tinsel + +#endif // TINSEL_SCENE_H diff --git a/engines/tinsel/sched.cpp b/engines/tinsel/sched.cpp new file mode 100644 index 0000000000..9391805040 --- /dev/null +++ b/engines/tinsel/sched.cpp @@ -0,0 +1,344 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Process scheduler. + */ + +#include "tinsel/sched.h" + +#include "common/util.h" + +namespace Tinsel { + + +/** list of all processes */ +static PROCESS *processList = 0; + +/** active process list - also saves scheduler state */ +static PROCESS active; + +/** pointer to free process list */ +static PROCESS *pFreeProcesses = 0; + +/** the currently active process */ +static PROCESS *pCurrent = 0; + +#ifdef DEBUG +// diagnostic process counters +static int numProcs = 0; +static int maxProcs = 0; +#endif + +/** + * Called from ProcessKill() to enable other resources + * a process may be allocated to be released. + */ +static VFPTRPP pRCfunction = 0; + + +/** + * Kills all processes and places them on the free list. + */ +void InitScheduler(void) { + +#ifdef DEBUG + // clear number of process in use + numProcs = 0; +#endif + + if (processList == NULL) { + // first time - allocate memory for process list + processList = (PROCESS *)calloc(NUM_PROCESS, sizeof(PROCESS)); + + // make sure memory allocated + if (processList == NULL) { + error("Cannot allocate memory for process data"); + } + + // fill with garbage + memset(processList, 'S', NUM_PROCESS * sizeof(PROCESS)); + } + + // no active processes + pCurrent = active.pNext = NULL; + + // place first process on free list + pFreeProcesses = processList; + + // link all other processes after first + for (int i = 1; i < NUM_PROCESS; i++) { + processList[i - 1].pNext = processList + i; + } + + // null the last process + processList[NUM_PROCESS - 1].pNext = NULL; +} + +void FreeProcessList(void) { + if (processList) { + free(processList); + processList = NULL; + } +} + + +#ifdef DEBUG +/** + * Shows the maximum number of process used at once. + */ +void ProcessStats(void) { + printf("%i process of %i used.\n", maxProcs, NUM_PROCESS); +} +#endif + + +/** + * Give all active processes a chance to run + */ +void Scheduler(void) { + // start dispatching active process list + PROCESS *pPrevProc = &active; + PROCESS *pProc = active.pNext; + while (pProc != NULL) { + if (--pProc->sleepTime <= 0) { + // process is ready for dispatch, activate it + pCurrent = pProc; + pProc->coroAddr(pProc->state); + pCurrent = NULL; + if (!pProc->state || pProc->state->_sleep <= 0) { + // Coroutine finished + ProcessKill(pProc); + pProc = pPrevProc; + } else { + pProc->sleepTime = pProc->state->_sleep; + } + } + pPrevProc = pProc; + pProc = pProc->pNext; + } +} + + +/** + * Creates a new process. + * + * @param pid process identifier + * @param CORO_ADDR coroutine start address + * @param pParam process specific info + * @param sizeParam size of process specific info + */ +PROCESS *CoroutineInstall(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam) { + PROCESS *pProc; + + // get a free process + pProc = pFreeProcesses; + + // trap no free process + assert(pProc != NULL); // Out of processes + +#ifdef DEBUG + // one more process in use + if (++numProcs > maxProcs) + maxProcs = numProcs; +#endif + + // get link to next free process + pFreeProcesses = pProc->pNext; + + if (pCurrent != NULL) { + // place new process before the next active process + pProc->pNext = pCurrent->pNext; + + // make this new process the next active process + pCurrent->pNext = pProc; + } else { // no active processes, place process at head of list + pProc->pNext = active.pNext; + active.pNext = pProc; + } + + // set coroutine entry point + pProc->coroAddr = coroAddr; + + // clear coroutine state + pProc->state = 0; + + // wake process up as soon as possible + pProc->sleepTime = 1; + + // set new process id + pProc->pid = pid; + + // set new process specific info + if (sizeParam) { + assert(sizeParam > 0 && sizeParam <= PARAM_SIZE); + + // set new process specific info + memcpy(pProc->param, pParam, sizeParam); + } + + + // return created process + return pProc; +} + +/** + * Kills the specified process. + * + * @param pKillProc which process to kill + */ +void ProcessKill(PROCESS *pKillProc) { + PROCESS *pProc, *pPrev; // process list pointers + + // make sure a valid process pointer + assert(pKillProc >= processList && pKillProc <= processList + NUM_PROCESS - 1); + + // can not kill the current process using ProcessKill ! + assert(pCurrent != pKillProc); + +#ifdef DEBUG + // one less process in use + --numProcs; + assert(numProcs >= 0); +#endif + + // search the active list for the process + for (pProc = active.pNext, pPrev = &active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) { + if (pProc == pKillProc) { + // found process in active list + + // Free process' resources + if (pRCfunction != NULL) + (pRCfunction)(pProc); + + delete pProc->state; + + // make prev point to next to unlink pProc + pPrev->pNext = pProc->pNext; + + // link first free process after pProc + pProc->pNext = pFreeProcesses; + + // make pProc the first free process + pFreeProcesses = pProc; + + return; + } + } + + // process not found in active list if we get to here + error("ProcessKill(): tried to kill a process not in the list of active processes"); +} + + + +/** + * Returns a pointer to the currently running process. + */ +PROCESS *CurrentProcess(void) { + return pCurrent; +} + +char *ProcessGetParamsSelf() { + PROCESS *pProc = pCurrent; + + // make sure a valid process pointer + assert(pProc >= processList && pProc <= processList + NUM_PROCESS - 1); + + return pProc->param; +} + +/** + * Returns the process identifier of the specified process. + * + * @param pProc which process + */ +int ProcessGetPID(PROCESS *pProc) { + // make sure a valid process pointer + assert(pProc >= processList && pProc <= processList + NUM_PROCESS - 1); + + // return processes PID + return pProc->pid; +} + +/** + * Kills any process matching the specified PID. The current + * process cannot be killed. + * + * @param pidKill process identifier of process to kill + * @param pidMask mask to apply to process identifiers before comparison + * @return The number of processes killed is returned. + */ +int KillMatchingProcess(int pidKill, int pidMask) { + int numKilled = 0; + PROCESS *pProc, *pPrev; // process list pointers + + for (pProc = active.pNext, pPrev = &active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) { + if ((pProc->pid & pidMask) == pidKill) { + // found a matching process + + // dont kill the current process + if (pProc != pCurrent) { + // kill this process + numKilled++; + + // make prev point to next to unlink pProc + pPrev->pNext = pProc->pNext; + + // link first free process after pProc + pProc->pNext = pFreeProcesses; + + // make pProc the first free process + pFreeProcesses = pProc; + + // set to a process on the active list + pProc = pPrev; + } + } + } + +#ifdef DEBUG + // adjust process in use + numProcs -= numKilled; + assert(numProcs >= 0); +#endif + + // return number of processes killed + return numKilled; +} + + + +/** + * Set pointer to a function to be called by ProcessKill(). + * + * May be called by a resource allocator, the function supplied is + * called by ProcessKill() to allow the resource allocator to free + * resources allocated to the dying process. + * + * @param pFunc Function to be called by ProcessKill() + */ +void SetResourceCallback(VFPTRPP pFunc) { + pRCfunction = pFunc; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/sched.h b/engines/tinsel/sched.h new file mode 100644 index 0000000000..6b89a60891 --- /dev/null +++ b/engines/tinsel/sched.h @@ -0,0 +1,100 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Data structures used by the process scheduler + */ + +#ifndef TINSEL_SCHED_H // prevent multiple includes +#define TINSEL_SCHED_H + +#include "tinsel/dw.h" // new data types +#include "tinsel/coroutine.h" + +namespace Tinsel { + +// the size of process specific info +#define PARAM_SIZE 32 + +// the maximum number of processes +#define NUM_PROCESS 64 + +typedef void (*CORO_ADDR)(CoroContext &); + + +// process structure + +struct PROCESS { + PROCESS *pNext; // pointer to next process in active or free list + + CoroContext state; // the state of the coroutine + CORO_ADDR coroAddr; // the entry point of the coroutine + + int sleepTime; // number of scheduler cycles to sleep + int pid; // process ID + char param[PARAM_SIZE]; // process specific info +}; + + +/*----------------------------------------------------------------------*\ +|* Scheduler Function Prototypes *| +\*----------------------------------------------------------------------*/ + +void InitScheduler(void); // called to init scheduler - kills all processes and places them on free list + +void FreeProcessList(void); + +#ifdef DEBUG +void ProcessStats(void); // Shows the maximum number of process used at once +#endif + +void Scheduler(void); // called to start process dispatching + +PROCESS *CoroutineInstall(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam); + +void ProcessKill( // kill a process + PROCESS *pKillProc); // which process to kill (must be different from current one) + +PROCESS *CurrentProcess(void); // Returns a pointer to the currently running process + +int ProcessGetPID( // Returns the process identifier of the specified process + PROCESS *pProc); // which process + +char *ProcessGetParamsSelf(); + +int KillMatchingProcess( // kill any process matching the pid parameters + int pidKill, // process identifier of process to kill + int pidMask); // mask to apply to process identifiers before comparison + + +// Pointer to a function of the form "void function(PPROCESS)" +typedef void (*VFPTRPP)(PROCESS *); + +void SetResourceCallback(VFPTRPP pFunc); // May be called by a resource allocator, + // the function supplied is called by ProcessKill() + // to allow the resource allocator to free resources + // allocated to the dying process. + + +} // end of namespace Tinsel + +#endif // TINSEL_SCHED_H diff --git a/engines/tinsel/scn.cpp b/engines/tinsel/scn.cpp new file mode 100644 index 0000000000..b14b1c5962 --- /dev/null +++ b/engines/tinsel/scn.cpp @@ -0,0 +1,80 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * A (some would say very) small collection of utility functions. + */ + +#include "common/endian.h" +#include "common/util.h" + +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/handle.h" +#include "tinsel/multiobj.h" +#include "tinsel/scn.h" +#include "tinsel/tinsel.h" // for _vm + +namespace Tinsel { + +/** + * Given a scene handle and a chunk id, gets the scene in RAM and + * locates the requested chunk. + * @param handle Scene handle + * @param chunk Chunk Id + */ +byte *FindChunk(SCNHANDLE handle, uint32 chunk) { + byte *bptr = LockMem(handle); + uint32 *lptr = (uint32 *)bptr; + uint32 add; + + // V1 chunk types can be found by substracting 2 from the + // chunk type. Note that CHUNK_STRING and CHUNK_BITMAP are + // the same in V1 and V2 + if (_vm->getVersion() == TINSEL_V1 && + chunk != CHUNK_STRING && chunk != CHUNK_BITMAP) + chunk -= 0x2L; + + while (1) { + if (READ_LE_UINT32(lptr) == chunk) + return (byte *)(lptr + 2); + + ++lptr; + add = READ_LE_UINT32(lptr); + if (!add) + return NULL; + + lptr = (uint32 *)(bptr + add); + } +} + +/** + * Get the actor id from a film (column 0) + */ +int extractActor(SCNHANDLE film) { + const FILM *pfilm = (const FILM *)LockMem(film); + const FREEL *preel = &pfilm->reels[0]; + const MULTI_INIT *pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(preel->mobj)); + return (int)FROM_LE_32(pmi->mulID); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/scn.h b/engines/tinsel/scn.h new file mode 100644 index 0000000000..29f3dc51fc --- /dev/null +++ b/engines/tinsel/scn.h @@ -0,0 +1,68 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_SCN_H // prevent multiple includes +#define TINSEL_SCN_H + +#include "tinsel/dw.h" + +namespace Tinsel { + + +// chunk identifier numbers + +// V2 chunks + +#define CHUNK_STRING 0x33340001L // same in V1 and V2 +#define CHUNK_BITMAP 0x33340002L // same in V1 and V2 +#define CHUNK_CHARPTR 0x33340003L // not used! +#define CHUNK_CHARMATRIX 0x33340004L // not used! +#define CHUNK_PALETTE 0x33340005L // not used! +#define CHUNK_IMAGE 0x33340006L // not used! +#define CHUNK_ANI_FRAME 0x33340007L // not used! +#define CHUNK_FILM 0x33340008L // not used! +#define CHUNK_FONT 0x33340009L // not used! +#define CHUNK_PCODE 0x3334000AL +#define CHUNK_ENTRANCE 0x3334000BL // not used! +#define CHUNK_POLYGONS 0x3334000CL // not used! +#define CHUNK_ACTORS 0x3334000DL // not used! +#define CHUNK_SCENE 0x3334000EL +#define CHUNK_TOTAL_ACTORS 0x3334000FL +#define CHUNK_TOTAL_GLOBALS 0x33340010L +#define CHUNK_TOTAL_OBJECTS 0x33340011L +#define CHUNK_OBJECTS 0x33340012L +#define CHUNK_MIDI 0x33340013L // not used! +#define CHUNK_SAMPLE 0x33340014L // not used! +#define CHUNK_TOTAL_POLY 0x33340015L +#define CHUNK_MBSTRING 0x33340022L // Multi-byte characters + +#define INDEX_FILENAME "index" // name of index file + +byte *FindChunk(SCNHANDLE handle, uint32 chunk); +int extractActor(SCNHANDLE film); + +} // end of namespace Tinsel + +#endif /* TINSEL_SCN_H */ diff --git a/engines/tinsel/scroll.cpp b/engines/tinsel/scroll.cpp new file mode 100644 index 0000000000..7c0b12730f --- /dev/null +++ b/engines/tinsel/scroll.cpp @@ -0,0 +1,432 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Handles scrolling + */ + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/graphics.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/scroll.h" +#include "tinsel/sched.h" + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern int BackgroundWidth(void); +extern int BackgroundHeight(void); + + + +//----------------- LOCAL DEFINES -------------------- + +#define LEFT 'L' +#define RIGHT 'R' +#define UP 'U' +#define DOWN 'D' + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static int LeftScroll = 0, DownScroll = 0; // Number of iterations outstanding + +static int scrollActor = 0; +static PMACTOR psActor = 0; +static int oldx = 0, oldy = 0; + +/** Boundaries and numbers of boundaries */ +static SCROLLDATA sd = { + { + {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, + {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0} + }, + { + {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, + {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0} + }, + 0, + 0 + }; + +static int ImageH = 0, ImageW = 0; + +static bool ScrollCursor = 0; // If a TAG or EXIT polygon is clicked on, + // the cursor is kept over that polygon + // whilst scrolling + +static int scrollPixels = SCROLLPIXELS; + + +/** + * Reset the ScrollCursor flag + */ +void DontScrollCursor(void) { + ScrollCursor = false; +} + +/** + * Set the ScrollCursor flag + */ +void DoScrollCursor(void) { + ScrollCursor = true; +} + +/** + * Configure a no-scroll boundary for a scene. + */ +void SetNoScroll(int x1, int y1, int x2, int y2) { + if (x1 == x2) { + /* Vertical line */ + assert(sd.NumNoH < MAX_HNOSCROLL); + + sd.NoHScroll[sd.NumNoH].ln = x1; // X pos of vertical line + sd.NoHScroll[sd.NumNoH].c1 = y1; + sd.NoHScroll[sd.NumNoH].c2 = y2; + sd.NumNoH++; + } else if (y1 == y2) { + /* Horizontal line */ + assert(sd.NumNoV < MAX_VNOSCROLL); + + sd.NoVScroll[sd.NumNoV].ln = y1; // Y pos of horizontal line + sd.NoVScroll[sd.NumNoV].c1 = x1; + sd.NoVScroll[sd.NumNoV].c2 = x2; + sd.NumNoV++; + } else { + /* No-scroll lines must be horizontal or vertical */ + } +} + +/** + * Does the obvious - called at the end of a scene. + */ +void DropNoScrolls(void) { + sd.NumNoH = sd.NumNoV = 0; +} + +/** + * Called from scroll process when it thinks that a scroll is in order. + * Checks for no-scroll boundaries and sets off a scroll if allowed. + */ +static void NeedScroll(int direction) { + uint i; + int BottomLine, RightCol; + int Loffset, Toffset; + + // get background offsets + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + switch (direction) { + case LEFT: /* Picture will go left, 'camera' right */ + + BottomLine = Toffset + (SCREEN_HEIGHT - 1); + RightCol = Loffset + (SCREEN_WIDTH - 1); + + for (i = 0; i < sd.NumNoH; i++) { + if (RightCol >= sd.NoHScroll[i].ln - 1 && RightCol <= sd.NoHScroll[i].ln + 1 && + ((sd.NoHScroll[i].c1 >= Toffset && sd.NoHScroll[i].c1 <= BottomLine) || + (sd.NoHScroll[i].c2 >= Toffset && sd.NoHScroll[i].c2 <= BottomLine) || + (sd.NoHScroll[i].c1 < Toffset && sd.NoHScroll[i].c2 > BottomLine))) + return; + } + + if (LeftScroll <= 0) { + scrollPixels = SCROLLPIXELS; + LeftScroll = RLSCROLL; + } + break; + + case RIGHT: /* Picture will go right, 'camera' left */ + + BottomLine = Toffset + (SCREEN_HEIGHT - 1); + + for (i = 0; i < sd.NumNoH; i++) { + if (Loffset >= sd.NoHScroll[i].ln - 1 && Loffset <= sd.NoHScroll[i].ln + 1 && + ((sd.NoHScroll[i].c1 >= Toffset && sd.NoHScroll[i].c1 <= BottomLine) || + (sd.NoHScroll[i].c2 >= Toffset && sd.NoHScroll[i].c2 <= BottomLine) || + (sd.NoHScroll[i].c1 < Toffset && sd.NoHScroll[i].c2 > BottomLine))) + return; + } + + if (LeftScroll >= 0) { + scrollPixels = SCROLLPIXELS; + LeftScroll = -RLSCROLL; + } + break; + + case UP: /* Picture will go upwards, 'camera' downwards */ + + BottomLine = Toffset + (SCREEN_HEIGHT - 1); + RightCol = Loffset + (SCREEN_WIDTH - 1); + + for (i = 0; i < sd.NumNoV; i++) { + if ((BottomLine >= sd.NoVScroll[i].ln - 1 && BottomLine <= sd.NoVScroll[i].ln + 1) && + ((sd.NoVScroll[i].c1 >= Loffset && sd.NoVScroll[i].c1 <= RightCol) || + (sd.NoVScroll[i].c2 >= Loffset && sd.NoVScroll[i].c2 <= RightCol) || + (sd.NoVScroll[i].c1 < Loffset && sd.NoVScroll[i].c2 > RightCol))) + return; + } + + if (DownScroll <= 0) { + scrollPixels = SCROLLPIXELS; + DownScroll = UDSCROLL; + } + break; + + case DOWN: /* Picture will go downwards, 'camera' upwards */ + + RightCol = Loffset + (SCREEN_WIDTH - 1); + + for (i = 0; i < sd.NumNoV; i++) { + if (Toffset >= sd.NoVScroll[i].ln - 1 && Toffset <= sd.NoVScroll[i].ln + 1 && + ((sd.NoVScroll[i].c1 >= Loffset && sd.NoVScroll[i].c1 <= RightCol) || + (sd.NoVScroll[i].c2 >= Loffset && sd.NoVScroll[i].c2 <= RightCol) || + (sd.NoVScroll[i].c1 < Loffset && sd.NoVScroll[i].c2 > RightCol))) + return; + } + + if (DownScroll >= 0) { + scrollPixels = SCROLLPIXELS; + DownScroll = -UDSCROLL; + } + break; + } +} + +/** + * Called from scroll process - Scrolls the image as appropriate. + */ +static void ScrollImage(void) { + int OldLoffset = 0, OldToffset = 0; // Used when keeping cursor on a tag + int Loffset, Toffset; + int curX, curY; + + // get background offsets + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + /* + * Keeping cursor on a tag? + */ + if (ScrollCursor) { + GetCursorXY(&curX, &curY, true); + if (InPolygon(curX, curY, TAG) != NOPOLY || InPolygon(curX, curY, EXIT) != NOPOLY) { + OldLoffset = Loffset; + OldToffset = Toffset; + } else + ScrollCursor = false; + } + + /* + * Horizontal scrolling + */ + if (LeftScroll > 0) { + LeftScroll -= scrollPixels; + if (LeftScroll < 0) { + Loffset += LeftScroll; + LeftScroll = 0; + } + Loffset += scrollPixels; // Move right + if (Loffset > ImageW - SCREEN_WIDTH) + Loffset = ImageW - SCREEN_WIDTH;// Now at extreme right + } else if (LeftScroll < 0) { + LeftScroll += scrollPixels; + if (LeftScroll > 0) { + Loffset += LeftScroll; + LeftScroll = 0; + } + Loffset -= scrollPixels; // Move left + if (Loffset < 0) + Loffset = 0; // Now at extreme left + } + + /* + * Vertical scrolling + */ + if (DownScroll > 0) { + DownScroll -= scrollPixels; + if (DownScroll < 0) { + Toffset += DownScroll; + DownScroll = 0; + } + Toffset += scrollPixels; // Move down + + if (Toffset > ImageH - SCREEN_HEIGHT) + Toffset = ImageH - SCREEN_HEIGHT;// Now at extreme bottom + + } else if (DownScroll < 0) { + DownScroll += scrollPixels; + if (DownScroll > 0) { + Toffset += DownScroll; + DownScroll = 0; + } + Toffset -= scrollPixels; // Move up + + if (Toffset < 0) + Toffset = 0; // Now at extreme top + } + + /* + * Move cursor if keeping cursor on a tag. + */ + if (ScrollCursor) + AdjustCursorXY(OldLoffset - Loffset, OldToffset - Toffset); + + PlayfieldSetPos(FIELD_WORLD, Loffset, Toffset); +} + + +/** + * See if the actor on whom the camera is is approaching an edge. + * Request a scroll if he is. + */ +static void MonitorScroll(void) { + int newx, newy; + int Loffset, Toffset; + + /* + * Only do it if the actor is there and is visible + */ + if (!psActor || getMActorHideState(psActor) + || getMActorState(psActor) == NO_MACTOR) + return; + + GetActorPos(scrollActor, &newx, &newy); + + if (oldx == newx && oldy == newy) + return; + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + + /* + * Approaching right side or left side of the screen? + */ + if (newx > Loffset+SCREEN_WIDTH-RLDISTANCE && Loffset < ImageW-SCREEN_WIDTH) { + if (newx > oldx) + NeedScroll(LEFT); + } else if (newx < Loffset + RLDISTANCE && Loffset) { + if (newx < oldx) + NeedScroll(RIGHT); + } + + /* + * Approaching bottom or top of the screen? + */ + if (newy > Toffset+SCREEN_HEIGHT-UDDISTANCE && Toffset < ImageH-SCREEN_HEIGHT) { + if (newy > oldy) + NeedScroll(UP); + } else if (Toffset && newy < Toffset + UDDISTANCE + GetActorBottom(scrollActor) - GetActorTop(scrollActor)) { + if (newy < oldy) + NeedScroll(DOWN); + } + + oldx = newx; + oldy = newy; +} + +/** + * Decide when to scroll and scroll when decided to. + */ +void ScrollProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + ImageH = BackgroundHeight(); // Dimensions + ImageW = BackgroundWidth(); // of this scene. + + // Give up if there'll be no purpose in this process + if (ImageW == SCREEN_WIDTH && ImageH == SCREEN_HEIGHT) + CORO_KILL_SELF(); + + LeftScroll = DownScroll = 0; // No iterations outstanding + oldx = oldy = 0; + scrollPixels = SCROLLPIXELS; + + if (!scrollActor) + scrollActor = LeadId(); + + psActor = GetMover(scrollActor); + + while (1) { + MonitorScroll(); // Set scroll requirement + + if (LeftScroll || DownScroll) // Scroll if required + ScrollImage(); + + CORO_SLEEP(1); // allow re-scheduling + } + + CORO_END_CODE; +} + +/** + * Change which actor the camera is following. + */ +void ScrollFocus(int ano) { + if (scrollActor != ano) { + oldx = oldy = 0; + scrollActor = ano; + + psActor = ano ? GetMover(scrollActor) : NULL; + } +} + +/** + * Scroll to abslote position. + */ +void ScrollTo(int x, int y, int iter) { + int Loffset, Toffset; // for background offsets + + scrollPixels = iter != 0 ? iter : SCROLLPIXELS; + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); // get background offsets + + LeftScroll = x - Loffset; + DownScroll = y - Toffset; +} + +/** + * Kill of any current scroll. + */ +void KillScroll(void) { + LeftScroll = DownScroll = 0; +} + + +void GetNoScrollData(SCROLLDATA *ssd) { + memcpy(ssd, &sd, sizeof(SCROLLDATA)); +} + +void RestoreNoScrollData(SCROLLDATA *ssd) { + memcpy(&sd, ssd, sizeof(SCROLLDATA)); +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/scroll.h b/engines/tinsel/scroll.h new file mode 100644 index 0000000000..02ce2afd5b --- /dev/null +++ b/engines/tinsel/scroll.h @@ -0,0 +1,77 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_SCROLL_H // prevent multiple includes +#define TINSEL_SCROLL_H + +namespace Tinsel { + +#define SCROLLPIXELS 8 // Number of pixels to scroll per iteration + +#define RLDISTANCE 50 // Distance from edge that triggers a scroll +#define UDDISTANCE 20 + +// Number of iterations to make +#define RLSCROLL 160 // 20*8 = 160 = half a screen +#define UDSCROLL 100 // 12.5*8 = 100 = half a screen + + +// These structures defined here so boundaries can be saved +struct NOSCROLLB { + int ln; + int c1; + int c2; +}; + +#define MAX_HNOSCROLL 10 +#define MAX_VNOSCROLL 10 + +struct SCROLLDATA{ + NOSCROLLB NoVScroll[MAX_VNOSCROLL]; // Vertical no-scroll boundaries + NOSCROLLB NoHScroll[MAX_HNOSCROLL]; // Horizontal no-scroll boundaries + unsigned NumNoV, NumNoH; // Counts of no-scroll boundaries +}; + + + +void DontScrollCursor(void); +void DoScrollCursor(void); + +void SetNoScroll(int x1, int y1, int x2, int y2); +void DropNoScrolls(void); + +void ScrollProcess(CORO_PARAM); + +void ScrollFocus(int actor); +void ScrollTo(int x, int y, int iter); + +void KillScroll(void); + +void GetNoScrollData(SCROLLDATA *ssd); +void RestoreNoScrollData(SCROLLDATA *ssd); + +} // end of namespace Tinsel + +#endif /* TINSEL_SCROLL_H */ diff --git a/engines/tinsel/serializer.h b/engines/tinsel/serializer.h new file mode 100644 index 0000000000..98ee398ef8 --- /dev/null +++ b/engines/tinsel/serializer.h @@ -0,0 +1,131 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Handles timers. + */ + +#ifndef TINSEL_SERIALIZER_H +#define TINSEL_SERIALIZER_H + +#include "common/scummsys.h" +#include "common/savefile.h" + + +namespace Tinsel { + + +#define SYNC_AS(SUFFIX,TYPE,SIZE) \ + template <class T> \ + void syncAs ## SUFFIX(T &val) { \ + if (_loadStream) \ + val = static_cast<T>(_loadStream->read ## SUFFIX()); \ + else { \ + TYPE tmp = val; \ + _saveStream->write ## SUFFIX(tmp); \ + } \ + _bytesSynced += SIZE; \ + } + + +// TODO: Write comment for this +// TODO: Inspired by the SCUMM engine -- move to common/ code and use in more engines? +class Serializer { +public: + Serializer(Common::SeekableReadStream *in, Common::OutSaveFile *out) + : _loadStream(in), _saveStream(out), _bytesSynced(0) { + assert(in || out); + } + + bool isSaving() { return (_saveStream != 0); } + bool isLoading() { return (_loadStream != 0); } + + uint bytesSynced() const { return _bytesSynced; } + + void syncBytes(byte *buf, uint16 size) { + if (_loadStream) + _loadStream->read(buf, size); + else + _saveStream->write(buf, size); + _bytesSynced += size; + } + + SYNC_AS(Byte, byte, 1) + + SYNC_AS(Uint16LE, uint16, 2) + SYNC_AS(Uint16BE, uint16, 2) + SYNC_AS(Sint16LE, int16, 2) + SYNC_AS(Sint16BE, int16, 2) + + SYNC_AS(Uint32LE, uint32, 4) + SYNC_AS(Uint32BE, uint32, 4) + SYNC_AS(Sint32LE, int32, 4) + SYNC_AS(Sint32BE, int32, 4) + +protected: + Common::SeekableReadStream *_loadStream; + Common::OutSaveFile *_saveStream; + + uint _bytesSynced; +}; + +#undef SYNC_AS + +// TODO: Make a subclass "VersionedSerializer", which makes it easy to support +// multiple versions of a savegame format (again inspired by SCUMM). +/* +class VersionedSerializer : public Serializer { +public: + // "version" is the version of the savegame we are loading/creating + VersionedSerializer(Common::SeekableReadStream *in, Common::OutSaveFile *out, int version) + : Serializer(in, out), _version(version) { + assert(in || out); + } + + void syncBytes(byte *buf, uint16 size, int minVersion = 0, int maxVersion = INF) { + if (_version < minVersion || _version > maxVersion) + return; // Do nothing if too old or too new + if (_loadStream) { + _loadStream->read(buf, size); + } else { + _saveStream->write(buf, size); + } + } + ... + +}; + +*/ + +// Mixin class / interface +// TODO Maybe call it ISerializable or SerializableMixin ? +// TODO: Taken from SCUMM engine -- move to common/ code? +class Serializable { +public: + virtual ~Serializable() {} + virtual void saveLoadWithSerializer(Serializer *ser) = 0; +}; + + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/sound.cpp b/engines/tinsel/sound.cpp new file mode 100644 index 0000000000..0a3b01089f --- /dev/null +++ b/engines/tinsel/sound.cpp @@ -0,0 +1,257 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * sound functionality + */ + +#include "tinsel/sound.h" + +#include "tinsel/dw.h" +#include "tinsel/config.h" +#include "tinsel/music.h" +#include "tinsel/strres.h" +#include "tinsel/tinsel.h" + +#include "common/endian.h" +#include "common/file.h" +#include "common/system.h" + +#include "sound/mixer.h" +#include "sound/audiocd.h" + +namespace Tinsel { + +//--------------------------- General data ---------------------------------- + +// get set when music/sample driver is installed +static bool bInstalled = false; + +SoundManager::SoundManager(TinselEngine *vm) : + //_vm(vm), // TODO: Enable this once global _vm var is gone + _sampleIndex(0), _sampleIndexLen(0) { +} + +SoundManager::~SoundManager() { + free(_sampleIndex); +} + +/** + * Plays the specified sample through the sound driver. + * @param id Identifier of sample to be played + * @param type type of sound (voice or sfx) + * @param handle sound handle + */ +bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle) { + // Floppy version has no sample file + if (_vm->getFeatures() & GF_FLOPPY) + return false; + + // no sample driver? + if (!_vm->_mixer->isReady()) + return false; + + // stop any currently playing sample + _vm->_mixer->stopHandle(_handle); + + // make sure id is in range + assert(id > 0 && id < _sampleIndexLen); + + // get file offset for this sample + uint32 dwSampleIndex = _sampleIndex[id]; + + // move to correct position in the sample file + _sampleStream.seek(dwSampleIndex); + if (_sampleStream.ioFailed() || _sampleStream.pos() != dwSampleIndex) + error("File %s is corrupt", SAMPLE_FILE); + + // read the length of the sample + uint32 sampleLen = _sampleStream.readUint32LE(); + if (_sampleStream.ioFailed()) + error("File %s is corrupt", SAMPLE_FILE); + + // allocate a buffer + void *sampleBuf = malloc(sampleLen); + assert(sampleBuf); + + // read all of the sample + if (_sampleStream.read(sampleBuf, sampleLen) != sampleLen) + error("File %s is corrupt", SAMPLE_FILE); + + // FIXME: Should set this in a different place ;) + _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volSound); + //_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic); + _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volVoice); + + + // play it + _vm->_mixer->playRaw(type, &_handle, sampleBuf, sampleLen, 22050, + Audio::Mixer::FLAG_AUTOFREE | Audio::Mixer::FLAG_UNSIGNED); + + if (handle) + *handle = _handle; + + return true; +} + +/** + * Returns TRUE if there is a sample for the specified sample identifier. + * @param id Identifier of sample to be checked + */ +bool SoundManager::sampleExists(int id) { + if (_vm->_mixer->isReady()) { + // make sure id is in range + if (id > 0 && id < _sampleIndexLen) { + // check for a sample index + if (_sampleIndex[id]) + return true; + } + } + + // no sample driver or no sample + return false; +} + +/** + * Returns true if a sample is currently playing. + */ +bool SoundManager::sampleIsPlaying(void) { + return _vm->_mixer->isSoundHandleActive(_handle); +} + +/** + * Stops any currently playing sample. + */ +void SoundManager::stopAllSamples(void) { + // stop currently playing sample + _vm->_mixer->stopHandle(_handle); +} + +/** + * Opens and inits all sound sample files. + */ +void SoundManager::openSampleFiles(void) { + // Floppy and demo versions have no sample files + if (_vm->getFeatures() & GF_FLOPPY || _vm->getFeatures() & GF_DEMO) + return; + + Common::File f; + + if (_sampleIndex) + // already allocated + return; + + // open sample index file in binary mode + if (f.open(SAMPLE_INDEX)) { + // get length of index file + f.seek(0, SEEK_END); // move to end of file + _sampleIndexLen = f.pos(); // get file pointer + f.seek(0, SEEK_SET); // back to beginning + + if (_sampleIndex == NULL) { + // allocate a buffer for the indices + _sampleIndex = (uint32 *)malloc(_sampleIndexLen); + + // make sure memory allocated + if (_sampleIndex == NULL) { + // disable samples if cannot alloc buffer for indices + // TODO: Disabled sound if we can't load the sample index? + return; + } + } + + // load data + if (f.read(_sampleIndex, _sampleIndexLen) != (uint32)_sampleIndexLen) + // file must be corrupt if we get to here + error("File %s is corrupt", SAMPLE_FILE); + +#ifdef SCUMM_BIG_ENDIAN + // Convert all ids from LE to native format + for (uint i = 0; i < _sampleIndexLen / sizeof(uint32); ++i) { + _sampleIndex[i] = READ_LE_UINT32(_sampleIndex + i); + } +#endif + + // close the file + f.close(); + + // convert file size to size in DWORDs + _sampleIndexLen /= sizeof(uint32); + } else + error("Cannot find file %s", SAMPLE_INDEX); + + // open sample file in binary mode + if (!_sampleStream.open(SAMPLE_FILE)) + error("Cannot find file %s", SAMPLE_FILE); + +/* + // gen length of the largest sample + sampleBuffer.size = _sampleStream.readUint32LE(); + if (_sampleStream.ioFailed()) + error("File %s is corrupt", SAMPLE_FILE); +*/ +} + +/** + * Initialises the sound driver. + */ + +bool SoundInit(void) { + if (!bInstalled) { +// if (mDriver != NULL) { + if (true) { // TODO: Check that a MIDI music output device is available + // open MIDI files + OpenMidiFiles(); + } + + if (_vm->_mixer->isReady()) { + // open sample files + _vm->_sound->openSampleFiles(); + } + bInstalled = true; + return true; + } else { + // already installed + return false; + } +} + + +/** + * De-initialises the sound driver. + */ + +bool SoundDeinit(void) { + if (bInstalled) { + bInstalled = false; + AudioCD.stop(); + StopMidi(); + _vm->_sound->stopAllSamples(); + DeleteMidiBuffer(); + return true; + } else { + // not installed + return false; + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/sound.h b/engines/tinsel/sound.h new file mode 100644 index 0000000000..c80e7589ec --- /dev/null +++ b/engines/tinsel/sound.h @@ -0,0 +1,83 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * This file contains the Sound Driver data structures etc. + */ + +#ifndef TINSEL_SOUND_H +#define TINSEL_SOUND_H + +#include "common/file.h" +#include "common/file.h" + +#include "sound/mixer.h" + +#include "tinsel/dw.h" +#include "tinsel/tinsel.h" + +namespace Tinsel { + +#define MAXSAMPVOL 127 + +/*----------------------------------------------------------------------*\ +|* Function Prototypes *| +\*----------------------------------------------------------------------*/ + +class SoundManager { +protected: + + //TinselEngine *_vm; // TODO: Enable this once global _vm var is gone + + /** Sample handle */ + Audio::SoundHandle _handle; + + /** Sample index buffer and number of entries */ + uint32 *_sampleIndex; + + /** Number of entries in the sample index */ + long _sampleIndexLen; + + /** file stream for sample file */ + Common::File _sampleStream; + +public: + + SoundManager(TinselEngine *vm); + ~SoundManager(); + + bool playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle = 0); + void stopAllSamples(void); // Stops any currently playing sample + + bool sampleExists(int id); + bool sampleIsPlaying(void); + + // TODO: Internal method, make this protected? + void openSampleFiles(void); +}; + +bool SoundInit(void); // Initialises the sound driver +bool SoundDeinit(void); // De-initialises the sound driver + +} // end of namespace Tinsel + +#endif // TINSEL_SOUND_H diff --git a/engines/tinsel/strres.cpp b/engines/tinsel/strres.cpp new file mode 100644 index 0000000000..abf5a880f6 --- /dev/null +++ b/engines/tinsel/strres.cpp @@ -0,0 +1,209 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * String resource managment routines + */ + +#include "tinsel/dw.h" +#include "tinsel/sound.h" +#include "tinsel/strres.h" +#include "common/file.h" +#include "common/endian.h" + +namespace Tinsel { + +#ifdef DEBUG +// Diagnostic number +int newestString; +#endif + +// buffer for resource strings +static uint8 *textBuffer = 0; + +// language resource string filenames +static const char *languageFiles[] = { + "english.txt", + "french.txt", + "german.txt", + "italian.txt", + "spanish.txt" +}; + +// Set if we're handling 2-byte characters. +bool bMultiByte = false; + +/** + * Called to load a resource file for a different language + * @param newLang The new language + */ +void ChangeLanguage(LANGUAGE newLang) { + Common::File f; + uint32 textLen = 0; // length of buffer + + if (textBuffer) { + // free the previous buffer + free(textBuffer); + textBuffer = NULL; + } + + // Try and open the specified language file. If it fails, and the language + // isn't English, try falling back on opening 'english.txt' - some foreign + // language versions reused it rather than their proper filename + if (!f.open(languageFiles[newLang])) { + if ((newLang == TXT_ENGLISH) || !f.open(languageFiles[TXT_ENGLISH])) + error("Cannot find file %s", languageFiles[newLang]); + } + + // Check whether the file is compressed or not - for compressed files the + // first long is the filelength and for uncompressed files it is the chunk + // identifier + textLen = f.readUint32LE(); + if (f.ioFailed()) + error("File %s is corrupt", languageFiles[newLang]); + + if (textLen == CHUNK_STRING || textLen == CHUNK_MBSTRING) { + // the file is uncompressed + + bMultiByte = (textLen == CHUNK_MBSTRING); + + // get length of uncompressed file + textLen = f.size(); + f.seek(0, SEEK_SET); // Set to beginning of file + + if (textBuffer == NULL) { + // allocate a text buffer for the strings + textBuffer = (uint8 *)malloc(textLen); + + // make sure memory allocated + assert(textBuffer); + } + + // load data + if (f.read(textBuffer, textLen) != textLen) + // file must be corrupt if we get to here + error("File %s is corrupt", languageFiles[newLang]); + + // close the file + f.close(); + } else { // the file must be compressed + error("Compression handling has been removed!"); + } +} + +/** + * Loads a string resource identified by id. + * @param id identifier of string to be loaded + * @param pBuffer points to buffer that receives the string + * @param bufferMax maximum number of chars to be copied to the buffer + */ +int LoadStringRes(int id, char *pBuffer, int bufferMax) { +#ifdef DEBUG + // For diagnostics + newestString = id; +#endif + + // base of string resource table + uint8 *pText = textBuffer; + + // index into text resource file + uint32 index = 0; + + // number of chunks to skip + int chunkSkip = id / STRINGS_PER_CHUNK; + + // number of strings to skip when in the correct chunk + int strSkip = id % STRINGS_PER_CHUNK; + + // length of string + int len; + + // skip to the correct chunk + while (chunkSkip-- != 0) { + // make sure chunk id is correct + assert(READ_LE_UINT32(pText + index) == CHUNK_STRING || READ_LE_UINT32(pText + index) == CHUNK_MBSTRING); + + if (READ_LE_UINT32(pText + index + sizeof(uint32)) == 0) { + // TEMPORARY DIRTY BODGE + strcpy(pBuffer, "!! HIGH STRING !!"); + + // string does not exist + return 0; + } + + // get index to next chunk + index = READ_LE_UINT32(pText + index + sizeof(uint32)); + } + + // skip over chunk id and offset + index += (2 * sizeof(uint32)); + + // pointer to strings + pText = pText + index; + + // skip to the correct string + while (strSkip-- != 0) { + // skip to next string + pText += *pText + 1; + } + + // get length of string + len = *pText; + + if (len) { + // the string exists + + // copy the string to the buffer + if (len < bufferMax) { + memcpy(pBuffer, pText + 1, len); + + // null terminate + pBuffer[len] = 0; + + // number of chars copied + return len + 1; + } else { + memcpy(pBuffer, pText + 1, bufferMax - 1); + + // null terminate + pBuffer[bufferMax - 1] = 0; + + // number of chars copied + return bufferMax; + } + } + + // TEMPORARY DIRTY BODGE + strcpy(pBuffer, "!! NULL STRING !!"); + + // string does not exist + return 0; +} + +void FreeTextBuffer() { + if (textBuffer) { + free(textBuffer); + textBuffer = NULL; + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/strres.h b/engines/tinsel/strres.h new file mode 100644 index 0000000000..fac287492b --- /dev/null +++ b/engines/tinsel/strres.h @@ -0,0 +1,69 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * String resource managment routines + */ + +#ifndef TINSEL_STRRES_H +#define TINSEL_STRRES_H + +#include "common/scummsys.h" +#include "tinsel/scn.h" + +namespace Tinsel { + +#define STRINGS_PER_CHUNK 64 // number of strings per chunk in the language text files +#define FIRST_STR_ID 1 // id number of first string in string table +#define MAX_STRING_SIZE 255 // maximum size of a string in the resource table +#define MAX_STRRES_SIZE 300000 // maximum size of string resource file + +// Set if we're handling 2-byte characters. +extern bool bMultiByte; + +/*----------------------------------------------------------------------*\ +|* Function Prototypes *| +\*----------------------------------------------------------------------*/ + +/** + * Called to load a resource file for a different language + * @param newLang The new language + */ +void ChangeLanguage(LANGUAGE newLang); + +/** + * Loads a string resource identified by id. + * @param id identifier of string to be loaded + * @param pBuffer points to buffer that receives the string + * @param bufferMax maximum number of chars to be copied to the buffer + */ +int LoadStringRes(int id, char *pBuffer, int bufferMax); + +/** + * Frees the text buffer allocated from ChangeLanguage() + */ +void FreeTextBuffer(); + +} // end of namespace Tinsel + +#endif + diff --git a/engines/tinsel/text.cpp b/engines/tinsel/text.cpp new file mode 100644 index 0000000000..dab97f7fdf --- /dev/null +++ b/engines/tinsel/text.cpp @@ -0,0 +1,279 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Text utilities. + */ + +#include "tinsel/dw.h" +#include "tinsel/graphics.h" // object plotting +#include "tinsel/handle.h" +#include "tinsel/sched.h" // process scheduler defines +#include "tinsel/strres.h" // bMultiByte +#include "tinsel/text.h" // text defines + +namespace Tinsel { + +/** + * Returns the length of one line of a string in pixels. + * @param szStr String + * @param pFont Which font to use for dimensions + */ +int StringLengthPix(char *szStr, const FONT *pFont) { + int strLen; // accumulated length of string + byte c; + SCNHANDLE hImg; + + // while not end of string or end of line + for (strLen = 0; (c = *szStr) != EOS_CHAR && c != LF_CHAR; szStr++) { + if (bMultiByte) { + if (c & 0x80) + c = ((c & ~0x80) << 8) + *++szStr; + } + hImg = FROM_LE_32(pFont->fontDef[c]); + + if (hImg) { + // there is a IMAGE for this character + const IMAGE *pChar = (const IMAGE *)LockMem(hImg); + + // add width of font bitmap + strLen += FROM_LE_16(pChar->imgWidth); + } else + // use width of space character + strLen += FROM_LE_32(pFont->spaceSize); + + // finally add the inter-character spacing + strLen += FROM_LE_32(pFont->xSpacing); + } + + // return length of line in pixels - minus inter-char spacing for last character + strLen -= FROM_LE_32(pFont->xSpacing); + return (strLen > 0) ? strLen : 0; +} + +/** + * Returns the justified x start position of a line of text. + * @param szStr String to output + * @param xPos X position of string + * @param pFont Which font to use + * @param mode Mode flags for the string + */ +int JustifyText(char *szStr, int xPos, const FONT *pFont, int mode) { + if (mode & TXT_CENTRE) { + // centre justify the text + + // adjust x positioning by half the length of line in pixels + xPos -= StringLengthPix(szStr, pFont) / 2; + } else if (mode & TXT_RIGHT) { + // right justify the text + + // adjust x positioning by length of line in pixels + xPos -= StringLengthPix(szStr, pFont); + } + + // return text line x start position + return xPos; +} + +/** + * Main text outputting routine. If a object list is specified a + * multi-object is created for the whole text and a pointer to the head + * of the list is returned. + * @param pList Object list to add text to + * @param szStr String to output + * @param colour Colour for monochrome text + * @param xPos X position of string + * @param yPos Y position of string + * @param hFont Which font to use + * @param mode Mode flags for the string + */ +OBJECT *ObjectTextOut(OBJECT *pList, char *szStr, int colour, int xPos, int yPos, + SCNHANDLE hFont, int mode) { + int xJustify; // x position of text after justification + int yOffset; // offset to next line of text + OBJECT *pFirst; // head of multi-object text list + OBJECT *pChar = 0; // object ptr for the character + byte c; + SCNHANDLE hImg; + const IMAGE *pImg; + + // make sure there is a linked list to add text to + assert(pList); + + // get font pointer + const FONT *pFont = (const FONT *)LockMem(hFont); + + // init head of text list + pFirst = NULL; + + // get image for capital W + assert(pFont->fontDef[(int)'W']); + pImg = (const IMAGE *)LockMem(FROM_LE_32(pFont->fontDef[(int)'W'])); + + // get height of capital W for offset to next line + yOffset = FROM_LE_16(pImg->imgHeight); + + while (*szStr) { + // x justify the text according to the mode flags + xJustify = JustifyText(szStr, xPos, pFont, mode); + + // repeat until end of string or end of line + while ((c = *szStr) != EOS_CHAR && c != LF_CHAR) { + if (bMultiByte) { + if (c & 0x80) + c = ((c & ~0x80) << 8) + *++szStr; + } + hImg = FROM_LE_32(pFont->fontDef[c]); + + if (hImg == 0) { + // no image for this character + + // add font spacing for a space character + xJustify += FROM_LE_32(pFont->spaceSize); + } else { // printable character + + int aniX, aniY; // char image animation offsets + + OBJ_INIT oi; + oi.hObjImg = FROM_LE_32(pFont->fontInit.hObjImg); + oi.objFlags = FROM_LE_32(pFont->fontInit.objFlags); + oi.objID = FROM_LE_32(pFont->fontInit.objID); + oi.objX = FROM_LE_32(pFont->fontInit.objX); + oi.objY = FROM_LE_32(pFont->fontInit.objY); + oi.objZ = FROM_LE_32(pFont->fontInit.objZ); + + // allocate and init a character object + if (pFirst == NULL) + // first time - init head of list + pFirst = pChar = InitObject(&oi); // FIXME: endian issue using fontInit!!! + else + // chain to multi-char list + pChar = pChar->pSlave = InitObject(&oi); // FIXME: endian issue using fontInit!!! + + // convert image handle to pointer + pImg = (const IMAGE *)LockMem(hImg); + + // fill in character object + pChar->hImg = hImg; // image def + pChar->width = FROM_LE_16(pImg->imgWidth); // width of chars bitmap + pChar->height = FROM_LE_16(pImg->imgHeight); // height of chars bitmap + pChar->hBits = FROM_LE_32(pImg->hImgBits); // bitmap + + // check for absolute positioning + if (mode & TXT_ABSOLUTE) + pChar->flags |= DMA_ABS; + + // set characters colour - only effective for mono fonts + pChar->constant = colour; + + // get Y animation offset + GetAniOffset(hImg, pChar->flags, &aniX, &aniY); + + // set x position - ignore animation point + pChar->xPos = intToFrac(xJustify); + + // set y position - adjust for animation point + pChar->yPos = intToFrac(yPos - aniY); + + if (mode & TXT_SHADOW) { + // we want to shadow the character + OBJECT *pShad; + + // allocate a object for the shadow and chain to multi-char list + pShad = pChar->pSlave = AllocObject(); + + // copy the character for a shadow + CopyObject(pShad, pChar); + + // add shadow offsets to characters position + pShad->xPos += intToFrac(FROM_LE_32(pFont->xShadow)); + pShad->yPos += intToFrac(FROM_LE_32(pFont->yShadow)); + + // shadow is behind the character + pShad->zPos--; + + // shadow is always mono + pShad->flags = DMA_CNZ | DMA_CHANGED; + + // check for absolute positioning + if (mode & TXT_ABSOLUTE) + pShad->flags |= DMA_ABS; + + // shadow always uses first palette entry + // should really alloc a palette here also ???? + pShad->constant = 1; + + // add shadow to object list + InsertObject(pList, pShad); + } + + // add character to object list + InsertObject(pList, pChar); + + // move to end of list + if (pChar->pSlave) + pChar = pChar->pSlave; + + // add character spacing + xJustify += FROM_LE_16(pImg->imgWidth); + } + + // finally add the inter-character spacing + xJustify += FROM_LE_32(pFont->xSpacing); + + // next character in string + ++szStr; + } + + // adjust the text y position and add the inter-line spacing + yPos += yOffset + FROM_LE_32(pFont->ySpacing); + + // check for newline + if (c == LF_CHAR) + // next character in string + ++szStr; + } + + // return head of list + return pFirst; +} + +/** + * Is there an image for this character in this font? + * @param hFont which font to use + * @param c character to test + */ +bool IsCharImage(SCNHANDLE hFont, char c) { + byte c2 = (byte)c; + + // Inventory save game name editor needs to be more clever for + // multi-byte characters. This bodge will stop it erring. + if (bMultiByte && (c2 & 0x80)) + return false; + + // get font pointer + const FONT *pFont = (const FONT *)LockMem(hFont); + + return pFont->fontDef[c2] != 0; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/text.h b/engines/tinsel/text.h new file mode 100644 index 0000000000..78998831a1 --- /dev/null +++ b/engines/tinsel/text.h @@ -0,0 +1,101 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Text utility defines + */ + +#ifndef TINSEL_TEXT_H // prevent multiple includes +#define TINSEL_TEXT_H + +#include "tinsel/object.h" // object manager defines + +namespace Tinsel { + +/** text mode flags - defaults to left justify */ +enum { + TXT_CENTRE = 0x0001, //!< centre justify text + TXT_RIGHT = 0x0002, //!< right justify text + TXT_SHADOW = 0x0004, //!< shadow each character + TXT_ABSOLUTE = 0x0008 //!< position of text is absolute (only for object text) +}; + +/** maximum number of characters in a font */ +#define MAX_FONT_CHARS 256 + + +#include "common/pack-start.h" // START STRUCT PACKING + +/** + * Text font data structure. + * @note only the pointer is used so the size of fontDef[] is not important. + * It is currently set at 300 because it suited me for debugging. + */ +struct FONT { + int xSpacing; //!< x spacing between characters + int ySpacing; //!< y spacing between characters + int xShadow; //!< x shadow offset + int yShadow; //!< y shadow offset + int spaceSize; //!< x spacing to use for a space character + OBJ_INIT fontInit; //!< structure used to init text objects + SCNHANDLE fontDef[300]; //!< image handle array for all characters in the font +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + + +/** structure for passing the correct parameters to ObjectTextOut */ +struct TEXTOUT { + OBJECT *pList; //!< object list to add text to + char *szStr; //!< string to output + int colour; //!< colour for monochrome text + int xPos; //!< x position of string + int yPos; //!< y position of string + SCNHANDLE hFont; //!< which font to use + int mode; //!< mode flags for the string + int sleepTime; //!< sleep time between each character (if non-zero) +}; + + +/*----------------------------------------------------------------------*\ +|* Text Function Prototypes *| +\*----------------------------------------------------------------------*/ + +OBJECT *ObjectTextOut( // output a string of text + OBJECT *pList, // object list to add text to + char *szStr, // string to output + int colour, // colour for monochrome text + int xPos, // x position of string + int yPos, // y position of string + SCNHANDLE hFont, // which font to use + int mode); // mode flags for the string + +OBJECT *ObjectTextOutIndirect( // output a string of text + TEXTOUT *pText); // pointer to TextOut struct with all parameters + +bool IsCharImage( // Is there an image for this character in this font? + SCNHANDLE hFont, // which font to use + char c); // character to test + +} // end of namespace Tinsel + +#endif // TINSEL_TEXT_H diff --git a/engines/tinsel/timers.cpp b/engines/tinsel/timers.cpp new file mode 100644 index 0000000000..c7b9d3708b --- /dev/null +++ b/engines/tinsel/timers.cpp @@ -0,0 +1,192 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Handles timers. + * + * Note: As part of the transition to ScummVM, the ticks field of a timer has been changed + * to a millisecond value, rather than ticks at 24Hz. Most places should be able to use + * the timers without change, since the ONE_SECOND constant has been set to be in MILLISECONDS + */ + +#include "tinsel/timers.h" +#include "tinsel/dw.h" +#include "tinsel/serializer.h" + +#include "common/system.h" + +namespace Tinsel { + +//----------------- LOCAL DEFINES -------------------- + +#define MAX_TIMERS 16 + +struct TIMER { + int tno; /**< Timer number */ + int ticks; /**< Tick count */ + int secs; /**< Second count */ + int delta; /**< Increment/decrement value */ + bool frame; /**< If set, in ticks, otherwise in seconds */ +}; + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +static TIMER timers[MAX_TIMERS]; + + +//-------------------------------------------------------- + +/** + * Gets the current time in number of ticks. + * + * DOS timer ticks is the number of 54.9254ms since midnight. Converting the + * millisecond count won't give the exact same 'since midnight' count, but I + * figure that as long as the timing interval is more or less accurate, it + * shouldn't be a problem. + */ + +uint32 DwGetCurrentTime() { + return g_system->getMillis() * 55 / 1000; +} + +/** + * Resets all of the timer slots + */ + +void RebootTimers(void) { + memset(timers, 0, sizeof(timers)); +} + +/** + * (Un)serialize the timer data for save/restore game. + */ +void syncTimerInfo(Serializer &s) { + for (int i = 0; i < MAX_TIMERS; i++) { + s.syncAsSint32LE(timers[i].tno); + s.syncAsSint32LE(timers[i].ticks); + s.syncAsSint32LE(timers[i].secs); + s.syncAsSint32LE(timers[i].delta); + s.syncAsSint32LE(timers[i].frame); + } +} + +/** + * Find the timer numbered thus, if one is thus numbered. + * @param num number of the timer + * @return the timer with the specified number, or NULL if there is none + */ +static TIMER *findTimer(int num) { + for (int i = 0; i < MAX_TIMERS; i++) { + if (timers[i].tno == num) + return &timers[i]; + } + return NULL; +} + +/** + * Find an empty timer slot. + */ +static TIMER *allocateTimer(int num) { + assert(num); // zero is not allowed as a timer number + assert(!findTimer(num)); // Allocating already existant timer + + for (int i = 0; i < MAX_TIMERS; i++) { + if (!timers[i].tno) { + timers[i].tno = num; + return &timers[i]; + } + } + + error("Too many timers"); +} + +/** + * Update all timers, as appropriate. + */ +void FettleTimers(void) { + for (int i = 0; i < MAX_TIMERS; i++) { + if (!timers[i].tno) + continue; + + timers[i].ticks += timers[i].delta; // Update tick value + + if (timers[i].frame) { + if (timers[i].ticks < 0) + timers[i].ticks = 0; // Have reached zero + } else { + if (timers[i].ticks < 0) { + timers[i].ticks = ONE_SECOND; + timers[i].secs--; + if (timers[i].secs < 0) + timers[i].secs = 0; // Have reached zero + } else if (timers[i].ticks == ONE_SECOND) { + timers[i].ticks = 0; + timers[i].secs++; // Another second has passed + } + } + } +} + +/** + * Start a timer up. + */ +void DwSetTimer(int num, int sval, bool up, bool frame) { + TIMER *pt; + + assert(num); // zero is not allowed as a timer number + + pt = findTimer(num); + if (pt == NULL) + pt = allocateTimer(num); + + pt->delta = up ? 1 : -1; // Increment/decrement value + pt->frame = frame; + + if (frame) { + pt->secs = 0; + pt->ticks = sval; + } else { + pt->secs = sval; + pt->ticks = 0; + } +} + +/** + * Return the current count of a timer. + */ +int Timer(int num) { + TIMER *pt; + + pt = findTimer(num); + + if (pt == NULL) + return -1; + + if (pt->frame) + return pt->ticks; + else + return pt->secs; +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/timers.h b/engines/tinsel/timers.h new file mode 100644 index 0000000000..75eb87ee2b --- /dev/null +++ b/engines/tinsel/timers.h @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Handles timers. + */ + +#ifndef TINSEL_TIMERS_H // prevent multiple includes +#define TINSEL_TIMERS_H + +#include "common/scummsys.h" +#include "tinsel/dw.h" + +namespace Tinsel { + +class Serializer; + +#define ONE_SECOND 24 + +uint32 DwGetCurrentTime(void); + +void RebootTimers(void); + +void syncTimerInfo(Serializer &s); + +void FettleTimers(void); + +void DwSetTimer(int num, int sval, bool up, bool frame); + +int Timer(int num); + +} // end of namespace Tinsel + +#endif diff --git a/engines/tinsel/tinlib.cpp b/engines/tinsel/tinlib.cpp new file mode 100644 index 0000000000..a07a16a205 --- /dev/null +++ b/engines/tinsel/tinlib.cpp @@ -0,0 +1,2980 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Glitter library functions. + * + * In the main called only from PCODE.C + * Function names are the same as Glitter code function names. + * + * To ensure exclusive use of resources and exclusive control responsibilities. + */ + +#define BODGE + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/coroutine.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/film.h" +#include "tinsel/font.h" +#include "tinsel/graphics.h" +#include "tinsel/handle.h" +#include "tinsel/inventory.h" +#include "tinsel/move.h" +#include "tinsel/multiobj.h" +#include "tinsel/music.h" +#include "tinsel/object.h" +#include "tinsel/palette.h" +#include "tinsel/pcode.h" +#include "tinsel/pid.h" +#include "tinsel/polygons.h" +#include "tinsel/rince.h" +#include "tinsel/savescn.h" +#include "tinsel/sched.h" +#include "tinsel/scn.h" +#include "tinsel/scroll.h" +#include "tinsel/sound.h" +#include "tinsel/strres.h" +#include "tinsel/text.h" +#include "tinsel/timers.h" // For ONE_SECOND constant +#include "tinsel/tinlib.h" +#include "tinsel/tinsel.h" +#include "tinsel/token.h" + + +namespace Tinsel { + +//----------------- EXTERNAL GLOBAL DATA -------------------- + +// In DOS_DW.C +extern bool bRestart; // restart flag - set to restart the game +extern bool bHasRestarted; // Set after a restart + +// In DOS_MAIN.C +// TODO/FIXME: From dos_main.c: "Only used on PSX so far" +int clRunMode = 0; + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// in BG.C +extern void startupBackground(SCNHANDLE bfilm); +extern void ChangePalette(SCNHANDLE hPal); +extern int BackgroundWidth(void); +extern int BackgroundHeight(void); + +// in DOS_DW.C +extern void SetHookScene(SCNHANDLE scene, int entrance, int transition); +extern void SetNewScene(SCNHANDLE scene, int entrance, int transition); +extern void UnHookScene(void); +extern void SuspendHook(void); +extern void UnSuspendHook(void); + +// in PDISPLAY.C +extern void EnableTags(void); +extern void DisableTags(void); +bool DisableTagsIfEnabled(void); +extern void setshowstring(void); + +// in PLAY.C +extern void playFilm(SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, int myescEvent, bool bTop); +extern void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, int myescEvent, bool bTop); + +// in SCENE.C +extern void setshowpos(void); + +#ifdef BODGE +// In DOS_HAND.C +bool ValidHandle(SCNHANDLE offset); + +// In SCENE.C +SCNHANDLE GetSceneHandle(void); +#endif + +//----------------- GLOBAL GLOBAL DATA -------------------- + +bool bEnableF1; + + +//----------------- LOCAL DEFINES -------------------- + +#define JAP_TEXT_TIME (2*ONE_SECOND) + +/*----------------------------------------------------------------------*\ +|* Library Procedure and Function codes *| +\*----------------------------------------------------------------------*/ + +enum LIB_CODE { + ACTORATTR = 0, ACTORDIRECTION, ACTORREF, ACTORSCALE, ACTORXPOS = 4, + ACTORYPOS, ADDICON, ADDINV1, ADDINV2, ADDOPENINV, AUXSCALE = 10, + BACKGROUND, CAMERA, CLOSEINVENTORY, CONTROL, CONVERSATION = 15, + CONVICON, CURSORXPOS, CURSORYPOS, DEC_CONVW, DEC_CURSOR = 20, + DEC_INV1, DEC_INV2, DEC_INVW, DEC_LEAD, DEC_TAGFONT = 25, + DEC_TALKFONT, DELICON, DELINV, EFFECTACTOR, ESCAPE, EVENT = 31, + GETINVLIMIT, HELDOBJECT, HIDE, ININVENTORY, INVDEPICT = 36, + INVENTORY, KILLACTOR, KILLBLOCK, KILLEXIT, KILLTAG, LEFTOFFSET = 42, + MOVECURSOR, NEWSCENE, NOSCROLL, OBJECTHELD, OFFSET, PAUSE = 48, + PLAY, PLAYMIDI, PLAYSAMPLE, PREPARESCENE, PRINT, PRINTOBJ = 54, + PRINTTAG, RANDOM, RESTORE_SCENE, SAVE_SCENE, SCALINGREELS = 59, + SCANICON, SCROLL, SETACTOR, SETBLOCK, SETEXIT, SETINVLIMIT = 65, + SETPALETTE, SETTAG, SETTIMER, SHOWPOS, SHOWSTRING, SPLAY = 71, + STAND, STANDTAG, STOP, SWALK, TAGACTOR, TALK, TALKATTR, TIMER = 79, + TOPOFFSET, TOPPLAY, TOPWINDOW, UNTAGACTOR, VIBRATE, WAITKEY = 85, + WAITTIME, WALK, WALKED, WALKINGACTOR, WALKPOLY, WALKTAG = 91, + WHICHINVENTORY = 92, + ACTORSON, CUTSCENE, HOOKSCENE, IDLETIME, RESETIDLETIME = 97, + TALKAT, UNHOOKSCENE, WAITFRAME, DEC_CSTRINGS, STOPMIDI, STOPSAMPLE = 103, + TALKATS = 104, + DEC_FLAGS, FADEMIDI, CLEARHOOKSCENE, SETINVSIZE, INWHICHINV = 109, + NOBLOCKING, SAMPLEPLAYING, TRYPLAYSAMPLE, ENABLEF1 = 113, + RESTARTGAME, QUITGAME, FRAMEGRAB, PLAYRTF, CDPLAY, CDLOAD = 119, + HASRESTARTED, RESTORE_CUT, RUNMODE, SUBTITLES, SETLANGUAGE = 124 +}; + + + +//----------------- LOCAL GLOBAL DATA -------------------- + +// Saved cursor co-ordinates for control(on) to restore cursor position +// as it was at control(off). +// They are global so that movecursor(..) has a net effect if it +// precedes control(on). +static int controlX = 0, controlY = 0; + +static int offtype = 0; // used by control() +static uint32 lastValue = 0; // used by dw_random() +static int scrollCount = 0; // used by scroll() + +static bool NotPointedRunning = false; // Used in printobj and printobjPointed + +static COLORREF s_talkfontColor = 0; + +//----------------- FORWARD REFERENCES -------------------- + +void resetidletime(void); +void stopmidi(void); +void stopsample(void); +void walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath, bool escOn, int myescTime); + + +/** + * NOT A LIBRARY FUNCTION + * + * Poke supplied colours into the DAC queue. + */ +static void setTextPal(COLORREF col) { + s_talkfontColor = col; + UpdateDACqueue(TALKFONT_COL, 1, &s_talkfontColor); +} + + +static int TextTime(char *pTstring) { + if (isJapanMode()) + return JAP_TEXT_TIME; + else if (!speedText) + return strlen(pTstring) + ONE_SECOND; + else + return strlen(pTstring) + ONE_SECOND + (speedText * 5 * ONE_SECOND) / 100; +} + +/*--------------------------------------------------------------------------*/ + + +/** + * Set actor's attributes. + * - currently only the text colour. + */ +void actorattr(int actor, int r1, int g1, int b1) { + storeActorAttr(actor, r1, g1, b1); +} + +/** + * Return the actor's direction. + */ +int actordirection(int actor) { + PMACTOR pActor; + + pActor = GetMover(actor); + assert(pActor != NULL); // not a moving actor + + return (int)GetMActorDirection(pActor); +} + +/** + * Return the actor's scale. + */ +int actorscale(int actor) { + PMACTOR pActor; + + pActor = GetMover(actor); + assert(pActor != NULL); // not a moving actor + + return (int)GetMActorScale(pActor); +} + +/** + * Returns the x or y position of an actor. + */ +int actorpos(int xory, int actor) { + int x, y; + + GetActorPos(actor, &x, &y); + return (xory == ACTORXPOS) ? x : y; +} + +/** + * Make all actors alive at the start of each scene. + */ +void actorson(void) { + setactorson(); +} + +/** + * Adds an icon to the conversation window. + */ +void addicon(int icon) { + AddToInventory(INV_CONV, icon, false); +} + +/** + * Place the object in inventory 1 or 2. + */ +void addinv(int invno, int object) { + assert(invno == INV_1 || invno == INV_2 || invno == INV_OPEN); // illegal inventory number + + AddToInventory(invno, object, false); +} + +/** + * Define an actor's walk and stand reels for an auxilliary scale. + */ +void auxscale(int actor, int scale, SCNHANDLE *rp) { + PMACTOR pActor; + + pActor = GetMover(actor); + assert(pActor); // Can't set aux scale for a non-moving actor + + int j; + for (j = 0; j < 4; ++j) + pActor->WalkReels[scale-1][j] = *rp++; + for (j = 0; j < 4; ++j) + pActor->StandReels[scale-1][j] = *rp++; + for (j = 0; j < 4; ++j) + pActor->TalkReels[scale-1][j] = *rp++; +} + +/** + * Defines the background image for a scene. + */ +void background(SCNHANDLE bfilm) { + startupBackground(bfilm); +} + +/** + * Sets focus of the scroll process. + */ +void camera(int actor) { + ScrollFocus(actor); +} + +/** + * A CDPLAY() is imminent. + */ +void cdload(SCNHANDLE start, SCNHANDLE next) { + assert(start && next && start != next); // cdload() fault + +// TODO/FIXME +// LoadExtraGraphData(start, next); +} + +/** + * Clear the hooked scene (if any) + */ + +void clearhookscene() { + SetHookScene(0, 0, 0); +} + +/** + * Guess what. + */ + +void closeinventory(void) { + KillInventory(); +} + +/** + * Turn off cursor and take control from player - and variations on the theme. + * OR Restore cursor and return control to the player. + */ + +void control(int param) { + bEnableF1 = false; + + switch (param) { + case CONTROL_STARTOFF: + GetControlToken(); // Take control + DisableTags(); // Switch off tags + DwHideCursor(); // Blank out cursor + offtype = param; + break; + + case CONTROL_OFF: + case CONTROL_OFFV: + case CONTROL_OFFV2: + if (TestToken(TOKEN_CONTROL)) { + GetControlToken(); // Take control + + DisableTags(); // Switch off tags + GetCursorXYNoWait(&controlX, &controlY, true); // Store cursor position + + // There may be a button timing out + GetToken(TOKEN_LEFT_BUT); + FreeToken(TOKEN_LEFT_BUT); + } + + if (offtype == CONTROL_STARTOFF) + GetCursorXYNoWait(&controlX, &controlY, true); // Store cursor position + + offtype = param; + + if (param == CONTROL_OFF) + DwHideCursor(); // Blank out cursor + else if (param == CONTROL_OFFV) { + UnHideCursor(); + FreezeCursor(); + } else if (param == CONTROL_OFFV2) { + UnHideCursor(); + } + break; + + case CONTROL_ON: + if (offtype != CONTROL_OFFV2 && offtype != CONTROL_STARTOFF) + SetCursorXY(controlX, controlY);// ... where it was + + FreeControlToken(); // Release control + + if (!InventoryActive()) + EnableTags(); // Tags back on + + RestoreMainCursor(); // Re-instate cursor... + } +} + +/** + * Open or close the conversation window. + */ + +void conversation(int fn, HPOLYGON hp, bool escOn, int myescEvent) { + assert(hp != NOPOLY); // conversation() must (currently) be called from a polygon code block + + switch (fn) { + case CONV_END: // Close down conversation + CloseDownConv(); + break; + + case CONV_DEF: // Default (i.e. TOP of screen) + case CONV_BOTTOM: // BOTTOM of screen + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + break; + + if (IsConvWindow()) + break; + + KillInventory(); + convPos(fn); + ConvPoly(hp); + PopUpInventory(INV_CONV); // Conversation window + ConvAction(INV_OPENICON); // CONVERSATION event + break; + } +} + +/** + * Add icon to conversation window's permanent default list. + */ + +void convicon(int icon) { + AddIconToPermanentDefaultList(icon); +} + +/** + * Returns the x or y position of the cursor. + */ + +int cursorpos(int xory) { + int x, y; + + GetCursorXY(&x, &y, true); + return (xory == CURSORXPOS) ? x : y; +} + +/** + * Declare conversation window. + */ + +void dec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { + idec_convw(text, MaxContents, MinWidth, MinHeight, + StartWidth, StartHeight, MaxWidth, MaxHeight); +} + +/** + * Declare config strings. + */ + +void dec_cstrings(SCNHANDLE *tp) { + setConfigStrings(tp); +} + +/** + * Declare cursor's reels. + */ + +void dec_cursor(SCNHANDLE bfilm) { + DwInitCursor(bfilm); +} + +/** + * Declare the language flags. + */ + +void dec_flags(SCNHANDLE hf) { + setFlagFilms(hf); +} + +/** + * Declare inventory 1's parameters. + */ + +void dec_inv1(SCNHANDLE text, int MaxContents, + int MinWidth, int MinHeight, + int StartWidth, int StartHeight, + int MaxWidth, int MaxHeight) { + idec_inv1(text, MaxContents, MinWidth, MinHeight, + StartWidth, StartHeight, MaxWidth, MaxHeight); +} + +/** + * Declare inventory 2's parameters. + */ + +void dec_inv2(SCNHANDLE text, int MaxContents, + int MinWidth, int MinHeight, + int StartWidth, int StartHeight, + int MaxWidth, int MaxHeight) { + idec_inv2(text, MaxContents, MinWidth, MinHeight, + StartWidth, StartHeight, MaxWidth, MaxHeight); +} + +/** + * Declare the bits that the inventory windows are constructed from. + */ + +void dec_invw(SCNHANDLE hf) { + setInvWinParts(hf); +} + +/** + * Declare lead actor. + * - the actor's id, walk and stand reels for all the regular scales, + * and the tag text. + */ + +void dec_lead(uint32 id, SCNHANDLE *rp, SCNHANDLE text) { + PMACTOR pActor; // Moving actor structure + + Tag_Actor(id, text, TAG_DEF); // The lead actor is automatically tagged + setleadid(id); // Establish this as the lead + SetMover(id); // Establish as a moving actor + + pActor = GetMover(id); // Get moving actor structure + assert(pActor); + + // Store all those reels + int i, j; + for (i = 0; i < 5; ++i) { + for (j = 0; j < 4; ++j) + pActor->WalkReels[i][j] = *rp++; + for (j = 0; j < 4; ++j) + pActor->StandReels[i][j] = *rp++; + for (j = 0; j < 4; ++j) + pActor->TalkReels[i][j] = *rp++; + } + + + for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) { + for (j = 0; j < 4; ++j) { + pActor->WalkReels[i][j] = pActor->WalkReels[4][j]; + pActor->StandReels[i][j] = pActor->StandReels[2][j]; + pActor->TalkReels[i][j] = pActor->TalkReels[4][j]; + } + } +} + +/** + * Declare the text font. + */ + +void dec_tagfont(SCNHANDLE hf) { + TagFontHandle(hf); // Store the font handle +} + +/** + * Declare the text font. + */ + +void dec_talkfont(SCNHANDLE hf) { + TalkFontHandle(hf); // Store the font handle +} + +/** + * Remove an icon from the conversation window. + */ + +void delicon(int icon) { + RemFromInventory(INV_CONV, icon); +} + +/** + * Delete the object from inventory 1 or 2. + */ + +void delinv(int object) { + if (!RemFromInventory(INV_1, object)) // Remove from inventory 1... + RemFromInventory(INV_2, object); // ...or 2 (whichever) + + DropItem(object); // Stop holding it +} + +/** + * enablef1 + */ + +void enablef1(void) { + bEnableF1 = true; +} + +/** + * fademidi(in/out) + */ + +void fademidi(CORO_PARAM, int inout) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + assert(inout == FM_IN || inout == FM_OUT); + + // To prevent compiler complaining + if (inout == FM_IN || inout == FM_OUT) + CORO_SLEEP(1); + CORO_END_CODE; +} + +/** + * Guess what. + */ + +int getinvlimit(int invno) { + return InvGetLimit(invno); +} + +/** + * Returns TRUE if the game has been restarted, FALSE if not. + */ +bool hasrestarted(void) { + return bHasRestarted; +} + +/** + * Returns which object is currently held. + */ + +int heldobject(void) { + return WhichItemHeld(); +} + +/** + * Removes a player from the screen, probably when he's about to be + * replaced by an animation. + * + * Not believed to work anymore! (hide() is not used). + */ + +void hide(int actor) { + HideActor(actor); +} + +/** + * hookscene(scene, entrance, transition) + */ + +void hookscene(SCNHANDLE scene, int entrance, int transition) { + SetHookScene(scene, entrance, transition); +} + +/** + * idletime + */ + +int idletime(void) { + uint32 x; + + x = getUserEventTime() / ONE_SECOND; + + if (!TestToken(TOKEN_CONTROL)) + resetidletime(); + + return (int)x; +} + +/** + * invdepict + */ +void invdepict(int object, SCNHANDLE hFilm) { + invObjectFilm(object, hFilm); +} + +/** + * See if an object is in the inventory. + */ +int ininventory(int object) { + return (InventoryPos(object) != INV_NOICON); +} + +/** + * Open an inventory. + */ +void inventory(int invno, bool escOn, int myescEvent) { + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + assert((invno == INV_1 || invno == INV_2)); // Trying to open illegal inventory + + PopUpInventory(invno); +} + +/** + * See if an object is in the inventory. + */ +int inwhichinv(int object) { + if (WhichItemHeld() == object) + return 0; + + if (IsInInventory(object, INV_1)) + return 1; + + if (IsInInventory(object, INV_2)) + return 2; + + return -1; +} + +/** + * Kill an actor. + */ +void killactor(int actor) { + DisableActor(actor); +} + +/** + * Turn a blocking polygon off. + */ +void killblock(int block) { + DisableBlock(block); +} + +/** + * Turn an exit off. + */ +void killexit(int exit) { + DisableExit(exit); +} + +/** + * Turn a tag off. + */ +void killtag(int tagno) { + DisableTag(tagno); +} + +/** + * Returns the left or top offset of the screen. + */ +int ltoffset(int lort) { + int Loffset, Toffset; + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + return (lort == LEFTOFFSET) ? Loffset : Toffset; +} + +/** + * Set new cursor position. + */ +void movecursor(int x, int y) { + SetCursorXY(x, y); + + controlX = x; // Save these values so that + controlY = y; // control(on) doesn't undo this +} + +/** + * Triggers change to a new scene. + */ +void newscene(CORO_PARAM, SCNHANDLE scene, int entrance, int transition) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + +#ifdef BODGE + if (!ValidHandle(scene)) { + scene = GetSceneHandle(); + entrance = 1; + } + assert(scene); // Non-existant first scene! +#endif + + SetNewScene(scene, entrance, transition); + +#if 1 + // Prevent tags and cursor re-appearing + GetControl(CONTROL_STARTOFF); +#endif + + // Prevent code subsequent to this call running before scene changes + if (ProcessGetPID(CurrentProcess()) != PID_MASTER_SCR) + CORO_KILL_SELF(); + CORO_END_CODE; +} + +/** + * Disable dynamic blocking for current scene. + */ +void noblocking(void) { + bNoBlocking = true; +} + +/** + * Define a no-scroll boundary for the current scene. + */ +void noscroll(int x1, int y1, int x2, int y2) { + SetNoScroll(x1, y1, x2, y2); +} + +/** + * Hold the specified object. + */ +void objectheld(int object) { + HoldItem(object); +} + +/** + * Set the top left offset of the screen. + */ +void offset(int x, int y) { + KillScroll(); + PlayfieldSetPos(FIELD_WORLD, x, y); +} + +/** + * Play a film. + */ +void play(CORO_PARAM, SCNHANDLE film, int x, int y, int compit, int actorid, bool splay, int sfact, + bool escOn, int myescEvent, bool bTop) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + assert(film != 0); // play(): Trying to play NULL film + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + // If this actor is dead, call a stop to the calling process + if (actorid && !actorAlive(actorid)) + CORO_KILL_SELF(); + + // 7/4/95 + if (!escOn) + myescEvent = GetEscEvents(); + + if (compit == 1) { + // Play to completion before returning + CORO_INVOKE_ARGS(playFilmc, (CORO_SUBCTX, film, x, y, actorid, splay, sfact, escOn, myescEvent, bTop)); + } else if (compit == 2) { + error("play(): compit == 2 - please advise John"); + } else { + // Kick off the play and return. + playFilm(film, x, y, actorid, splay, sfact, escOn, myescEvent, bTop); + } + CORO_END_CODE; +} + +/** + * Play a midi file. + */ +void playmidi(CORO_PARAM, SCNHANDLE hMidi, int loop, bool complete) { + // FIXME: This is a workaround for the FIXME below + if (GetMidiVolume() == 0) + return; + + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + assert(loop == MIDI_DEF || loop == MIDI_LOOP); + + PlayMidiSequence(hMidi, loop == MIDI_LOOP); + + // FIXME: The following check messes up the script arguments when + // entering the secret door in the bookshelf in the library, + // leading to a crash, when the music volume is set to 0 (MidiPlaying() + // always false then). + // + // Why exactly this happens is unclear. An analysis of the involved + // script(s) might reveal more. + // + // Note: This check&sleep was added in DW v2. It was most likely added + // to ensure that the MIDI song started playing before the next opcode + // is executed. + if (!MidiPlaying()) + CORO_SLEEP(1); + + if (complete) { + while (MidiPlaying()) + CORO_SLEEP(1); + } + CORO_END_CODE; +} + +/** + * Play a sample. + */ +void playsample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + Audio::SoundHandle handle; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + // Don't play SFX if voice is already playing + if (_vm->_mixer->hasActiveChannelOfType(Audio::Mixer::kSpeechSoundType)) + return; + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) { + _vm->_sound->stopAllSamples(); // Stop any currently playing sample + return; + } + + if (volSound != 0 && _vm->_sound->sampleExists(sample)) { + _vm->_sound->playSample(sample, Audio::Mixer::kSFXSoundType, &_ctx->handle); + + if (complete) { + while (_vm->_mixer->isSoundHandleActive(_ctx->handle)) { + // Abort if escapable and ESCAPE is pressed + if (escOn && myescEvent != GetEscEvents()) { + _vm->_mixer->stopHandle(_ctx->handle); + break; + } + + CORO_SLEEP(1); + } + } + } else { + // Prevent Glitter lock-up + CORO_SLEEP(1); + } + CORO_END_CODE; +} + +/** + * Play a sample. + */ +void tryplaysample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + // Don't do it if it's not appropriate + if (_vm->_sound->sampleIsPlaying()) { + // return, but prevent Glitter lock-up + CORO_SLEEP(1); + return; + } + + CORO_INVOKE_ARGS(playsample, (CORO_SUBCTX, sample, complete, escOn, myescEvent)); + CORO_END_CODE; +} + +/** + * Trigger pre-loading of a scene's data. + */ +void preparescene(SCNHANDLE scene) { +#ifdef BODGE + if (!ValidHandle(scene)) + return; +#endif +} + +/** + * Print the given text at the given place for the given time. + * + * Print(....., h) -> hold = 1 (not used) + * Print(....., s) -> hold = 2 (sustain) + */ +void print(CORO_PARAM, int x, int y, SCNHANDLE text, int time, int hold, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + OBJECT *pText; // text object pointer + int myleftEvent; + bool bSample; // Set if a sample is playing + Audio::SoundHandle handle; + int timeout; + int time; + CORO_END_CONTEXT(_ctx); + + bool bJapDoPrintText; // Bodge to get-around Japanese bodge + + CORO_BEGIN_CODE(_ctx); + + _ctx->pText = NULL; + _ctx->bSample = false; + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + // Kick off the voice sample + if (volVoice != 0 && _vm->_sound->sampleExists(text)) { + _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle); + _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle); + } + + // Calculate display time + LoadStringRes(text, tBufferAddr(), TBUFSZ); + bJapDoPrintText = false; + if (time == 0) { + // This is a 'talky' print + _ctx->time = TextTime(tBufferAddr()); + + // Cut short-able if sustain was not set + _ctx->myleftEvent = (hold == 2) ? 0 : GetLeftEvents(); + } else { + _ctx->time = time * ONE_SECOND; + _ctx->myleftEvent = 0; + if (isJapanMode()) + bJapDoPrintText = true; + } + + // Print the text + if (bJapDoPrintText || (!isJapanMode() && (bSubtitles || !_ctx->bSample))) { + int Loffset, Toffset; // Screen position + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), + 0, x - Loffset, y - Toffset, hTalkFontHandle(), TXT_CENTRE); + assert(_ctx->pText); // string produced NULL text + if (IsTopWindow()) + MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT); + + /* + * New feature: Don't go off the side of the background + */ + int shift; + shift = MultiRightmost(_ctx->pText) + 2; + if (shift >= BackgroundWidth()) // Not off right + MultiMoveRelXY(_ctx->pText, BackgroundWidth() - shift, 0); + shift = MultiLeftmost(_ctx->pText) - 1; + if (shift <= 0) // Not off left + MultiMoveRelXY(_ctx->pText, -shift, 0); + shift = MultiLowest(_ctx->pText); + if (shift > BackgroundHeight()) // Not off bottom + MultiMoveRelXY(_ctx->pText, 0, BackgroundHeight() - shift); + } + + // Give up if nothing printed and no sample + if (_ctx->pText == NULL && !_ctx->bSample) + return; + + // Leave it up until time runs out or whatever + _ctx->timeout = SAMPLETIMEOUT; + do { + CORO_SLEEP(1); + + // Abort if escapable and ESCAPE is pressed + // Abort if left click - hardwired feature for talky-print! + // Will be ignored if myleftevent happens to be 0! + // Abort if sample times out + if ((escOn && myescEvent != GetEscEvents()) + || (_ctx->myleftEvent && _ctx->myleftEvent != GetLeftEvents()) + || (_ctx->bSample && --_ctx->timeout <= 0)) + break; + + if (_ctx->bSample) { + // Wait for sample to end whether or not + if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) { + if (_ctx->pText == NULL || speedText == DEFTEXTSPEED) { + // No text or speed modification - just depends on sample + break; + } else { + // Must wait for time + _ctx->bSample = false; + } + } + } else { + // No sample - just depends on time + if (_ctx->time-- <= 0) + break; + } + + } while (1); + + // Delete the text + if (_ctx->pText != NULL) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + _vm->_mixer->stopHandle(_ctx->handle); + + CORO_END_CODE; +} + + +static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const PINV_OBJECT pinvo, OBJECT *&pText, const int textx, const int texty, const int item); +static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText); + +/** + * Print the given inventory object's name or whatever. + */ +void printobj(CORO_PARAM, const SCNHANDLE text, const PINV_OBJECT pinvo, const int event) { + CORO_BEGIN_CONTEXT; + OBJECT *pText; // text object pointer + int textx, texty; + int item; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + assert(pinvo != 0); // printobj() may only be called from an object code block + + if (text == (SCNHANDLE)-1) { // 'OFF' + NotPointedRunning = true; + return; + } + if (text == (SCNHANDLE)-2) { // 'ON' + NotPointedRunning = false; + return; + } + + GetCursorXY(&_ctx->textx, &_ctx->texty, false); // Cursor position.. + _ctx->item = InvItem(&_ctx->textx, &_ctx->texty, true); // ..to text position + + if (_ctx->item == INV_NOICON) + return; + + if (event != POINTED) { + NotPointedRunning = true; // Get POINTED text to die + CORO_SLEEP(1); // Give it chance to + } else + NotPointedRunning = false; // There may have been an OFF without an ON + + // Display the text and set it's Z position + if (event == POINTED || (!isJapanMode() && (bSubtitles || !_vm->_sound->sampleExists(text)))) { + int xshift; + + LoadStringRes(text, tBufferAddr(), TBUFSZ); // The text string + _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), + 0, _ctx->textx, _ctx->texty, hTagFontHandle(), TXT_CENTRE); + assert(_ctx->pText); // printobj() string produced NULL text + MultiSetZPosition(_ctx->pText, Z_INV_ITEXT); + + // Don't go off the side of the screen + xshift = MultiLeftmost(_ctx->pText); + if (xshift < 0) { + MultiMoveRelXY(_ctx->pText, - xshift, 0); + _ctx->textx -= xshift; + } + xshift = MultiRightmost(_ctx->pText); + if (xshift > SCREEN_WIDTH) { + MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0); + _ctx->textx += SCREEN_WIDTH - xshift; + } + } else + _ctx->pText = NULL; + + if (event == POINTED) { + // FIXME: Is there ever an associated sound if in POINTED mode??? + assert(!_vm->_sound->sampleExists(text)); + CORO_INVOKE_ARGS(printobjPointed, (CORO_SUBCTX, text, pinvo, _ctx->pText, _ctx->textx, _ctx->texty, _ctx->item)); + } else { + CORO_INVOKE_2(printobjNonPointed, text, _ctx->pText); + } + + // Delete the text, if haven't already + if (_ctx->pText) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + + CORO_END_CODE; +} + +static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const PINV_OBJECT pinvo, OBJECT *&pText, const int textx, const int texty, const int item) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + // Have to give way to non-POINTED-generated text + // and go away if the item gets picked up + int x, y; + do { + // Give up if this item gets picked up + if (WhichItemHeld() == pinvo->id) + break; + + // Give way to non-POINTED-generated text + if (NotPointedRunning) { + // Delete the text, and wait for the all-clear + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), pText); + pText = NULL; + while (NotPointedRunning) + CORO_SLEEP(1); + + GetCursorXY(&x, &y, false); + if (InvItem(&x, &y, false) != item) + break; + + // Re-display in the same place + LoadStringRes(text, tBufferAddr(), TBUFSZ); + pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), + 0, textx, texty, hTagFontHandle(), TXT_CENTRE); + assert(pText); // printobj() string produced NULL text + MultiSetZPosition(pText, Z_INV_ITEXT); + } + + CORO_SLEEP(1); + + // Carry on until the cursor leaves this icon + GetCursorXY(&x, &y, false); + } while (InvItemId(x, y) == pinvo->id); + + CORO_END_CODE; +} + +static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText) { + CORO_BEGIN_CONTEXT; + bool bSample; // Set if a sample is playing + Audio::SoundHandle handle; + + int myleftEvent; + bool took_control; + int ticks; + int timeout; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + // Kick off the voice sample + if (volVoice != 0 && _vm->_sound->sampleExists(text)) { + _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle); + _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle); + } else + _ctx->bSample = false; + + _ctx->myleftEvent = GetLeftEvents(); + _ctx->took_control = GetControl(CONTROL_OFF); + + // Display for a time, but abort if conversation gets hidden + if (isJapanMode()) + _ctx->ticks = JAP_TEXT_TIME; + else if (pText) + _ctx->ticks = TextTime(tBufferAddr()); + else + _ctx->ticks = 0; + + _ctx->timeout = SAMPLETIMEOUT; + do { + CORO_SLEEP(1); + --_ctx->timeout; + + // Abort if left click - hardwired feature for talky-print! + // Abort if sample times out + // Abort if conversation hidden + if (_ctx->myleftEvent != GetLeftEvents() || _ctx->timeout <= 0 || convHid()) + break; + + if (_ctx->bSample) { + // Wait for sample to end whether or not + if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) { + if (pText == NULL || speedText == DEFTEXTSPEED) { + // No text or speed modification - just depends on sample + break; + } else { + // Must wait for time + _ctx->bSample = false; + } + } + } else { + // No sample - just depends on time + if (_ctx->ticks-- <= 0) + break; + } + } while (1); + + NotPointedRunning = false; // Let POINTED text back in + + if (_ctx->took_control) + control(CONTROL_ON); // Free control if we took it + + _vm->_mixer->stopHandle(_ctx->handle); + + CORO_END_CODE; +} + +/** + * Register the fact that this poly would like its tag displayed. + */ +void printtag(HPOLYGON hp, SCNHANDLE text) { + assert(hp != NOPOLY); // printtag() may only be called from a polygon code block + + if (PolyTagState(hp) == TAG_OFF) { + SetPolyTagState(hp, TAG_ON); + SetPolyTagHandle(hp, text); + } +} + +/** + * quitgame + */ +void quitgame(void) { + stopmidi(); + stopsample(); + _vm->quitFlag = true; +} + +/** + * Return a random number between optional limits. + */ +int dw_random(int n1, int n2, int norpt) { + int i = 0; + uint32 value; + + do { + value = n1 + _vm->getRandomNumber(n2 - n1); + } while ((lastValue == value) && (norpt == RAND_NORPT) && (++i <= 10)); + + lastValue = value; + return value; +} + +/** + * resetidletime + */ +void resetidletime(void) { + resetUserEventTime(); +} + +/** + * restartgame + */ +void restartgame(void) { + stopmidi(); + stopsample(); + bRestart = true; +} + +/** + * Restore saved scene. + */ +void restore_scene(bool bFade) { + UnSuspendHook(); + PleaseRestoreScene(bFade); +} + +/** + * runmode + */ +int runmode(void) { + return clRunMode; +} + +/** + * sampleplaying + */ +bool sampleplaying(bool escOn, int myescEvent) { + // escape effects introduced 14/12/95 to fix + // while (sampleplaying()) pause; + + if (escOn && myescEvent != GetEscEvents()) + return false; + + return _vm->_sound->sampleIsPlaying(); +} + +/** + * Save current scene. + */ +void save_scene(CORO_PARAM) { + PleaseSaveScene(coroParam); + SuspendHook(); +} + +/** + * scalingreels + */ +void scalingreels(int actor, int scale, int direction, + SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) { + + setscalingreels(actor, scale, direction, left, right, forward, away); +} + +/** + * Return the icon that caused the CONVERSE event. + */ + +int scanicon(void) { + return convIcon(); +} + +/** + * Scroll the screen to target co-ordinates. + */ + +void scroll(CORO_PARAM, int x, int y, int iter, bool comp, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + int mycount; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + if (escOn && myescEvent != GetEscEvents()) { + // Instant completion! + offset(x, y); + } else { + _ctx->mycount = ++scrollCount; + + ScrollTo(x, y, iter); + + if (comp) { + int Loffset, Toffset; + do { + CORO_SLEEP(1); + + // If escapable and ESCAPE is pressed... + if (escOn && myescEvent != GetEscEvents()) { + // Instant completion! + offset(x, y); + break; + } + + // give up if have been superseded + if (_ctx->mycount != scrollCount) + CORO_KILL_SELF(); + + PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); + } while (Loffset != x || Toffset != y); + } + } + CORO_END_CODE; +} + +/** + * Un-kill an actor. + */ +void setactor(int actor) { + EnableActor(actor); +} + +/** + * Turn a blocking polygon on. + */ + +void setblock(int blockno) { + EnableBlock(blockno); +} + +/** + * Turn an exit on. + */ + +void setexit(int exitno) { + EnableExit(exitno); +} + +/** + * Guess what. + */ +void setinvlimit(int invno, int n) { + InvSetLimit(invno, n); +} + +/** + * Guess what. + */ +void setinvsize(int invno, int MinWidth, int MinHeight, + int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) { + InvSetSize(invno, MinWidth, MinHeight, StartWidth, StartHeight, MaxWidth, MaxHeight); +} + +/** + * Guess what. + */ +void setlanguage(LANGUAGE lang) { + assert(lang == TXT_ENGLISH || lang == TXT_FRENCH + || lang == TXT_GERMAN || lang == TXT_ITALIAN + || lang == TXT_SPANISH); // ensure language is valid + + ChangeLanguage(lang); +} + +/** + * Set palette + */ +void setpalette(SCNHANDLE hPal, bool escOn, int myescEvent) { + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + ChangePalette(hPal); +} + +/** + * Turn a tag on. + */ +void settag(int tagno) { + EnableTag(tagno); +} + +/** + * Initialise a timer. + */ +void settimer(int timerno, int start, bool up, bool frame) { + DwSetTimer(timerno, start, up != 0, frame != 0); +} + +#ifdef DEBUG +/** + * Enable display of diagnostic co-ordinates. + */ +void showpos(void) { + setshowpos(); +} + +/** + * Enable display of diagnostic co-ordinates. + */ +void showstring(void) { + setshowstring(); +} +#endif + +/** + * Special play - slow down associated actor's movement while the play + * is running. After the play, position the actor where the play left + * it and continue walking, if the actor still is. + */ + +void splay(CORO_PARAM, int sf, SCNHANDLE film, int x, int y, bool complete, int actorid, bool escOn, int myescEvent) { + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + play(coroParam, film, x, y, complete, actorid, true, sf, escOn, myescEvent, false); +} + +/** + * (Re)Position an actor. + * If moving actor is not around yet in this scene, start it up. + */ + +void stand(int actor, int x, int y, SCNHANDLE film) { + PMACTOR pActor; // Moving actor structure + + pActor = GetMover(actor); + if (pActor) { + if (pActor->MActorState == NO_MACTOR) { + // create a moving actor process + MActorProcessCreate(x, y, (actor == LEAD_ACTOR) ? LeadId() : actor, pActor); + + if (film == TF_NONE) { + SetMActorStanding(pActor); + } else { + switch (film) { + case TF_NONE: + break; + + case TF_UP: + SetMActorDirection(pActor, AWAY); + SetMActorStanding(pActor); + break; + case TF_DOWN: + SetMActorDirection(pActor, FORWARD); + SetMActorStanding(pActor); + break; + case TF_LEFT: + SetMActorDirection(pActor, LEFTREEL); + SetMActorStanding(pActor); + break; + case TF_RIGHT: + SetMActorDirection(pActor, RIGHTREEL); + SetMActorStanding(pActor); + break; + + default: + AlterMActor(pActor, film, AR_NORMAL); + break; + } + } + } else { + switch (film) { + case TF_NONE: + if (x != -1 && y != -1) + MoveMActor(pActor, x, y); + break; + + case TF_UP: + SetMActorDirection(pActor, AWAY); + if (x != -1 && y != -1) + MoveMActor(pActor, x, y); + SetMActorStanding(pActor); + break; + case TF_DOWN: + SetMActorDirection(pActor, FORWARD); + if (x != -1 && y != -1) + MoveMActor(pActor, x, y); + SetMActorStanding(pActor); + break; + case TF_LEFT: + SetMActorDirection(pActor, LEFTREEL); + if (x != -1 && y != -1) + MoveMActor(pActor, x, y); + SetMActorStanding(pActor); + break; + case TF_RIGHT: + SetMActorDirection(pActor, RIGHTREEL); + if (x != -1 && y != -1) + MoveMActor(pActor, x, y); + SetMActorStanding(pActor); + break; + + default: + if (x != -1 && y != -1) + MoveMActor(pActor, x, y); + AlterMActor(pActor, film, AR_NORMAL); + break; + } + } + } else if (actor == NULL_ACTOR) { + // + } else { + assert(film != 0); // Trying to play NULL film + + // Kick off the play and return. + playFilm(film, x, y, actor, false, 0, false, 0, false); + } +} + +/** + * Position the actor at the polygon's tag node. + */ +void standtag(int actor, HPOLYGON hp) { + SCNHANDLE film; + int pnodex, pnodey; + + assert(hp != NOPOLY); // standtag() may only be called from a polygon code block + + // Lead actor uses tag node film + film = getPolyFilm(hp); + getPolyNode(hp, &pnodex, &pnodey); + if (film && (actor == LEAD_ACTOR || actor == LeadId())) + stand(actor, pnodex, pnodey, film); + else + stand(actor, pnodex, pnodey, 0); +} + +/** + * Kill a moving actor's walk. + */ +void stop(int actor) { + PMACTOR pActor; + + pActor = GetMover(actor); + assert(pActor); // Trying to stop a null actor + + GetToken(pActor->actorToken); // Kill the walk process + pActor->stop = true; // Cause the actor to stop + FreeToken(pActor->actorToken); +} + +void stopmidi(void) { + StopMidi(); // Stop any currently playing midi +} + +void stopsample(void) { + _vm->_sound->stopAllSamples(); // Stop any currently playing sample +} + +void subtitles(int onoff) { + assert (onoff == ST_ON || onoff == ST_OFF); + + if (isJapanMode()) + return; // Subtitles are always off in JAPAN version (?) + + if (onoff == ST_ON) + bSubtitles = true; + else + bSubtitles = false; +} + +/** + * Special walk. + * Walk into or out of a legal path. + */ +void swalk(CORO_PARAM, int actor, int x1, int y1, int x2, int y2, SCNHANDLE film, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + bool took_control; // Set if this function takes control + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + // For lead actor, lock out the user (if not already locked out) + if (actor == LeadId() || actor == LEAD_ACTOR) + _ctx->took_control = GetControl(CONTROL_OFFV2); + else + _ctx->took_control = false; + + HPOLYGON hPath; + + hPath = InPolygon(x1, y1, PATH); + if (hPath != NOPOLY) { + // Walking out of a path + stand(actor, x1, y1, 0); + } else { + hPath = InPolygon(x2, y2, PATH); + // One of them has to be in a path + assert(hPath != NOPOLY); //one co-ordinate must be in a legal path + + // Walking into a path + stand(actor, x2, y2, 0); // Get path's characteristics + stand(actor, x1, y1, 0); + } + + CORO_INVOKE_ARGS(walk, (CORO_SUBCTX, actor, x2, y2, film, 0, true, escOn, myescEvent)); + + // Free control if we took it + if (_ctx->took_control) + control(CONTROL_ON); + + CORO_END_CODE; +} + +/** + * Define a tagged actor. + */ + +void tagactor(int actor, SCNHANDLE text, int tp) { + Tag_Actor(actor, text, tp); +} + +/** + * Text goes over actor's head while actor plays the talk reel. + */ + +void FinishTalkingReel(PMACTOR pActor, int actor) { + if (pActor) { + SetMActorStanding(pActor); + AlterMActor(pActor, 0, AR_POPREEL); + } else { + setActorTalking(actor, false); + playFilm(getActorPlayFilm(actor), -1, -1, 0, false, 0, false, 0, false); + } +} + +void talk(CORO_PARAM, SCNHANDLE film, const SCNHANDLE text, int actorid, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + int Loffset, Toffset; // Top left of display + int actor; // The speaking actor + PMACTOR pActor; // For moving actors + int myleftEvent; + int ticks; + bool bTookControl; // Set if this function takes control + bool bTookTags; // Set if this function disables tags + OBJECT *pText; // text object pointer + bool bSample; // Set if a sample is playing + bool bTalkReel; // Set while talk reel is playing + Audio::SoundHandle handle; + int timeout; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->Loffset = 0; + _ctx->Toffset = 0; + _ctx->ticks = 0; + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + _ctx->myleftEvent = GetLeftEvents(); + + // If this actor is dead, call a stop to the calling process + if (actorid && !actorAlive(actorid)) + CORO_KILL_SELF(); + + /* + * Find out which actor is talking + * and with which direction if no film supplied + */ + TFTYPE direction; + switch (film) { + case TF_NONE: + case TF_UP: + case TF_DOWN: + case TF_LEFT: + case TF_RIGHT: + _ctx->actor = LeadId(); // If no film, actor is lead actor + direction = (TFTYPE)film; + break; + + default: + _ctx->actor = extractActor(film); + assert(_ctx->actor); // talk() - no actor ID in the reel + direction = TF_BOGUS; + break; + } + + /* + * Lock out the user (for lead actor, if not already locked out) + * May need to disable tags for other actors + */ + if (_ctx->actor == LeadId()) + _ctx->bTookControl = GetControl(CONTROL_OFF); + else + _ctx->bTookControl = false; + _ctx->bTookTags = DisableTagsIfEnabled(); + + /* + * Kick off the voice sample + */ + if (volVoice != 0 && _vm->_sound->sampleExists(text)) { + _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle); + _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle); + } else + _ctx->bSample = false; + + /* + * Replace actor with the talk reel, saving the current one + */ + _ctx->pActor = GetMover(_ctx->actor); + if (_ctx->pActor) { + if (direction != TF_BOGUS) + film = GetMactorTalkReel(_ctx->pActor, direction); + AlterMActor(_ctx->pActor, film, AR_PUSHREEL); + } else { + setActorTalking(_ctx->actor, true); + setActorTalkFilm(_ctx->actor, film); + playFilm(film, -1, -1, 0, false, 0, escOn, myescEvent, false); + } + _ctx->bTalkReel = true; + CORO_SLEEP(1); // Allow the play to come in + + /* + * Display the text. + */ + _ctx->pText = NULL; + if (isJapanMode()) { + _ctx->ticks = JAP_TEXT_TIME; + } else if (bSubtitles || !_ctx->bSample) { + int aniX, aniY; // actor position + int xshift, yshift; + /* + * Work out where to display the text + */ + PlayfieldGetPos(FIELD_WORLD, &_ctx->Loffset, &_ctx->Toffset); + GetActorMidTop(_ctx->actor, &aniX, &aniY); + aniY -= _ctx->Toffset; + + setTextPal(getActorTcol(_ctx->actor)); + LoadStringRes(text, tBufferAddr(), TBUFSZ); + _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), + 0, aniX - _ctx->Loffset, aniY, hTalkFontHandle(), TXT_CENTRE); + assert(_ctx->pText); // talk() string produced NULL text; + if (IsTopWindow()) + MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT); + + /* + * Set bottom of text just above the speaker's head + * But don't go off the top of the screen + */ + yshift = aniY - MultiLowest(_ctx->pText) - 2; // Just above head + MultiMoveRelXY(_ctx->pText, 0, yshift); // + yshift = MultiHighest(_ctx->pText); + if (yshift < 4) + MultiMoveRelXY(_ctx->pText, 0, 4 - yshift); // Not off top + + /* + * Don't go off the side of the screen + */ + xshift = MultiRightmost(_ctx->pText) + 2; + if (xshift >= SCREEN_WIDTH) // Not off right + MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0); + xshift = MultiLeftmost(_ctx->pText) - 1; + if (xshift <= 0) // Not off left + MultiMoveRelXY(_ctx->pText, -xshift, 0); + /* + * Work out how long to talk. + * During this time, reposition the text if the screen scrolls. + */ + _ctx->ticks = TextTime(tBufferAddr()); + } + + _ctx->timeout = SAMPLETIMEOUT; + do { + // Keep text in place if scrolling + if (_ctx->pText != NULL) { + int nLoff, nToff; + + PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff); + if (nLoff != _ctx->Loffset || nToff != _ctx->Toffset) { + MultiMoveRelXY(_ctx->pText, _ctx->Loffset - nLoff, _ctx->Toffset - nToff); + _ctx->Loffset = nLoff; + _ctx->Toffset = nToff; + } + } + + CORO_SLEEP(1); + --_ctx->timeout; + + // Abort if escapable and ESCAPE is pressed + // Abort if left click - hardwired feature for talk! + // Abort if sample times out + if ((escOn && myescEvent != GetEscEvents()) + || (_ctx->myleftEvent != GetLeftEvents()) + || (_ctx->timeout <= 0)) + break; + + if (_ctx->bSample) { + // Wait for sample to end whether or not + if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) { + if (_ctx->pText == NULL || speedText == DEFTEXTSPEED) { + // No text or speed modification - just depends on sample + break; + } else { + // Talk reel stops at end of speech + FinishTalkingReel(_ctx->pActor, _ctx->actor); + _ctx->bTalkReel = false; + _ctx->bSample = false; + } + } + } else { + // No sample - just depends on time + if (_ctx->ticks-- <= 0) + break; + } + } while (1); + + /* + * The talk is over now - dump the text + * Stop the sample + * Restore the actor's film or standing reel + */ + if (_ctx->pText != NULL) + MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText); + _vm->_mixer->stopHandle(_ctx->handle); + if (_ctx->bTalkReel) + FinishTalkingReel(_ctx->pActor, _ctx->actor); + + /* + * Restore user control and tags, as appropriate + * And, finally, release the talk token. + */ + if (_ctx->bTookControl) + control(CONTROL_ON); + if (_ctx->bTookTags) + EnableTags(); + + CORO_END_CODE; +} + +/** + * talkat(actor, x, y, text) + */ +void talkat(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, bool escOn, int myescEvent) { + if (!coroParam) { + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + if (!isJapanMode() && (bSubtitles || !_vm->_sound->sampleExists(text))) + setTextPal(getActorTcol(actor)); + } + + print(coroParam, x, y, text, 0, 0, escOn, myescEvent); +} + +/** + * talkats(actor, x, y, text, sustain) + */ +void talkats(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, int sustain, bool escOn, int myescEvent) { + if (!coroParam) { + assert(sustain == 2); + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + if (!isJapanMode()) + setTextPal(getActorTcol(actor)); + } + + print(coroParam, x, y, text, 0, sustain, escOn, myescEvent); +} + +/** + * Set talk font's palette entry. + */ +void talkattr(int r1, int g1, int b1, bool escOn, int myescEvent) { + if (isJapanMode()) + return; + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + if (r1 > MAX_INTENSITY) r1 = MAX_INTENSITY; // } Ensure + if (g1 > MAX_INTENSITY) g1 = MAX_INTENSITY; // } within limits + if (b1 > MAX_INTENSITY) b1 = MAX_INTENSITY; // } + + setTextPal(RGB(r1, g1, b1)); +} + +/** + * Get a timer's current count. + */ +int timer(int timerno) { + return Timer(timerno); +} + +/** + * topplay(film, x, y, actor, hold, complete) + */ +void topplay(CORO_PARAM, SCNHANDLE film, int x, int y, int complete, int actorid, bool splay, int sfact, bool escOn, int myescTime) { + play(coroParam, film, x, y, complete, actorid, splay, sfact, escOn, myescTime, true); +} + +/** + * Open or close the 'top window' + */ + +void topwindow(int bpos) { + assert(bpos == TW_START || bpos == TW_END); + + switch (bpos) { + case TW_END: + KillInventory(); + break; + + case TW_START: + KillInventory(); + PopUpConf(TOPWIN); + break; + } +} + +/** + * unhookscene + */ + +void unhookscene(void) { + UnHookScene(); +} + +/** + * Un-define an actor as tagged. + */ + +void untagactor(int actor) { + UnTagActor(actor); +} + +/** + * vibrate + */ + +void vibrate(void) { +} + +/** + * waitframe(int actor, int frameNumber) + */ + +void waitframe(CORO_PARAM, int actor, int frameNumber, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + while (getActorSteps(actor) < frameNumber) { + CORO_SLEEP(1); + + // Abort if escapable and ESCAPE is pressed + if (escOn && myescEvent != GetEscEvents()) + break; + } + CORO_END_CODE; +} + +/** + * Return when a key pressed or button pushed. + */ + +void waitkey(CORO_PARAM, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + int startEvent; + int startX, startY; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + while (1) { + _ctx->startEvent = getUserEvents(); + // Store cursor position + while (!GetCursorXYNoWait(&_ctx->startX, &_ctx->startY, false)) + CORO_SLEEP(1); + + while (_ctx->startEvent == getUserEvents()) { + CORO_SLEEP(1); + + // Not necessary to monitor escape as it's an event anyway + + int curX, curY; + GetCursorXY(&curX, &curY, false); // Store cursor position + if (curX != _ctx->startX || curY != _ctx->startY) + break; + + if (IsConfWindow()) + break; + } + + if (!IsConfWindow()) + return; + + do { + CORO_SLEEP(1); + } while (IsConfWindow()); + + CORO_SLEEP(ONE_SECOND / 2); // Let it die down + } + CORO_END_CODE; +} + +/** + * Pause for requested time. + */ + +void waittime(CORO_PARAM, int time, bool frame, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + int time; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + // Don't do it if it's not wanted + if (escOn && myescEvent != GetEscEvents()) + return; + + if (!frame) + time *= ONE_SECOND; + + _ctx->time = time; + do { + CORO_SLEEP(1); + + // Abort if escapable and ESCAPE is pressed + if (escOn && myescEvent != GetEscEvents()) + break; + } while (_ctx->time--); + CORO_END_CODE; +} + +/** + * Set a moving actor off on a walk. + */ +void walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath, bool escOn, int myescEvent) { + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + PMACTOR pActor = GetMover(actor); + assert(pActor); // Can't walk a non-moving actor + + CORO_BEGIN_CODE(_ctx); + + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + stand(actor, x, y, 0); + return; + } + + assert(pActor->hCpath != NOPOLY); // moving actor not in path + + GetToken(pActor->actorToken); + SetActorDest(pActor, x, y, igPath, film); + DontScrollCursor(); + + if (hold == 2) { + ; + } else { + while (MAmoving(pActor)) { + CORO_SLEEP(1); + + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + stand(actor, x, y, 0); + FreeToken(pActor->actorToken); + return; + } + } + } + FreeToken(pActor->actorToken); + CORO_END_CODE; +} + +/** + * Set a moving actor off on a walk. + * Wait to see if its aborted or completed. + */ +void walked(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, bool escOn, int myescEvent, bool &retVal) { + // COROUTINE + CORO_BEGIN_CONTEXT; + int ticket; + CORO_END_CONTEXT(_ctx); + + PMACTOR pActor = GetMover(actor); + assert(pActor); // Can't walk a non-moving actor + + CORO_BEGIN_CODE(_ctx); + + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + stand(actor, x, y, 0); + retVal = true; + return; + } + + CORO_SLEEP(ONE_SECOND); + + assert(pActor->hCpath != NOPOLY); // moving actor not in path + + // Briefly aquire token to kill off any other normal walk + GetToken(pActor->actorToken); + FreeToken(pActor->actorToken); + + SetActorDest(pActor, x, y, false, film); + DontScrollCursor(); + + _ctx->ticket = GetActorTicket(pActor); + + while (MAmoving(pActor)) { + CORO_SLEEP(1); + + if (_ctx->ticket != GetActorTicket(pActor)) { + retVal = false; + return; + } + + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + stand(actor, x, y, 0); + retVal = true; + return; + } + } + + int endx, endy; + GetMActorPosition(pActor, &endx, &endy); + retVal = (_ctx->ticket == GetActorTicket(pActor) && endx == x && endy == y); + + CORO_END_CODE; +} + +/** + * Declare a moving actor. + */ +void walkingactor(uint32 id, SCNHANDLE *rp) { + PMACTOR pActor; // Moving actor structure + + SetMover(id); // Establish as a moving actor + pActor = GetMover(id); + assert(pActor); + + // Store all those reels + int i, j; + for (i = 0; i < 5; ++i) { + for (j = 0; j < 4; ++j) + pActor->WalkReels[i][j] = *rp++; + for (j = 0; j < 4; ++j) + pActor->StandReels[i][j] = *rp++; + } + + + for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) { + for (j = 0; j < 4; ++j) { + pActor->WalkReels[i][j] = pActor->WalkReels[4][j]; + pActor->StandReels[i][j] = pActor->StandReels[2][j]; + } + } +} + +/** + * Walk a moving actor towards the polygon's tag, but return when the + * actor enters the polygon. + */ + +void walkpoly(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myescEvent) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + PMACTOR pActor = GetMover(actor); + assert(pActor); // Can't walk a non-moving actor + + CORO_BEGIN_CODE(_ctx); + + int aniX, aniY; // cursor/actor position + int pnodex, pnodey; + + assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block + + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + standtag(actor, hp); + return; + } + + GetToken(pActor->actorToken); + getPolyNode(hp, &pnodex, &pnodey); + SetActorDest(pActor, pnodex, pnodey, false, film); + DoScrollCursor(); + + do { + CORO_SLEEP(1); + + if (escOn && myescEvent != GetEscEvents()) { + // Straight there if escaped + standtag(actor, hp); + FreeToken(pActor->actorToken); + return; + } + + GetMActorPosition(pActor, &aniX, &aniY); + } while (!MActorIsInPolygon(pActor, hp) && MAmoving(pActor)); + + FreeToken(pActor->actorToken); + + CORO_END_CODE; +} + +/** + * walktag(actor, reel, hold) + */ + +void walktag(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myescEvent) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + PMACTOR pActor = GetMover(actor); + assert(pActor); // Can't walk a non-moving actor + + CORO_BEGIN_CODE(_ctx); + + int pnodex, pnodey; + + assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block + + // Straight there if escaped + if (escOn && myescEvent != GetEscEvents()) { + standtag(actor, hp); + return; + } + + GetToken(pActor->actorToken); + getPolyNode(hp, &pnodex, &pnodey); + SetActorDest(pActor, pnodex, pnodey, false, film); + DoScrollCursor(); + + while (MAmoving(pActor)) { + CORO_SLEEP(1); + + if (escOn && myescEvent != GetEscEvents()) { + // Straight there if escaped + standtag(actor, hp); + FreeToken(pActor->actorToken); + return; + } + } + + // Adopt the tag-related reel + SCNHANDLE pfilm = getPolyFilm(hp); + + switch (pfilm) { + case TF_NONE: + break; + + case TF_UP: + SetMActorDirection(pActor, AWAY); + SetMActorStanding(pActor); + break; + case TF_DOWN: + SetMActorDirection(pActor, FORWARD); + SetMActorStanding(pActor); + break; + case TF_LEFT: + SetMActorDirection(pActor, LEFTREEL); + SetMActorStanding(pActor); + break; + case TF_RIGHT: + SetMActorDirection(pActor, RIGHTREEL); + SetMActorStanding(pActor); + break; + + default: + if (actor == LEAD_ACTOR || actor == LeadId()) + AlterMActor(pActor, pfilm, AR_NORMAL); + else + SetMActorStanding(pActor); + break; + } + + FreeToken(pActor->actorToken); + CORO_END_CODE; +} + +/** + * whichinventory + */ + +int whichinventory(void) { + return WhichInventoryOpen(); +} + + +/** + * Subtract one less that the number of parameters from pp + * pp then points to the first parameter. + * + * If the library function has no return value: + * return -(the number of parameters) to pop them from the stack + * + * If the library function has a return value: + * return -(the number of parameters - 1) to pop most of them from + * the stack, and stick the return value in pp[0] + * @param operand Library function + * @param pp Top of parameter stack + */ +int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const PINT_CONTEXT pic, RESUME_STATE *pResumeState) { + debug(7, "CallLibraryRoutine op %d (escOn %d, myescEvent %d)", operand, pic->escOn, pic->myescEvent); + switch (operand) { + case ACTORATTR: + pp -= 3; // 4 parameters + actorattr(pp[0], pp[1], pp[2], pp[3]); + return -4; + + case ACTORDIRECTION: + pp[0] = actordirection(pp[0]); + return 0; + + case ACTORREF: + error("actorref isn't a real function!"); + + case ACTORSCALE: + pp[0] = actorscale(pp[0]); + return 0; + + case ACTORSON: + actorson(); + return 0; + + case ACTORXPOS: + pp[0] = actorpos(ACTORXPOS, pp[0]); + return 0; + + case ACTORYPOS: + pp[0] = actorpos(ACTORYPOS, pp[0]); + return 0; + + case ADDICON: + addicon(pp[0]); + return -1; + + case ADDINV1: + addinv(INV_1, pp[0]); + return -1; + + case ADDINV2: + addinv(INV_2, pp[0]); + return -1; + + case ADDOPENINV: + addinv(INV_OPEN, pp[0]); + return -1; + + case AUXSCALE: + pp -= 13; // 14 parameters + auxscale(pp[0], pp[1], (SCNHANDLE *)(pp+2)); + return -14; + + case BACKGROUND: + background(pp[0]); + return -1; + + case CAMERA: + camera(pp[0]); + return -1; + + case CDLOAD: + pp -= 1; // 2 parameters + cdload(pp[0], pp[1]); + return -2; + + case CDPLAY: + error("cdplay isn't a real function!"); + + case CLEARHOOKSCENE: + clearhookscene(); + return 0; + + case CLOSEINVENTORY: + closeinventory(); + return 0; + + case CONTROL: + control(pp[0]); + return -1; + + case CONVERSATION: + conversation(pp[0], pic->hpoly, pic->escOn, pic->myescEvent); + return -1; + + case CONVICON: + convicon(pp[0]); + return -1; + + case CURSORXPOS: + pp[0] = cursorpos(CURSORXPOS); + return 0; + + case CURSORYPOS: + pp[0] = cursorpos(CURSORYPOS); + return 0; + + case CUTSCENE: + error("cutscene isn't a real function!"); + + case DEC_CONVW: + pp -= 7; // 8 parameters + dec_convw(pp[0], pp[1], pp[2], pp[3], + pp[4], pp[5], pp[6], pp[7]); + return -8; + + case DEC_CSTRINGS: + pp -= 19; // 20 parameters + dec_cstrings((SCNHANDLE *)pp); + return -20; + + case DEC_CURSOR: + dec_cursor(pp[0]); + return -1; + + case DEC_FLAGS: + dec_flags(pp[0]); + return -1; + + case DEC_INV1: + pp -= 7; // 8 parameters + dec_inv1(pp[0], pp[1], pp[2], pp[3], + pp[4], pp[5], pp[6], pp[7]); + return -8; + + case DEC_INV2: + pp -= 7; // 8 parameters + dec_inv2(pp[0], pp[1], pp[2], pp[3], + pp[4], pp[5], pp[6], pp[7]); + return -8; + + case DEC_INVW: + dec_invw(pp[0]); + return -1; + + case DEC_LEAD: + pp -= 61; // 62 parameters + dec_lead(pp[0], (SCNHANDLE *)&pp[1], pp[61]); + return -62; + + case DEC_TAGFONT: + dec_tagfont(pp[0]); + return -1; + + case DEC_TALKFONT: + dec_talkfont(pp[0]); + return -1; + + case DELICON: + delicon(pp[0]); + return -1; + + case DELINV: + delinv(pp[0]); + return -1; + + case EFFECTACTOR: + assert(pic->event == ENTER || pic->event == LEAVE); // effectactor() must be from effect poly code + + pp[0] = pic->actorid; + return 0; + + case ENABLEF1: + enablef1(); + return 0; + + case EVENT: + pp[0] = pic->event; + return 0; + + case FADEMIDI: + fademidi(coroParam, pp[0]); + return -1; + + case FRAMEGRAB: + return -1; + + case GETINVLIMIT: + pp[0] = getinvlimit(pp[0]); + return 0; + + case HASRESTARTED: + pp[0] = hasrestarted(); + return 0; + + case HELDOBJECT: + pp[0] = heldobject(); + return 0; + + case HIDE: + hide(pp[0]); + return -1; + + case HOOKSCENE: + pp -= 2; // 3 parameters + hookscene(pp[0], pp[1], pp[2]); + return -3; + + case IDLETIME: + pp[0] = idletime(); + return 0; + + case ININVENTORY: + pp[0] = ininventory(pp[0]); + return 0; // using return value + + case INVDEPICT: + pp -= 1; // 2 parameters + invdepict(pp[0], pp[1]); + return -2; + + case INVENTORY: + inventory(pp[0], pic->escOn, pic->myescEvent); + return -1; + + case INWHICHINV: + pp[0] = inwhichinv(pp[0]); + return 0; // using return value + + case KILLACTOR: + killactor(pp[0]); + return -1; + + case KILLBLOCK: + killblock(pp[0]); + return -1; + + case KILLEXIT: + killexit(pp[0]); + return -1; + + case KILLTAG: + killtag(pp[0]); + return -1; + + case LEFTOFFSET: + pp[0] = ltoffset(LEFTOFFSET); + return 0; + + case MOVECURSOR: + pp -= 1; // 2 parameters + movecursor(pp[0], pp[1]); + return -2; + + case NEWSCENE: + pp -= 2; // 3 parameters + if (*pResumeState == RES_2) + *pResumeState = RES_NOT; + else + newscene(coroParam, pp[0], pp[1], pp[2]); + return -3; + + case NOBLOCKING: + noblocking(); + return 0; + + case NOSCROLL: + pp -= 3; // 4 parameters + noscroll(pp[0], pp[1], pp[2], pp[3]); + return -4; + + case OBJECTHELD: + objectheld(pp[0]); + return -1; + + case OFFSET: + pp -= 1; // 2 parameters + offset(pp[0], pp[1]); + return -2; + + case PLAY: + pp -= 5; // 6 parameters + + if (pic->event == ENTER || pic->event == LEAVE) + play(coroParam, pp[0], pp[1], pp[2], pp[5], 0, false, 0, pic->escOn, pic->myescEvent, false); + else + play(coroParam, pp[0], pp[1], pp[2], pp[5], pic->actorid, false, 0, pic->escOn, pic->myescEvent, false); + return -6; + + case PLAYMIDI: + pp -= 2; // 3 parameters + playmidi(coroParam, pp[0], pp[1], pp[2]); + return -3; + + case PLAYRTF: + error("playrtf only applies to cdi!"); + + case PLAYSAMPLE: + pp -= 1; // 2 parameters + playsample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); + return -2; + + case PREPARESCENE: + preparescene(pp[0]); + return -1; + + case PRINT: + pp -= 5; // 6 parameters + /* pp[2] was intended to be attribute */ + print(coroParam, pp[0], pp[1], pp[3], pp[4], pp[5], pic->escOn, pic->myescEvent); + return -6; + + case PRINTOBJ: + printobj(coroParam, pp[0], pic->pinvo, pic->event); + return -1; + + case PRINTTAG: + printtag(pic->hpoly, pp[0]); + return -1; + + case QUITGAME: + quitgame(); + return 0; + + case RANDOM: + pp -= 2; // 3 parameters + pp[0] = dw_random(pp[0], pp[1], pp[2]); + return -2; // One holds return value + + case RESETIDLETIME: + resetidletime(); + return 0; + + case RESTARTGAME: + restartgame(); + return 0; + + case RESTORE_CUT: + restore_scene(false); + return 0; + + case RESTORE_SCENE: + restore_scene(true); + return 0; + + case RUNMODE: + pp[0] = runmode(); + return 0; + + case SAMPLEPLAYING: + pp[0] = sampleplaying(pic->escOn, pic->myescEvent); + return 0; + + case SAVE_SCENE: + if (*pResumeState == RES_1) + *pResumeState = RES_2; + else + save_scene(coroParam); + return 0; + + case SCALINGREELS: + pp -= 6; // 7 parameters + scalingreels(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]); + return -7; + + case SCANICON: + pp[0] = scanicon(); + return 0; + + case SCROLL: + pp -= 3; // 4 parameters + scroll(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent); + return -4; + + case SETACTOR: + setactor(pp[0]); + return -1; + + case SETBLOCK: + setblock(pp[0]); + return -1; + + case SETEXIT: + setexit(pp[0]); + return -1; + + case SETINVLIMIT: + pp -= 1; // 2 parameters + setinvlimit(pp[0], pp[1]); + return -2; + + case SETINVSIZE: + pp -= 6; // 7 parameters + setinvsize(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]); + return -7; + + case SETLANGUAGE: + setlanguage((LANGUAGE)pp[0]); + return -1; + + case SETPALETTE: + setpalette(pp[0], pic->escOn, pic->myescEvent); + return -1; + + case SETTAG: + settag(pp[0]); + return -1; + + case SETTIMER: + pp -= 3; // 4 parameters + settimer(pp[0], pp[1], pp[2], pp[3]); + return -4; + + case SHOWPOS: +#ifdef DEBUG + showpos(); +#endif + return 0; + + case SHOWSTRING: +#ifdef DEBUG + showstring(); +#endif + return 0; + + case SPLAY: + pp -= 6; // 7 parameters + + if (pic->event == ENTER || pic->event == LEAVE) + splay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], 0, pic->escOn, pic->myescEvent); + else + splay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], pic->actorid, pic->escOn, pic->myescEvent); + return -7; + + case STAND: + pp -= 3; // 4 parameters + stand(pp[0], pp[1], pp[2], pp[3]); + return -4; + + case STANDTAG: + standtag(pp[0], pic->hpoly); + return -1; + + case STOP: + stop(pp[0]); + return -1; + + case STOPMIDI: + stopmidi(); + return 0; + + case STOPSAMPLE: + stopsample(); + return 0; + + case SUBTITLES: + subtitles(pp[0]); + return -1; + + case SWALK: + pp -= 5; // 6 parameters + swalk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pic->escOn, pic->myescEvent); + return -6; + + case TAGACTOR: + pp -= 2; // 3 parameters + tagactor(pp[0], pp[1], pp[2]); + return -3; + + case TALK: + pp -= 1; // 2 parameters + + if (pic->event == ENTER || pic->event == LEAVE) + talk(coroParam, pp[0], pp[1], 0, pic->escOn, pic->myescEvent); + else + talk(coroParam, pp[0], pp[1], pic->actorid, pic->escOn, pic->myescEvent); + return -2; + + case TALKAT: + pp -= 3; // 4 parameters + talkat(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent); + return -4; + + case TALKATS: + pp -= 4; // 5 parameters + talkats(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pic->escOn, pic->myescEvent); + return -5; + + case TALKATTR: + pp -= 2; // 3 parameters + talkattr(pp[0], pp[1], pp[2], pic->escOn, pic->myescEvent); + return -3; + + case TIMER: + pp[0] = timer(pp[0]); + return 0; + + case TOPOFFSET: + pp[0] = ltoffset(TOPOFFSET); + return 0; + + case TOPPLAY: + pp -= 5; // 6 parameters + topplay(coroParam, pp[0], pp[1], pp[2], pp[5], pic->actorid, false, 0, pic->escOn, pic->myescEvent); + return -6; + + case TOPWINDOW: + topwindow(pp[0]); + return -1; + + case TRYPLAYSAMPLE: + pp -= 1; // 2 parameters + tryplaysample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); + return -2; + + case UNHOOKSCENE: + unhookscene(); + return 0; + + case UNTAGACTOR: + untagactor(pp[0]); + return -1; + + case VIBRATE: + vibrate(); + return 0; + + case WAITKEY: + waitkey(coroParam, pic->escOn, pic->myescEvent); + return 0; + + case WAITFRAME: + pp -= 1; // 2 parameters + waitframe(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); + return -2; + + case WAITTIME: + pp -= 1; // 2 parameters + waittime(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent); + return -2; + + case WALK: + pp -= 4; // 5 parameters + walk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], false, pic->escOn, pic->myescEvent); + return -5; + + case WALKED: { + pp -= 3; // 4 parameters + bool tmp; + walked(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent, tmp); + if (!coroParam) { + // Only write the result to the stack if walked actually completed running. + pp[0] = tmp; + } + } + return -3; + + case WALKINGACTOR: + pp -= 40; // 41 parameters + walkingactor(pp[0], (SCNHANDLE *)&pp[1]); + return -41; + + case WALKPOLY: + pp -= 2; // 3 parameters + walkpoly(coroParam, pp[0], pp[1], pic->hpoly, pic->escOn, pic->myescEvent); + return -3; + + case WALKTAG: + pp -= 2; // 3 parameters + walktag(coroParam, pp[0], pp[1], pic->hpoly, pic->escOn, pic->myescEvent); + return -3; + + case WHICHINVENTORY: + pp[0] = whichinventory(); + return 0; + + default: + error("Unsupported library function"); + } + + error("Can't possibly get here"); +} + + +} // end of namespace Tinsel diff --git a/engines/tinsel/tinlib.h b/engines/tinsel/tinlib.h new file mode 100644 index 0000000000..001de70896 --- /dev/null +++ b/engines/tinsel/tinlib.h @@ -0,0 +1,41 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * Text utility defines + */ + +#ifndef TINSEL_TINLIB_H // prevent multiple includes +#define TINSEL_TINLIB_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +// Library functions in TINLIB.C + +void control(int param); +void stand(int actor, int x, int y, SCNHANDLE film); + +} // end of namespace Tinsel + +#endif // TINSEL_TINLIB_H diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp new file mode 100644 index 0000000000..ec16e73294 --- /dev/null +++ b/engines/tinsel/tinsel.cpp @@ -0,0 +1,1005 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/endian.h" +#include "common/events.h" +#include "common/keyboard.h" +#include "common/file.h" +#include "common/savefile.h" +#include "common/config-manager.h" +#include "common/stream.h" + +#include "graphics/cursorman.h" + +#include "base/plugins.h" +#include "base/version.h" + +#include "sound/mididrv.h" +#include "sound/mixer.h" +#include "sound/audiocd.h" + +#include "tinsel/actors.h" +#include "tinsel/background.h" +#include "tinsel/config.h" +#include "tinsel/cursor.h" +#include "tinsel/dw.h" +#include "tinsel/events.h" +#include "tinsel/faders.h" +#include "tinsel/film.h" +#include "tinsel/handle.h" +#include "tinsel/heapmem.h" // MemoryInit +#include "tinsel/inventory.h" +#include "tinsel/music.h" +#include "tinsel/object.h" +#include "tinsel/pid.h" +#include "tinsel/savescn.h" +#include "tinsel/scn.h" +#include "tinsel/serializer.h" +#include "tinsel/sound.h" +#include "tinsel/strres.h" +#include "tinsel/timers.h" +#include "tinsel/tinsel.h" + +namespace Tinsel { + +//----------------- EXTERNAL FUNCTIONS --------------------- + +// In BG.CPP +extern void SetDoFadeIn(bool tf); +extern void DropBackground(void); + +// In CURSOR.CPP +extern void CursorProcess(CORO_PARAM); + +// In INVENTORY.CPP +extern void InventoryProcess(CORO_PARAM); + +// In SCENE.CPP +extern void PrimeBackground( void ); +extern void NewScene(SCNHANDLE scene, int entry); +extern SCNHANDLE GetSceneHandle(void); + +// In TIMER.CPP +extern void FettleTimers(void); +extern void RebootTimers(void); + +//----------------- FORWARD DECLARATIONS --------------------- +void SetNewScene(SCNHANDLE scene, int entrance, int transition); + +//----------------- GLOBAL GLOBAL DATA -------------------- + +bool bRestart = false; +bool bHasRestarted = false; + +#ifdef DEBUG +bool bFast; // set to make it go ludicrously fast +#endif + +//----------------- LOCAL GLOBAL DATA -------------------- + +struct Scene { + SCNHANDLE scene; // Memory handle for scene + int entry; // Entrance number + int trans; // Transition - not yet used +}; + +static Scene NextScene = { 0, 0, 0 }; +static Scene HookScene = { 0, 0, 0 }; +static Scene DelayedScene = { 0, 0, 0 }; + +static bool bHookSuspend = false; + +static uint32 lastLeftClick = 0, lastRightClick = 0; + +static PROCESS *pMouseProcess = 0; +static PROCESS *pKeyboardProcess = 0; + +// Stack of pending mouse button events +Common::List<Common::EventType> mouseButtons; + +// Stack of pending keypresses +Common::List<Common::Event> keypresses; + +//----------------- LOCAL PROCEDURES -------------------- + +/** + * Process to handle keypresses + */ +void KeyboardProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + while (true) { + if (keypresses.empty()) { + // allow scheduling + CORO_SLEEP(1); + continue; + } + + // Get the next keyboard event off the stack + Common::Event evt = *keypresses.begin(); + keypresses.erase(keypresses.begin()); + + // Switch for special keys + switch (evt.kbd.keycode) { + // Drag action + case Common::KEYCODE_LALT: + case Common::KEYCODE_RALT: + if (evt.type == Common::EVENT_KEYDOWN) { + if (!bSwapButtons) + ProcessButEvent(BE_RDSTART); + else + ProcessButEvent(BE_LDSTART); + } else { + if (!bSwapButtons) + ProcessButEvent(BE_LDEND); + else + ProcessButEvent(BE_RDEND); + } + continue; + + case Common::KEYCODE_LCTRL: + case Common::KEYCODE_RCTRL: + if (evt.type == Common::EVENT_KEYDOWN) { + ProcessKeyEvent(LOOK_KEY); + } else { + // Control key release + } + continue; + + default: + break; + } + + // At this point only key down events need processing + if (evt.type == Common::EVENT_KEYUP) + continue; + + if (_vm->_keyHandler != NULL) + // Keyboard is hooked, so pass it on to that handler first + if (!_vm->_keyHandler(evt.kbd)) + continue; + + switch (evt.kbd.keycode) { + /*** SPACE = WALKTO ***/ + case Common::KEYCODE_SPACE: + ProcessKeyEvent(WALKTO_KEY); + continue; + + /*** RETURN = ACTION ***/ + case Common::KEYCODE_RETURN: + case Common::KEYCODE_KP_ENTER: + ProcessKeyEvent(ACTION_KEY); + continue; + + /*** l = LOOK ***/ + case Common::KEYCODE_l: // LOOK + ProcessKeyEvent(LOOK_KEY); + continue; + + case Common::KEYCODE_ESCAPE: + // WORKAROUND: Check if any of the starting logo screens are active, and if so + // manually skip to the title screen, allowing them to be bypassed + { + int sceneOffset = (_vm->getFeatures() & GF_SCNFILES) ? 1 : 0; + int sceneNumber = (GetSceneHandle() >> SCNHANDLE_SHIFT) - sceneOffset; + if ((language == TXT_GERMAN) && + ((sceneNumber >= 25 && sceneNumber <= 27) || (sceneNumber == 17))) { + // Skip to title screen + // It seems the German CD version uses scenes 25,26,27,17 for the intro, + // instead of 13,14,15,11; also, the title screen is 11 instead of 10 + SetNewScene((11 + sceneOffset) << SCNHANDLE_SHIFT, 1, TRANS_CUT); + } else if ((sceneNumber >= 13) && (sceneNumber <= 15) || (sceneNumber == 11)) { + // Skip to title screen + SetNewScene((10 + sceneOffset) << SCNHANDLE_SHIFT, 1, TRANS_CUT); + } else { + // Not on an intro screen, so process the key normally + ProcessKeyEvent(ESC_KEY); + } + } + continue; + +#ifdef SLOW_RINCE_DOWN + case '>': + AddInterlude(1); + continue; + case '<': + AddInterlude(-1); + continue; +#endif + + case Common::KEYCODE_F1: + // Options dialog + ProcessKeyEvent(OPTION_KEY); + continue; + case Common::KEYCODE_F5: + // Save game + ProcessKeyEvent(SAVE_KEY); + continue; + case Common::KEYCODE_F7: + // Load game + ProcessKeyEvent(LOAD_KEY); + continue; + case Common::KEYCODE_q: + if ((evt.kbd.flags == Common::KBD_CTRL) || (evt.kbd.flags == Common::KBD_ALT)) + ProcessKeyEvent(QUIT_KEY); + continue; + case Common::KEYCODE_PAGEUP: + case Common::KEYCODE_KP9: + ProcessKeyEvent(PGUP_KEY); + continue; + case Common::KEYCODE_PAGEDOWN: + case Common::KEYCODE_KP3: + ProcessKeyEvent(PGDN_KEY); + continue; + case Common::KEYCODE_HOME: + case Common::KEYCODE_KP7: + ProcessKeyEvent(HOME_KEY); + continue; + case Common::KEYCODE_END: + case Common::KEYCODE_KP1: + ProcessKeyEvent(END_KEY); + continue; + default: + ProcessKeyEvent(NOEVENT_KEY); + break; + } + } + CORO_END_CODE; +} + +/** + * Process to handle changes in the mouse buttons. + */ +void MouseProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + bool lastLWasDouble; + bool lastRWasDouble; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + _ctx->lastLWasDouble = false; + _ctx->lastRWasDouble = false; + + while (true) { + // FIXME: I'm still keeping the ctrl/Alt handling in the KeyProcess method. + // Need to make sure that this works correctly + //DragKeys(); + + if (mouseButtons.empty()) { + // allow scheduling + CORO_SLEEP(1); + continue; + } + + // get next mouse button event + Common::EventType type = *mouseButtons.begin(); + mouseButtons.erase(mouseButtons.begin()); + + switch (type) { + case Common::EVENT_LBUTTONDOWN: + // left button press + if (DwGetCurrentTime() - lastLeftClick < (uint32)dclickSpeed) { + // signal left drag start + ProcessButEvent(BE_LDSTART); + + // signal left double click event + ProcessButEvent(BE_DLEFT); + + _ctx->lastLWasDouble = true; + } else { + // signal left drag start + ProcessButEvent(BE_LDSTART); + + // signal left single click event + ProcessButEvent(BE_SLEFT); + + _ctx->lastLWasDouble = false; + } + break; + + case Common::EVENT_LBUTTONUP: + // left button release + + // update click timer + if (_ctx->lastLWasDouble == false) + lastLeftClick = DwGetCurrentTime(); + else + lastLeftClick -= dclickSpeed; + + // signal left drag end + ProcessButEvent(BE_LDEND); + break; + + case Common::EVENT_RBUTTONDOWN: + // right button press + + if (DwGetCurrentTime() - lastRightClick < (uint32)dclickSpeed) { + // signal right drag start + ProcessButEvent(BE_RDSTART); + + // signal right double click event + ProcessButEvent(BE_DRIGHT); + + _ctx->lastRWasDouble = true; + } else { + // signal right drag start + ProcessButEvent(BE_RDSTART); + + // signal right single click event + ProcessButEvent(BE_SRIGHT); + + _ctx->lastRWasDouble = false; + } + break; + + case Common::EVENT_RBUTTONUP: + // right button release + + // update click timer + if (_ctx->lastRWasDouble == false) + lastRightClick = DwGetCurrentTime(); + else + lastRightClick -= dclickSpeed; + + // signal right drag end + ProcessButEvent(BE_RDEND); + break; + + default: + break; + } + } + CORO_END_CODE; +} + +/** + * Installs the event driver processes + */ + +void EventsInstall(void) { + lastLeftClick = lastRightClick = DwGetCurrentTime(); + + pMouseProcess = CoroutineInstall(PID_MOUSE, MouseProcess, NULL, 0); + pKeyboardProcess = CoroutineInstall(PID_KEYBOARD, KeyboardProcess, NULL, 0); +} + +/** + * Removes the event driver processes + */ + +void EventsUninstall(void) { + ProcessKill(pMouseProcess); + ProcessKill(pKeyboardProcess); +} + +/** + * Run the master script. + * Continues between scenes, or until Interpret() returns. + */ +static void MasterScriptProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + PINT_CONTEXT pic; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + _ctx->pic = InitInterpretContext(GS_MASTER, 0, NOEVENT, NOPOLY, 0, NULL); + CORO_INVOKE_1(Interpret, _ctx->pic); + CORO_END_CODE; +} + +/** + * Store the facts pertaining to a scene change. + */ + +void SetNewScene(SCNHANDLE scene, int entrance, int transition) { + if (HookScene.scene == 0 || bHookSuspend) { + // This scene comes next + NextScene.scene = scene; + NextScene.entry = entrance; + NextScene.trans = transition; + } else { + // This scene gets delayed + DelayedScene.scene = scene; + DelayedScene.entry = entrance; + DelayedScene.trans = transition; + + // The hooked scene comes next + NextScene.scene = HookScene.scene; + NextScene.entry = HookScene.entry; + NextScene.trans = HookScene.trans; + + HookScene.scene = 0; + } +} + +void SetHookScene(SCNHANDLE scene, int entrance, int transition) { + assert(HookScene.scene == 0); // scene already hooked + + HookScene.scene = scene; + HookScene.entry = entrance; + HookScene.trans = transition; +} + +void UnHookScene(void) { + assert(DelayedScene.scene != 0); // no scene delayed + + // The delayed scene can go now + NextScene.scene = DelayedScene.scene; + NextScene.entry = DelayedScene.entry; + NextScene.trans = DelayedScene.trans; + + DelayedScene.scene = 0; +} + +void SuspendHook(void) { + bHookSuspend = true; +} + +void UnSuspendHook(void) { + bHookSuspend = false; +} + +void syncSCdata(Serializer &s) { + s.syncAsUint32LE(HookScene.scene); + s.syncAsSint32LE(HookScene.entry); + s.syncAsSint32LE(HookScene.trans); + + s.syncAsUint32LE(DelayedScene.scene); + s.syncAsSint32LE(DelayedScene.entry); + s.syncAsSint32LE(DelayedScene.trans); +} + + +//----------------------------------------------------------------------- + +static void RestoredProcess(CORO_PARAM) { + // COROUTINE + CORO_BEGIN_CONTEXT; + PINT_CONTEXT pic; + CORO_END_CONTEXT(_ctx); + + CORO_BEGIN_CODE(_ctx); + + // get the stuff copied to process when it was created + _ctx->pic = *((PINT_CONTEXT *)ProcessGetParamsSelf()); + + _ctx->pic = RestoreInterpretContext(_ctx->pic); + CORO_INVOKE_1(Interpret, _ctx->pic); + + CORO_END_CODE; +} + +void RestoreProcess(PINT_CONTEXT pic) { + CoroutineInstall(PID_TCODE, RestoredProcess, &pic, sizeof(pic)); +} + +void RestoreMasterProcess(PINT_CONTEXT pic) { + CoroutineInstall(PID_MASTER_SCR, RestoredProcess, &pic, sizeof(pic)); +} + +// FIXME: CountOut is used by ChangeScene +static int CountOut = 1; // == 1 for immediate start of first scene + +/** + * If a scene restore is going on, just return (we don't update the + * screen during this time). + * If a scene change is required, 'order' the data for the new scene and + * start a fade out and a countdown. + * When the count expires, the screen will have faded. Ensure the scene | + * is loaded, clear the screen, and start the new scene. + */ +void ChangeScene() { + + if (IsRestoringScene()) + return; + + if (NextScene.scene != 0) { + if (!CountOut) { + switch (NextScene.trans) { + case TRANS_CUT: + CountOut = 1; + break; + + case TRANS_FADE: + default: + // Trigger pre-load and fade and start countdown + CountOut = COUNTOUT_COUNT; + FadeOutFast(NULL); + break; + } + } else if (--CountOut == 0) { + ClearScreen(0L); + + NewScene(NextScene.scene, NextScene.entry); + NextScene.scene = 0; + + switch (NextScene.trans) { + case TRANS_CUT: + SetDoFadeIn(false); + break; + + case TRANS_FADE: + default: + SetDoFadeIn(true); + break; + } + } + } +} + +/** + * LoadBasicChunks + */ + +void LoadBasicChunks(void) { + byte *cptr; + int numObjects; + + // Allocate RAM for savescene data + InitialiseSs(); + + // CHUNK_TOTAL_ACTORS seems to be missing in the released version, hard coding a value + // TODO: Would be nice to just change 511 to MAX_SAVED_ALIVES + cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_ACTORS); + RegisterActors((cptr != NULL) ? READ_LE_UINT32(cptr) : 511); + + // CHUNK_TOTAL_GLOBALS seems to be missing in some versions. + // So if it is missing, set a reasonably high value for the number of globals. + cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_GLOBALS); + RegisterGlobals((cptr != NULL) ? READ_LE_UINT32(cptr) : 512); + + cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_TOTAL_OBJECTS); + numObjects = (cptr != NULL) ? READ_LE_UINT32(cptr) : 0; + + cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_OBJECTS); + +#ifdef SCUMM_BIG_ENDIAN + //convert to native endianness + INV_OBJECT *io = (INV_OBJECT *)cptr; + for (int i = 0; i < numObjects; i++, io++) { + io->id = FROM_LE_32(io->id); + io->hFilm = FROM_LE_32(io->hFilm); + io->hScript = FROM_LE_32(io->hScript); + io->attribute = FROM_LE_32(io->attribute); + } +#endif + + RegisterIcons(cptr, numObjects); + + cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_POLY); + if (cptr != NULL) + MaxPolygons(*cptr); +} + +//----------------- TinselEngine -------------------- + +// Global pointer to engine +TinselEngine *_vm; + +struct GameSettings { + const char *gameid; + const char *description; + byte id; + uint32 features; + const char *detectname; +}; + +static const GameSettings tinselSettings[] = { + {"tinsel", "Tinsel game", 0, 0, 0}, + + {NULL, NULL, 0, 0, NULL} +}; + +TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc) : + Engine(syst), _gameDescription(gameDesc), _screenSurface(true) { + _vm = this; + + // Setup mixer + _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); + + const GameSettings *g; + + const char *gameid = ConfMan.get("gameid").c_str(); + for (g = tinselSettings; g->gameid; ++g) + if (!scumm_stricmp(g->gameid, gameid)) + _gameId = g->id; + + int cd_num = ConfMan.getInt("cdrom"); + if (cd_num >= 0) + _system->openCD(cd_num); + + int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI); + bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); + //bool adlib = (midiDriver == MD_ADLIB); + + MidiDriver *driver = MidiDriver::createMidi(midiDriver); + if (native_mt32) + driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + + _music = new MusicPlayer(driver); + //_music->setNativeMT32(native_mt32); + //_music->setAdlib(adlib); + + _musicVolume = ConfMan.getInt("music_volume"); + + _sound = new SoundManager(this); + + _mousePos.x = 0; + _mousePos.y = 0; + _keyHandler = NULL; + _dosPlayerDir = 0; + quitFlag = false; +} + +TinselEngine::~TinselEngine() { + delete _sound; + delete _music; + delete _console; + FreeSs(); + FreeTextBuffer(); + FreeHandleTable(); + FreeActors(); + FreeObjectList(); + FreeGlobals(); + FreeProcessList(); +} + +int TinselEngine::init() { + // Initialize backend + _system->beginGFXTransaction(); + initCommonGFX(false); + _system->initSize(SCREEN_WIDTH, SCREEN_HEIGHT); + _system->endGFXTransaction(); + + _screenSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, 1); + + g_system->getEventManager()->registerRandomSource(_random, "tinsel"); + + _console = new Console(); + + // init memory manager + MemoryInit(); + + // load user configuration + ReadConfig(); + +#if 1 + // FIXME: The following is taken from RestartGame(). + // It may have to be adjusted a bit + RebootCursor(); + RebootDeadTags(); + RebootMovers(); + RebootTimers(); + RebootScalingReels(); + + DelayedScene.scene = HookScene.scene = 0; +#endif + + // Init palette and object managers, scheduler, keyboard and mouse + RestartDrivers(); + + // TODO: More stuff from dos_main.c may have to be added here + + // Set language - we'll be clever here and use the ScummVM language setting + language = TXT_ENGLISH; + switch (getLanguage()) { + case Common::FR_FRA: + language = TXT_FRENCH; + break; + case Common::DE_DEU: + language = TXT_GERMAN; + break; + case Common::IT_ITA: + language = TXT_ITALIAN; + break; + case Common::ES_ESP: + language = TXT_SPANISH; + break; + default: + language = TXT_ENGLISH; + } + ChangeLanguage(language); + + // load in graphics info + SetupHandleTable(); + + // Actors, globals and inventory icons + LoadBasicChunks(); + + return 0; +} + +Common::String TinselEngine::getSavegamePattern() const { + return _targetName + ".???"; +} + +Common::String TinselEngine::getSavegameFilename(int16 saveNum) const { + char filename[256]; + snprintf(filename, 256, "%s.%03d", getTargetName().c_str(), saveNum); + return filename; +} + +#define GAME_FRAME_DELAY (1000 / ONE_SECOND) + +int TinselEngine::go() { + uint32 timerVal = 0; + + // Continuous game processes + CreateConstProcesses(); + + // allow game to run in the background + //RestartBackgroundProcess(); // FIXME: is this still needed? + + //dumpMusic(); // dumps all of the game's music in external XMIDI files + +#if 0 + // Load game from specified slot, if any + // FIXME: Not working correctly right now + if (ConfMan.hasKey("save_slot")) { + getList(); + RestoreGame(ConfMan.getInt("save_slot")); + } +#endif + + // Foreground loop + + while (!quitFlag) { + assert(_console); + if (_console->isAttached()) + _console->onFrame(); + + // Check for time to do next game cycle + if ((g_system->getMillis() > timerVal + GAME_FRAME_DELAY)) { + timerVal = g_system->getMillis(); + AudioCD.updateCD(); + NextGameCycle(); + } + + if (bRestart) { + RestartGame(); + bRestart = false; + bHasRestarted = true; // Set restarted flag + } + + // Save/Restore scene file transfers + ProcessSRQueue(); + +#ifdef DEBUG + if (bFast) + continue; // run flat-out +#endif + // Loop processing events while there are any pending + while (pollEvent()); + + g_system->delayMillis(10); + } + + // Write configuration + WriteConfig(); + + return 0; +} + + +void TinselEngine::NextGameCycle(void) { + // + ChangeScene(); + + // Allow a user event for this schedule + ResetEcount(); + + // schedule process + Scheduler(); + + // redraw background + DrawBackgnd(); + + // Why waste resources on yet another process? + FettleTimers(); +} + + +bool TinselEngine::pollEvent() { + if (!g_system->getEventManager()->pollEvent(_event)) + return false; + + // Handle the various kind of events + switch (_event.type) { + case Common::EVENT_QUIT: + quitFlag = true; + break; + + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONDOWN: + case Common::EVENT_RBUTTONUP: + // Add button to queue for the mouse process + mouseButtons.push_back(_event.type); + break; + + case Common::EVENT_MOUSEMOVE: + _mousePos = _event.mouse; + break; + + case Common::EVENT_KEYDOWN: + case Common::EVENT_KEYUP: + KeyProcess(); + break; + + default: + break; + } + + return true; +} + +/** + * Start the processes that continue between scenes. + */ + +void TinselEngine::CreateConstProcesses(void) { + // Process to run the master script + CoroutineInstall(PID_MASTER_SCR, MasterScriptProcess, NULL, 0); + + // Processes to run the cursor and inventory, + CoroutineInstall(PID_CURSOR, CursorProcess, NULL, 0); + CoroutineInstall(PID_INVENTORY, InventoryProcess, NULL, 0); +} + +/** + * Restart the game + */ + +void TinselEngine::RestartGame(void) { + HoldItem(INV_NOICON); // Holding nothing + + DropBackground(); // No background + + // Ditches existing infrastructure background + PrimeBackground(); + + // Next scene change won't need to fade out + // -> reset the count used by ChangeScene + CountOut = 1; + + RebootCursor(); + RebootDeadTags(); + RebootMovers(); + RebootTimers(); + RebootScalingReels(); + + DelayedScene.scene = HookScene.scene = 0; + + // remove keyboard, mouse and joystick drivers + ChopDrivers(); + + // Init palette and object managers, scheduler, keyboard and mouse + RestartDrivers(); + + // Actors, globals and inventory icons + LoadBasicChunks(); + + // Continuous game processes + CreateConstProcesses(); +} + +/** + * Init palette and object managers, scheduler, keyboard and mouse. + */ + +void TinselEngine::RestartDrivers(void) { + // init the palette manager + ResetPalAllocator(); + + // init the object manager + KillAllObjects(); + + // init the process scheduler + InitScheduler(); + + // init the event handlers + EventsInstall(); + + // install sound driver + SoundInit(); + + // Set midi volume + SetMidiVolume(volMidi); +} + +/** + * Remove keyboard, mouse and joystick drivers. + */ + +void TinselEngine::ChopDrivers(void) { + // remove sound driver + SoundDeinit(); + + // remove event drivers + EventsUninstall(); +} + +/** + * Process a keyboard event + */ + +void TinselEngine::KeyProcess(void) { + + // Handle any special keys immediately + switch (_event.kbd.keycode) { + case Common::KEYCODE_d: + if ((_event.kbd.flags == Common::KBD_CTRL) && (_event.type == Common::EVENT_KEYDOWN)) { + // Activate the debugger + assert(_console); + _console->attach(); + return; + } + break; + default: + break; + } + + // Check for movement keys + int idx = 0; + switch (_event.kbd.keycode) { + case Common::KEYCODE_UP: + case Common::KEYCODE_KP8: + idx = MSK_UP; + break; + case Common::KEYCODE_DOWN: + case Common::KEYCODE_KP2: + idx = MSK_DOWN; + break; + case Common::KEYCODE_LEFT: + case Common::KEYCODE_KP4: + idx = MSK_LEFT; + break; + case Common::KEYCODE_RIGHT: + case Common::KEYCODE_KP6: + idx = MSK_RIGHT; + break; + default: + break; + } + if (idx != 0) { + if (_event.type == Common::EVENT_KEYDOWN) + _dosPlayerDir |= idx; + else + _dosPlayerDir &= ~idx; + return; + } + + // All other keypresses add to the queue for processing in KeyboardProcess + keypresses.push_back(_event); +} + +} // End of namespace Tinsel diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h new file mode 100644 index 0000000000..99136e0e7b --- /dev/null +++ b/engines/tinsel/tinsel.h @@ -0,0 +1,141 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_H +#define TINSEL_H + +#include "common/scummsys.h" +#include "common/system.h" +#include "common/events.h" +#include "common/keyboard.h" +#include "common/util.h" + +#include "sound/mididrv.h" +#include "sound/mixer.h" + +#include "engines/engine.h" +#include "tinsel/debugger.h" +#include "tinsel/graphics.h" +#include "tinsel/sound.h" + +namespace Tinsel { + +class MusicPlayer; +class SoundManager; + +enum TinselGameID { + GID_DW1 = 0, + GID_DW2 = 1 +}; + +enum TinselGameFeatures { + GF_DEMO = 1 << 0, + GF_CD = 1 << 1, + GF_FLOPPY = 1 << 2, + GF_SCNFILES = 1 << 3 +}; + +enum TinselEngineVersion { + TINSEL_V1 = 1 << 0, + TINSEL_V2 = 1 << 1 +}; + +struct TinselGameDescription; + +enum TinselKeyDirection { + MSK_LEFT = 1, MSK_RIGHT = 2, MSK_UP = 4, MSK_DOWN = 8, + MSK_DIRECTION = MSK_LEFT | MSK_RIGHT | MSK_UP | MSK_DOWN +}; + +typedef bool (*KEYFPTR)(const Common::KeyState &); + +class TinselEngine : public ::Engine { + int _gameId; + Common::KeyState _keyPressed; + Common::RandomSource _random; + Surface _screenSurface; + Common::Event _event; + Common::Point _mousePos; + uint8 _dosPlayerDir; + Console *_console; +protected: + + int init(); + int go(); + +public: + TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc); + virtual ~TinselEngine(); + int getGameId() { + return _gameId; + } + + const TinselGameDescription *_gameDescription; + uint32 getGameID() const; + uint32 getFeatures() const; + Common::Language getLanguage() const; + uint16 getVersion() const; + Common::Platform getPlatform() const; + bool quitFlag; + + SoundManager *_sound; + MusicPlayer *_music; + + KEYFPTR _keyHandler; +private: + //MusicPlayer *_music; + int _musicVolume; + + void NextGameCycle(void); + void CreateConstProcesses(void); + void RestartGame(void); + void RestartDrivers(void); + void ChopDrivers(void); + void KeyProcess(void); +public: + const Common::String getTargetName() const { return _targetName; } + Common::String getSavegamePattern() const; + Common::String getSavegameFilename(int16 saveNum) const; + Common::SaveFileManager *getSaveFileMan() { return _saveFileMan; } + Surface &screen() { return _screenSurface; } + + bool pollEvent(); + Common::Event event() { return _event; } + Common::Point getMousePosition() const { return _mousePos; } + void setMousePosition(const Common::Point &pt) { + g_system->warpMouse(pt.x, pt.y); + _mousePos = pt; + } + void divertKeyInput(KEYFPTR fptr) { _keyHandler = fptr; } + int getRandomNumber(int maxNumber) { return _random.getRandomNumber(maxNumber); } + uint8 getKeyDirection() const { return _dosPlayerDir; } +}; + +// Global reference to the TinselEngine object +extern TinselEngine *_vm; + +} // End of namespace Tinsel + +#endif /* TINSEL_H */ diff --git a/engines/tinsel/token.cpp b/engines/tinsel/token.cpp new file mode 100644 index 0000000000..e50290c3f0 --- /dev/null +++ b/engines/tinsel/token.cpp @@ -0,0 +1,129 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + * To ensure exclusive use of resources and exclusive control responsibilities. + */ + +#include "common/util.h" + +#include "tinsel/sched.h" +#include "tinsel/token.h" + +namespace Tinsel { + +//----------------- LOCAL GLOBAL DATA -------------------- + +struct Token { + PROCESS *proc; +}; + +static Token tokens[NUMTOKENS]; + + +/** + * Release all tokens held by this process, and kill the process. + */ +static void TerminateProcess(PROCESS *tProc) { + + // Release tokens held by the process + for (int i = 0; i < NUMTOKENS; i++) { + if (tokens[i].proc == tProc) { + tokens[i].proc = NULL; + } + } + + // Kill the process + ProcessKill(tProc); +} + +/** + * Gain control of the CONTROL token if it is free. + */ +void GetControlToken() { + const int which = TOKEN_CONTROL; + + if (tokens[which].proc == NULL) { + tokens[which].proc = CurrentProcess(); + } +} + +/** + * Release control of the CONTROL token. + */ +void FreeControlToken() { + // Allow anyone to free TOKEN_CONTROL + tokens[TOKEN_CONTROL].proc = NULL; +} + + +/** + * Gain control of a token. If the requested token is out of range, or + * is already held by the calling process, then the calling process + * will be killed off. + * + * Otherwise, the calling process will gain the token. If the token was + * held by another process, then the previous holder is killed off. + */ +void GetToken(int which) { + assert(TOKEN_LEAD <= which && which < NUMTOKENS); + + if (tokens[which].proc != NULL) { + assert(tokens[which].proc != CurrentProcess()); + TerminateProcess(tokens[which].proc); + } + + tokens[which].proc = CurrentProcess(); +} + +/** + * Release control of a token. If the requested token is not owned by + * the calling process, then the calling process will be killed off. + */ +void FreeToken(int which) { + assert(TOKEN_LEAD <= which && which < NUMTOKENS); + + assert(tokens[which].proc == CurrentProcess()); // we'd have been killed if some other proc had taken this token + + tokens[which].proc = NULL; +} + +/** + * If it's a valid token and it's free, returns true. + */ +bool TestToken(int which) { + if (which < 0 || which >= NUMTOKENS) + return false; + + return (tokens[which].proc == NULL); +} + +/** + * Call at the start of each scene. + */ +void FreeAllTokens(void) { + for (int i = 0; i < NUMTOKENS; i++) { + tokens[i].proc = NULL; + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/token.h b/engines/tinsel/token.h new file mode 100644 index 0000000000..4ab4775bfb --- /dev/null +++ b/engines/tinsel/token.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef TINSEL_TOKEN_H +#define TINSEL_TOKEN_H + +#include "tinsel/dw.h" + +namespace Tinsel { + +// Fixed tokens + +enum { + TOKEN_CONTROL = 0, + TOKEN_LEAD, // = TOKEN_CONTROL + 1 + TOKEN_LEFT_BUT = TOKEN_LEAD + MAX_MOVERS, + + NUMTOKENS // = TOKEN_LEFT_BUT + 1 +}; + +// Token functions + +void GetControlToken(); +void FreeControlToken(); + +void GetToken(int which); +void FreeToken(int which); + +void FreeAllTokens(void); +bool TestToken(int which); + + +} // end of namespace Tinsel + +#endif // TINSEL_TOKEN_H |