code:
from conans import ConanFile, CMake, tools
from conans.errors import ConanException, ConanInvalidConfiguration
import os
import shutil
import textwrap

class Re3Conan(ConanFile):
    name = "re3"
    version = "master"
    license = "???"  # FIXME: https://github.com/GTAmodding/re3/issues/794
    settings = "os", "arch", "compiler", "build_type"
    generators = "cmake", "cmake_find_package"
    options = {
        "audio": ["openal", "miles"],
        "with_libsndfile": [True, False],
        "with_opus": [True, False],
    }
    default_options = {
        "audio": "openal",
        "with_libsndfile": False,
        "with_opus": False,
        # "libsndfile:with_external_libs": False,
        # "mpg123:flexible_resampling": False,
        # "mpg123:network": False,
        # "mpg123:icy": False,
        # "mpg123:id3v2": False,
        # "mpg123:ieeefloat": False,
        # "mpg123:layer1": False,
        # "mpg123:layer2": False,
        # "mpg123:layer3": False,
        # "mpg123:moreinfo": False,
        # "sdl2:vulkan": False,
        # "sdl2:opengl": True,
        # "sdl2:sdl2main": True,
    }
    no_copy_source = True

    @property
    def _os_is_playstation2(self):
        try:
            return self.settings.os == "Playstation2"
        except ConanException:
            return False

    def configure(self):
        if self.options.audio != "openal":
            self.options.with_libsndfile = False

    def requirements(self):
        self.requires("librw/{}".format(self.version))
        self.requires("mpg123/1.26.4")
        if self.options.audio == "openal":
            self.requires("openal/1.21.0")
        elif self.options.audio == "miles":
            self.requires("miles-sdk/{}".format(self.version))
        if self.options.with_libsndfile:
            self.requires("libsndfile/1.0.30")
        if self.options.with_opus:
            self.requires("opusfile/0.12")

    def export_sources(self):
        for d in ("cmake", "gamefiles", "src"):
            shutil.copytree(src=d, dst=os.path.join(self.export_sources_folder, d))
        self.copy("CMakeLists.txt")

    def validate(self):
        if self.options["librw"].platform == "gl3" and self.options["librw"].gl3_gfxlib != "glfw":
            raise ConanInvalidConfiguration("Only `glfw` is supported as gl3_gfxlib.")
        #if not self.options.with_opus:
        #    if not self.options["libsndfile"].with_external_libs:
        #        raise ConanInvalidConfiguration("re3 with opus support requires a libsndfile built with external libs (=ogg/flac/opus/vorbis)")

    @property
    def _re3_audio(self):
        return {
            "miles": "MSS",
            "openal": "OAL",
        }[str(self.options.audio)]

    def build(self):
        if self.source_folder == self.build_folder:
            raise Exception("cannot build with source_folder == build_folder")
        try:
            os.unlink(os.path.join(self.install_folder, "Findlibrw.cmake"))
            tools.save("FindOpenAL.cmake",
                       textwrap.dedent(
                           """
                           set(OPENAL_FOUND ON)
                           set(OPENAL_INCLUDE_DIR ${OpenAL_INCLUDE_DIRS})
                           set(OPENAL_LIBRARY ${OpenAL_LIBRARIES})
                           set(OPENAL_DEFINITIONS ${OpenAL_DEFINITIONS})
                           """), append=True)
            if self.options["librw"].platform == "gl3" and self.options["librw"].gl3_gfxlib == "glfw":
                tools.save("Findglfw3.cmake",
                           textwrap.dedent(
                               """
                               if(NOT TARGET glfw)
                                 message(STATUS "Creating glfw TARGET")
                                 add_library(glfw INTERFACE IMPORTED)
                                 set_target_properties(glfw PROPERTIES
                                    INTERFACE_LINK_LIBRARIES CONAN_PKG::glfw)
                               endif()
                               """), append=True)
            tools.save("CMakeLists.txt",
                       textwrap.dedent(
                           """
                           cmake_minimum_required(VERSION 3.0)
                           project(cmake_wrapper)

                           include("{}/conanbuildinfo.cmake")
                           conan_basic_setup(TARGETS NO_OUTPUT_DIRS)

                           add_subdirectory("{}" re3)
                           """).format(self.install_folder.replace("\\", "/"),
                                       self.source_folder.replace("\\", "/")))
        except FileNotFoundError:
            pass
        cmake = CMake(self)
        cmake.definitions["RE3_AUDIO"] = self._re3_audio
        cmake.definitions["RE3_WITH_OPUS"] = self.options.with_opus
        cmake.definitions["RE3_INSTALL"] = True
        cmake.definitions["RE3_VENDORED_LIBRW"] = False
        env = {}
        if self._os_is_playstation2:
            cmake.definitions["CMAKE_TOOLCHAIN_FILE"] = self.deps_user_info["ps2dev-cmaketoolchain"].cmake_toolchain_file
            env["PS2SDK"] = self.deps_cpp_info["ps2dev-ps2sdk"].rootpath

        with tools.environment_append(env):
            cmake.configure(source_folder=self.build_folder)
        cmake.build()

    def package(self):
        cmake = CMake(self)
        cmake.install()

license: mit


code:
; Author: Alexander Roger
; Date:	21/12/2000
;
; Author: Andrzej Madajczyk
; Date: 26/02/2001
;		14/03/2001			- Alpha (opacity) support added;
;		10/05/2001			- Drag/Friction Decceleration changed to constants;
;		28/08/2001			- Initial Color Variation added;
;
;
;
;
; Note! Last line of the file MUST BE ";the end\n", otherwise you'll get parsing error(s) of the file;
;
;
;
;Particle Systems Configuration Data:: Format
;
;
;A:	Particle Type Name (max 20 chars)
;
;B/C/D:	Render Colouring (r,g,b) (0-255)
;
;CV: Initial Color Variation (for r,g,b only, in %) (0-100);
;	(i.e. Color=(100,100,100) and CV=20, then v=random(-20,20), real_color=(100+v, 100+v, 100+v));
;
;
;
;B2/C2/D2: Fade Destination Color (r,g,b) (0-255)
;
;FT: Color Fade Time for (B,C,D)->(B2,C2,D2), (0 for none);
;
;
;
;
;E:	Default Initial Radius (float)
;F:	Expansion Rate	(float)
;
;
; Color "Fade-to-Black" options:
;G:	Initial Intensity (0-255)
;H:	Fade Time (time between fade steps in frames)
;I:	Fade Amount (-255 to 255)  can get brighter or dimmer
;
; "Fade Alpha" options:
;GA: Initial Intensity (0-255)
;HA: Fade Time (time between fade steps in frames)
;IA: Fade Amount
;
; "Z Rotation" options:
;GZA: Initial Angle (0-1023)
;HZA: Change Time (time between steps in frames)
;IZA: Angle Change Amount
;
;GZR: Initial Z Radius
;HZR: Change Time (time between steps in frames)
;IZR: Z Radius Change Amount
;
;
;J:	Animation Speed (0=no animation)(time between steps msec)
;K:	Start Animation Frame ( 0 -> )
;L:	Final Animation Frame ( H -> )
;
;
;M:	Rotation Speed (0=None,i-deg/frame)
;N:	Gravitational Acceleration (0=none, float)
;O:	Drag/Friction Decceleration (int: 0=none, 50=0.50, 80=0.80, 90=0.90, 95=0.95, 96=0.96, 99=0.99)
;
;P:	Default Life-Span of Particle (msec)
;
;Q:	Position Random Error	[position += (+/-)rand(a)]
;R:	Velocity Random Error	[velocity += (+/-)rand(b)]
;S:	Expansion Rate Error	[exp_rate += (+)rand(c)]
;T:	Rotation Rate Error		[rot_speed = (+/-)rand(d)]
;U:	Life-Span Error Shape	[shape distribution, e=0->all at default, e->Inf then shape->0] (max=255!!)
;V:	Trail Length Multiplier [length *= (float) multiplier] (only used if trail flag active)
;
;CR:Particle Create Range (in meters: 0=no check); if particles are created enough far away from camera, they are deleted (not added to particle system);
;
;
;Z:	Flags! Guide:		1=ZCHECK_FIRST,	2=ZCHECK_STEP,	4=DRAW_OPAQUE,	8=SCREEN_TRAIL,
;						16=SPEED_TRAIL,	32=RAND_VERT_V,	64=CYCLE_ANIM,	128=DRAW_DARK,	256=VERT_TRAIL
;						1024=DRAWTOP2D, 2048=CLIPOUT2D
;						4096=ZCHECK_BUMP,	8192=ZCHECK_BUMP_FIRST
;
;
;
;default:
;GUNFLASH			255	255	255		0	0.1		0.0		255	0	128		0	0	0		0	0.0		1.0		250		0.0		0.0		0.0		0	0	1.0	0
;
;good idea for fire-smudge?
;GUNFLASH			255	255	255		0	1.0		0.0		255	0	32		100	0	3		0	0.0		1.0		400		0.0		0.0		0.0		0	0	1.0	0
;
;current:
;GUNFLASH			255	255	255		0	1.0		0.0		255	0	32		100	0	3		0	0.0		1.0		400		0.0		0.0		0.0		0	0	1.0	0
;
;
;SPARK_SMALL		255	255	128		0	0.005	0.0		255	0	0		0	0	0		0	0.0		1.0		500		0.0		0.05	0.0		0	0	0.5	40
;
;
;
;
;
;
;
;	A				B	C	D	CV		B2	C2	D2	FT		E		F		G	H	I		GA	HA	IA		GZA	HZA	IZA		GZR	HZR	IZR		J	K	L		M	N		O		P		Q		R		S		T	U	V	CR		Z
;
SPARK				255	128	64	0		0	0	0	0		0.005	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.001	1		300		0.0		0.07	0.0		0	0	1.0	20.0	48
SPARK_SMALL			255	255	128	0		0	0	0	0		0.005	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.001	1		500		0.0		0.05	0.0		0	0	0.6	20.0	40
;
WHEEL_DIRT			8	24	8	0		0	0	0	0		0.05	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.002	1		1000	0.15	0.015	0.0		0	0	1.0	30.0	4
;
;
;WHEEL_WATER		24	24	24	0		0	0	0	0		0.05	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.002	1		1000	0.15	0.015	0.0		0	0	1.0	20.0	0
WHEEL_WATER			24	24	32	0		0	0	0	0		0.05	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.004	1		1000	0.15	0.015	0.0		0	0	1.0	20.0	1
;
;
BLOOD				128	128	128	0		0	0	0	0		0.02	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.03	1		2000	0.3		0.05	0.0		0	0	1.0	50.0	5
BLOOD_SMALL			255	32	32	0		0	0	0	0		0.007	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.005 	1		2000	0.05	0.05 	0.0		0	0	1.0	50.0	53
;BLOOD_SPLAT		128	128	128	0		0	0	0	0		0.1 	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.0 	1		200		0.3		0.0		0.0		0	0	1.0	400.0	36
BLOOD_SPURT			255	32	32	0		0	0	0	0		0.008 	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.005 	1		2000	0.0		0.01	0.0		0	0	2.0	50.0	52
DEBRIS				64	64	64	0		0	0	0	0		0.5		0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.01	95		1000	0.2		0.0		0.0		0	0	1.0	50.0	4
DEBRIS2				64	64	64	0		0	0	0	0		0.04	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		5	0.01	99		1000	0.03	0.04	0.0		0	0	1.0	50.0	38
WATER				64	64	128	0		0	0	0	0		0.01	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.0		1		2000	0.0		0.0		0.0		0	0	1.0	100.0	0
;
;
;FLAME				255	74	30	0		0	0	0	0		0.2		0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		32	0	4		0	0.0		1		100		0.05	0.0		0.0		0	0	1.0	400.0	0
;FLAME				255	74	30	0		0	255	0	400		0.8		-0.02	255	0	10		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	-0.005	1		2000	0.02	0.01	0.01	0	0	1.0	200.0	0
FLAME				255	74	30	0		0	0	0	0		0.8		-0.02	255	0	10		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	-0.005	1		2000	0.02	0.01	0.01	0	0	1.0	200.0	0
;
;
;
;FIREBALL			255	74	30	0		0	0	0	0		0.1		0.04	255	1	8		255	0	0		0	0	0		0.0	0	0.0		32	0	7		0	0.0		96		1000	0.1		0.0		0.0		0	0	1.0	400.0	0
;
;FIREBALL			255	74	30	0		0	0	0	0		0.1		0.05	255	0	6		255	0	0		0	0	0		0.0	0	0.0		1	0	7		0	-0.002	96		2000	0.1		0.02	0.02	3	0	1.0	200.0	0
FIREBALL			255	74	30	0		0	0	0	0		0.1		0.02	255	0	6		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	-0.003	96		2000	0.1		0.03	0.014	2.5	0	1.0	200.0	0
;
;
;
GUNFLASH			170	170	170	0		0	0	0	0		0.1		0.0		255	1	50		255	0	0		0	0	0		0.0	0	0.0		51	0	3		0	0.0		1		250		0.0		0.0		0.0		0	0	1.0	35.0	0
GUNFLASH_NOANIM		128	128	128	0		0	0	0	0		0.1		0.0		255	1	128		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.0		1		25		0.0		0.0		0.0		0	0	1.0	35.0	0
;
GUNSMOKE			64	64	64	0		0	0	0	0		0.15	0.0		255 0	0		255	0	0		0	0	0		0.0	0	0.0		2	0	7		0	-0.002	95		1000	0.0		0.0		0.0		0	0	1.0	60.0	0
GUNSMOKE2			255	255	255	0		0	0	0	0		0.05	0.02	255	0	0		255	0	8		0	0	0		0.0	0	0.0		0	0	3		4	-0.001	80		1400	0.05	0.05	0.01	3	0	1.0	60.0	4
;
;
SMOKE				32	32	32	0		0	0	0	0		0.15	0.015	255	5	25		255	0	0		0	0	0		0.0	0	0.0		32	0	4		0	-0.01	95		1000	0.05	0.05	0.01	3	0	1.0	150.0	0
;SMOKE_SLOWMOTION	32	32	32	0		0	0	0	0		0.15	0.015	255	5	15		255	0	0		0	0	0		0.0	0	0.0		32	0	4		0	-0.003	95		1000	0.05	0.05	0.01	3	0	1.0	400.0	0
SMOKE_SLOWMOTION	32	32	32	0		0	0	0	0		0.15	0.015	128	5	11		255	0	0		0	0	0		0.0	0	0.0		32	0	4		0	-0.003	95		3000	0.05	0.05	0.01	3	0	1.0	150.0	0
;
;
;
;GARAGEPAINT_SPRAY	32	32	32	0		0	0	0	0		0.15	0.015	255	0	5		255	0	0		0	0	0		0.0	0	0.0		0	0	4		0	-0.001	95		2000	0.05	0.05	0.01	3	0	1.0	400.0	0
GARAGEPAINT_SPRAY	32	32	32	0		0	0	0	0		0.15	0.015	255	0	5		255	0	0		0	0	0		0.0	0	0.0		0	0	4		0	-0.0005	95		4000	0.05	0.05	0.01	3	0	1.0	100.0	0
SHARD				255	255	255	0		0	0	0	0		0.03	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.0		96		300		0.0		0.0		0.0		0	0	1.0	100.0	0
SPLASH				64	64	128	0		0	0	0	0		0.1		0.007	255	1	10		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.0		1		1000	0.0		0.0		0.0		0	0	1.0	100.0	0
;BLOOD_SPLASH		24	64	0	0		0	0	0	0		0.1		0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.0		96		300		0.0		0.0		0.0		0	0	1.0	100.0	0
;
;
;CARFLAME			255	74	30	0		0	0	0	0		0.5		0.04	255	2	20		255	0	0		0	0	0		0.0	0	0.0		32	0	4		0	0.0		1		1000	0.4		0.0		0.0		0	0	1.0	400.0	64
;CARFLAME			255	74	30	0		0	0	0	0		0.8		-0.02	255	0	10		255	0	0		0	0	0		0.0	0	0.0		32	0	4		0	-0.001	1		2000	0.4		0.01	0.01	0	0	1.0	400.0	64
;CARFLAME			255	74	30	0		0	0	0	0		0.8		-0.02	255	0	10		255	0	0		0	0	0		0.0	0	0.0		32	0	4		0	-0.001	1		2000	0.4		0.01	0.01	0	0	1.0	400.0	64
;
CARFLAME			255	74	30	0		0	0	0	0		0.8		-0.02	255	0	10		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	-0.005	1		2000	0.02	0.01	0.01	0	0	1.0	100.0	0
;
;
STEAM				64	64	64	0		0	0	0	0		0.5		0.05	255	1	16		255	0	0		0	0	0		0.0	0	0.0		32	0	4		0	-0.005	95		2000	0.01	0.03	0.0		0	0	1.0	85.0	0
;
;default:
;STEAM2				255	255	255	0		0	0	0	0		0.5		0.05	255	0	0		128	2	8		0	0	0		0.0	0	0.0		32	0	4		0	-0.005	95		2000	0.01	0.03	0.0		0	0	1.0	400.0	4
STEAM2				255	255	255	0		0	0	0	0		0.5		0.015	255	0	0		192	0	1		0	0	10		0.5	1	0.02	32	0	4		0	-0.002	95		8000	0.01	0.03	0.0		0	0	1.0	85.0	4
;
;
;STEAM_NY			255	255	255	0		0	0	0	0		0.5		0.05	255	0	0		128	2	8		0	0	0		0.0	0	0.0		32	0	4		0	-0.005	95		2000	0.01	0.03	0.0		0	0	1.0	400.0	4
STEAM_NY			255	255	255	0		0	0	0	0		0.5		0.05	255	0	0		96	2	8		0	0	0		0.0	0	0.0		32	0	4		0	-0.005	95		1400	0.01	0.03	0.0		0	0	1.0	85.0	4
STEAM_NY_SLOWMOTION	255	255	255	0		0	0	0	0		0.5		0.05	255	0	0		96	2	8		0	0	0		0.0	0	0.0		32	0	4		0	-0.0015	95		1400	0.01	0.03	0.0		0	0	1.0	85.0	4
;
;
;ENGINE_STEAM		210	210	210	0		0	0	0	0		0.5		0.05	255	0	0		192	2	16		0	0	0		0.0	0	0.0		32	0	4		0	-0.005	95		2000	0.01	0.03	0.0		0	0	1.0	250.0	4
ENGINE_STEAM		210	210	210	0		0	0	0	0		0.5		0.05	255	0	0		192	0	10		0	0	0		0.0	0	0.0		32	0	4		1	-0.005	95		4000	0.03	0.03	0.02		0	0	1.0	85.0	4
;
;
;RAINDROP			32	32	32	0		0	0	0	0		0.6		0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.025	1		1000	0.0		0.0		0.0		0	0	1.0	15.0	1
RAINDROP			64	64	64	0		0	0	0	0		0.4		0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	3		0	0.05	1		1000	0.0		0.0		0.0		0	0	1.0	15.0	1
RAINDROP_SMALL		16	16	16	0		0	0	0	0		0.3		0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.05	1		1000	0.0		0.0		0.0		0	0	1.0	15.0	1
RAIN_SPLASH			32	32	32	0		0	0	0	0		0.08	0.0		255	0	5		255	0	0		0	0	0		0.0	0	0.0		1	0	4		0	0.0		1		500		0.0		0.0		0.0		0	0	1.0	15.0	0
RAIN_SPLASH_BIGGROW	128	128	128	0		0	0	0	0		0.5		0.06	255	0	2		255	0	0		0	0	0		0.0	0	0.0		2	1	4		0	0.0		1		5500	0.0		0.0		0.0		0	0	1.0	15.0	0
RAIN_SPLASHUP		48	48	48	0		0	0	0	0		0.1		0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	1		0	0.0		1		50		0.0		0.0		0.0		0	0	1.0	15.0	0
;
WATERSPRAY			64	64	64	0		0	0	0	0		0.2		0.0		255	0	25		255	0	0		0	0	0		0.0	0	0.0		3	0	2		0	0.002	1		800		0.05	0.0		0.01	0	0	1.0	20.0	0
;
;
;
;EXPLOSION_MEDIUM	80	80	80	0		0	0	0	0		0.6		0.04	255	5	8		255	0	0		0	0	0		0.0	0	0.0		8	0	11		0	0.0		96		15000	0.2		0.0		0.0		3	0	1.0	400.0	0
;EXPLOSION_LARGE	80	80	80	0		0	0	0	0		1.1		0.04	255	5	8		255	0	0		0	0	0		0.0	0	0.0		8	0	11		0	0.0		96		15000	0.8		0.0		0.0		3	0	1.0	400.0	0
;EXPLOSION_MEDIUM	80	80	80	0		0	0	0	0		0.6		0.04	255	1	4		255	0	0		0	0	0		0.0	0	0.0		1	0	11		0	0.0		96		7000	0.2		0.0		0.0		0	0	1.0	400.0	0
;EXPLOSION_LARGE	80	80	80	0		0	0	0	0		1.1		0.04	255	1	4		255	0	0		0	0	0		0.0	0	0.0		1	0	11		0	0.0		96		7000	0.8		0.0		0.0		0	0	1.0	400.0	0
;
;EXPLOSION_MEDIUM	80	80	80	0		0	0	0	0		0.6		0.04	255	0	3		255	0	0		0	0	0		0.0	0	0.0		1	0	11		0	-0.001	96		6000	0.2		0.0		0.0		0	0	1.0	400.0	0
;EXPLOSION_LARGE	80	80	80	0		0	0	0	0		1.1		0.04	255	0	3		255	0	0		0	0	0		0.0	0	0.0		1	0	11		0	-0.001	96		6000	0.8		0.0		0.0		0	0	1.0	400.0	0
EXPLOSION_MEDIUM	80	80	80	0		0	0	0	0		0.6		0.04	255	0	3		255	0	0		0	0	0		0.0	0	0.0		2	0	5		0	-0.001	96		6000	0.2		0.0		0.0		0	0	1.0	200.0	0
EXPLOSION_LARGE		80	80	80	0		0	0	0	0		1.1		0.04	255	0	3		255	0	0		0	0	0		0.0	0	0.0		2	0	5		0	-0.001	96		6000	0.8		0.0		0.0		0	0	1.0	200.0	0
EXPLOSION_MFAST		80	80	80	0		0	0	0	0		0.6		0.04	255	0	6		255	0	0		0	0	0		0.0	0	0.0		2	0	5		0	-0.001	96		3500	0.2		0.0		0.0		0	0	1.0	200.0	0
EXPLOSION_LFAST		80	80	80	0		0	0	0	0		1.1		0.04	255	0	6		255	0	0		0	0	0		0.0	0	0.0		2	0	5		0	-0.001	96		3500	0.8		0.0		0.0		0	0	1.0	200.0	0
;
;
;
;
;BOAT_SPLASH		32	64	32	0		0	0	0	0		0.2		0.2		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.01	1		2000	0.0		0.0		0.0		0	0	1.0	200.0	0
;BOAT_THRUSTJET		24	32	24	0		0	0	0	0		0.5		0.1		255	0	0		255	0	0		0	0	0		0.0	0	0.0		250	0	4		0	0.01	50		1000	0.0		0.0		0.0		0	4	1.0	200.0	8
;BOAT_SPLASH		16	32	32	0		0	0	0	0		0.2		0.2		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.01	1		2000	0.0		0.0		0.0		0	0	1.0	200.0	0
;BOAT_THRUSTJET		8	24	24	0		0	0	0	0		0.5		0.1		255	0	0		255	0	0		0	0	0		0.0	0	0.0		250	0	4		0	0.01	50		1000	0.0		0.0		0.0		0	4	1.0	200.0	8
;CAR_SPLASH			64	64	64	0		0	0	0	0		2.0		0.25	255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.02	1		2000	0.0		0.0		0.0		0	0	1.0	250.0	0
;CAR_SPLASH			64	64	64	0		0	0	0	0		2.0		0.25	255	0	0		200	0	8		0	0	0		0.0	0	0.0		0	0	0		0	0.04	1		2000	0.0		0.0		0.0		0	0	1.0	150.0	4
;CAR_SPLASH			64	64	64	0		0	0	0	0		2.0		0.35	255	0	0		200	0	8		0	0	0		0.0	0	0.0		0	0	0		0	0.05	1		2000	0.0		0.0		0.0		0	0	1.0	150.0	4
;CAR_SPLASH			64	64	64	0		0	0	0	0		1.0		0.25	255	0	0		180	0	5		0	0	0		0.0	0	0.0		2	1	3		0	0.05	1		1000	0.0		0.0		0.0		0	0	1.0	150.0	12
;
;
;CAR_SPLASH			64	64	64	0		0	0	0	0		1.0		0.15	255	0	0		180	0	2		0	0	0		0.0	0	0.0		2	0	3		0	0.02	1		2000	0.0		0.0		0.0		0	0	1.0	150.0	12
;CAR_SPLASH			48	48	64	0		0	0	0	0		1.0		0.15	96	0	0		255	0	0		0	0	0		0.0	0	0.0		6	0	2		0	0.01	1		2000	0.5		0.04	0.0		0	0	2.0	150.0	288
;CAR_SPLASH			48	48	64	0		0	0	0	0		1.0		0.05	96	0	0		255	0	0		0	0	0		0.0	0	0.0		0	1	2		0	0.01	1		2000	0.5		0.04	0.0		0	0	2.0	150.0	288
;	A				B	C	D	CV		B2	C2	D2	FT		E		F		G	H	I		GA	HA	IA		GZA	HZA	IZA		GZR	HZR	IZR		J	K	L		M	N		O		P		Q		R		S		T	U	V	CR		Z
CAR_SPLASH			48	48	60	0		0	0	0	0		1.0		0.00	128	1	4		128	0	0		0	0	0		0.0	0	0.0		0	0	2		0	0.01	1		2000	0.5		0.04	0.0		0	0	1.4	150.0	272
;
;
;
;BOAT_SPLASH		70	70	70	0		0	0	0	0		0.2		0.2		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.01	1		1000	0.0		0.0		0.0		0	0	1.0	150.0	0
BOAT_SPLASH			64	64	64	0		0	0	0	0		0.2		0.2		255	0	2		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.01	1		1000	0.0		0.0		0.0		0	0	1.0	150.0	0
;
;
;BOAT_THRUSTJET		90	90	90	0		0	0	0	0		1.8		0.1		255	0	0		120	0	1		0	0	0		0.0	0	0.0		0	1	4		0	0.01	50		1600	0.8		0.4		0.02	0	4	1.0	150.0	4
BOAT_THRUSTJET		90	90	90	0		0	0	0	0		1.4		0.06	255	0	0		 96	0	1		0	0	0		0.0	0	0.0		0	1	4		0	0.01	50		1600	0.8		0.4		0.02	0	4	1.0	150.0	4
;
;
;BOAT_WAKE			255	255	255	0		0	0	0	0		2.0		0.2		255	0	0		128	0	1		0	0	0		0.0	0	0.0		0	0	0		0	0.03	50		1600	0.8		0.4		0.02	0	4	1.0	150.0	4
BOAT_WAKE			255	255	255	0		0	0	0	0		1.5		0.45	255	0	0		192	0	2		0	0	0		0.0	0	0.0		0	0	0		0	0.0		50		1600	0.8		0.4		0.02	0	4	1.0	150.0	4
;
;
;
;
;
;	A				B	C	D	CV		B2	C2	D2	FT		E		F		G	H	I		GA	HA	IA		GZA	HZA	IZA		GZR	HZR	IZR		J	K	L		M	N		O		P		Q		R		S		T	U	V	CR		Z
WATER_HYDRANT		64	64	64	0		0	0	0	0		0.8		0.01	255	1	16		255	1	16		0	0	0		0.0	0	0.0		0	0	2		0	0.007	99		500		0.02	0.08	0.0		0	4	1.0	85.0	16
WATER_CANNON		64	64	128	0		0	0	0	0		0.03	0.03	255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.0		1		1000	0.0		0.0		0.0		0	0	1.0	85.0	0
EXTINGUISH_STEAM	32	32	32	0		0	0	0	0		0.1		0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.0		1		1000	0.0		0.0		0.0		0	0	1.0	85.0	0
;
;
;
;PED_SPLASH			32	32	64	0		0	0	0	0		0.1		0.05	255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.0		1		1000	0.0		0.0		0.0		0	0	1.0	85.0	0
PED_SPLASH			48	48	60	0		0	0	0	0		0.1		0.06	96	0	0		255	0	0		0	0	0		0.0	0	0.0		0	1	2		0	0.01	1		2000	0.5		0.04	0.0		0	0	1.4	50.0	256
;
;
PEDFOOT_DUST		170	166	150	0		0	0	0	0		0.01	0.015	255	0	0		63	0	4		0	0	0		0.0	0	0.0		0	0	0		0	-0.0005	1		1000	0.0		0.0		0.0		0	0	1.0	6.0		4
;
HELI_DUST			17	15	9	0		0	0	0	0		0.2		0.1		255	1	8		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	-0.001	1		1000	0.2		0.05	0.0		0	0	1.0	85.0	0
HELI_ATTACK			255	255	128	0		0	0	0	0		0.01	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.0		1		500		0.0		0.0		0.0		0	0	0.5	85.0	10
;
;
;ENGINE_SMOKE		16	16	16	0		0	0	0	0		0.5		0.04	255	0	0		63	0	0		0	0	0		0.0	0	0.0		0	0	0		0	-0.005	95		2000	0.01	0.03	0.0		0	0	1.0	150.0	4
;ENGINE_SMOKE2		8	8	8	0		0	0	0	0		1.0		0.2		128	2	4		63	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.001	1		1000	0.0		0.0		0.0		0	3	1.0	150.0	4
ENGINE_SMOKE		16	16	16	0		0	0	0	0		0.5		0.04	255	0	0		52	0	2		10	0	80		0.0	0	0.0		0	0	5		2	-0.009	95		2000	0.11	0.03	0.01		1	0	1.0	85.0	4
ENGINE_SMOKE2		9	9	9	80		0	0	0	0		1.0		0.06	128	0	1		140	0	5		10	0	80		0.0	0	0.0		0	0	0		2	0.002	1		1300	0.0		0.01	0.0		3	3	1.0	85.0	4
;
;
CARFLAME_SMOKE		32	32	32	0		0	0	0	0		0.05	0.01	255	0	0		64	0	2		0	0	0		0.0	0	0.0		0	0	0		0	-0.008	95		2000	0.01	0.03	0.01	0	0	1.0	85.0	4
FIREBALL_SMOKE		32	32	32	0		0	0	0	0		0.05	0.03	255	0	0		128	0	2		0	0	0		0.0	0	0.0		0	0	0		0	-0.004	95		2000	0.01	0.03	0.01	0	0	1.0	85.0	4
;
PAINT_SMOKE			255	0	0	0		0	0	0	0		0.1		0.01	255	1	8		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.0		95		3000	0.0		0.005	0.0		0	0	1.0	85.0	0
TREE_LEAVES			64	64	64	0		0	0	0	0		0.2		0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.0		1		1000	0.0		0.0		0.0		0	0	1.0	85.0	0
;
;
;CARCOLLISION_DUST	224	224	224	0		0	0	0	0		0.15	0.04	255	0	0		127	1	8		0	0	0		0.0	0	0.0		0	0	0		0	-0.002	90		2000	0.02	0.02	0.0		0	0	1.0	80.0	4
CARCOLLISION_DUST	76	76	76	0		0	0	0	0		0.10	0.02	255	0	0		160	0	4		0	0	0		0.0	0	0.0		0	0	0		0	-0.0015	90		2000	0.02	0.02	0.0		0	0	1.0	30.0	4
;
;
CAR_DEBRIS			32	32	32	0		0	0	0	0		0.5		0.0		224	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	4		0	0.010	90		1000	0.02	0.02	0.0		0	0	1.0	50.0	4
HELI_DEBRIS			32	32	32	0		0	0	0	0		1.5		0.0		224	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	4		0	0.065	90		1500	0.02	0.02	0.0		0	0	1.0	150.0	4
;
;
;
;EXHAUST_FUMES		80	80	80	0		0	0	0	0		0.03	0.03	255	0	0		122	0	4		0	0	0		0.0	0	0.0		2	0	4		0	-0.001	95		1500	0.01	0.03	0.0		0	0	1.0	50.0	4
EXHAUST_FUMES		98	98	108	0		0	0	0	0		0.03	0.06	255	0	0		152	0	12		0	0	0		0.0	0	0.0		2	0	4		0	-0.002	96		1000	0.01	0.03	0.0		0	0	1.0	25.0	4
;
;
;RUBBER				40	40	40	0		0	0	0	0		0.4		0.005	255	21	20		255	0	0		0	0	0		0.0	0	0.0		3	0	4		0	-0.0005	1		1000	0.02	0.0		0.0		0	0	1.0	400.0	4
RUBBER_SMOKE		255	255	255	0		0	0	0	0		0.4		0.005	255	0	0		127	1	8		0	0	0		0.0	0	0.0		3	0	4		0	-0.0005	1		1000	0.02	0.0		0.0		0	0	1.0	50.0	4
;BURNINGRUBBER_SMOKE128 128	128	0		0	0	0	0		0.35	0.06	255	0	0		192	1	6		0	0	0		0.0	0	0.0		0	0	0		0	-0.002	90		4000	0.02	0.02	0.0		0	0	1.0	400.0	4
BURNINGRUBBER_SMOKE	128 128	128	0		0	0	0	0		0.35	0.06	255	0	0		128	0	4		0	0	0		0.0	0	0.0		0	0	0		0	-0.002	90		2000	0.02	0.02	0.0		0	0	1.0	50.0	4
;
;
BULLETHIT_SMOKE		192	192	192	0		0	0	0	0		0.15	0.03	70  0	2		255	1	10		0	0	0		0.0	0	0.0		0	0	0		0	-0.001	90		2000	0.04	0.02	0.0		0	0	1.0	150.0	0
;
;
GUNSHELL_FIRST		108	108	108	0		0	0	0	0		0.015	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	4		0	0.010	90		1000	0.02	0.02	0.0		0	0	1.0	0.0		12292
GUNSHELL			108	108	108	0		0	0	0	0		0.015	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	4		0	0.010	90		1000	0.02	0.02	0.0		0	0	1.0	12.0	4100
GUNSHELL_BUMP1		108	108	108	0		0	0	0	0		0.015	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	4		0	0.010	90		1000	0.02	0.02	0.0		0	0	1.0	8.0		4100
GUNSHELL_BUMP2		108	108	108	0		0	0	0	0		0.015	0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	0	4		0	0.010	90		400		0.02	0.02	0.0		0	0	1.0	8.0		4100
;
;
TEST				255	64	64	0		0	0	0	0		0.2		0.025	255	1	20		255	0	0		0	0	0		0.0	0	0.0		0	0	0		0	0.0		1		3000	0.0		0.0		0.0		0	0	1.0	400.0	128
;
;
;Particles with flag DRAWTOP2D should be placed last and VR (Visibility Range) set to 0!
;
;BIRD_FRONT			8	8	8	0		0	0	0	0		0.05	0.0		255	0	0		255	2	1		0	0	0		0.0	0	0.0		1	0	3		0	0.0		1		10000	0.0		0.0		0.0		0	0	1.0	0.0		3140
BIRD_FRONT			8	8	8	0		0	0	0	0		1.05	0.0		255	0	0		255	2	2		0	0	0		0.0	0	0.0		1	0	3		0	0.0		1		8000	0.0		0.0		0.0		0	0	1.0	0.0		68
;
RAINDROP_2D			32	32	32	0		0	0	0	0		0.5		0.0		255	0	0		255	0	0		0	0	0		0.0	0	0.0		0	1	0		0	0.0		1		1000	0.0		0.0		0.0		0	0	1.0	0.0		3072
;
;
;
;
;
;
;
;
;
;
;
;
; below is just backup of above values:
;
;SPARK				255	128	64		0.005	0.0		255	0	0		0	0	0		0	0.0		1.0		300		0.0		0.07	0.0		0	0	1.0	48
;SPARK_SMALL			255	255	128		0.005	0.0		255	0	0		0	0	0		0	0.0		1.0		500		0.0		0.05	0.0		0	0	0.6	40
;BLOOD				128	128	128		0.02	0.0		255	0	0		0	0	0		0	0.03	1.0		2000	0.3		0.05	0.0		0	0	1.0	6
;BLOOD_SMALL			255	32	32		0.007	0.0		255	0	0		0	0	0		0	0.005 	1.0		2000	0.05	0.05 	0.0		0	0	1.0	54
;BLOOD_SPLAT			128	128	128		0.1 	0.0		255	0	0		0	0	0		0	0.0 	1.0		200		0.3		0.0		0.0		0	0	1.0	36
;BLOOD_SPURT			255	32	32		0.008 	0.0		255	0	0		0	0	0		0	0.005 	1.0		2000	0.0		0.01	0.0		0	0	2.0	52
;DEBRIS				64	64	64		0.5		0.0		255	0	0		0	0	0		0	0.01	0.95	1000	0.2		0.0		0.0		0	0	1.0	4
;DEBRIS2				64	64	64		0.04	0.0		255	0	0		0	0	0		5	0.01	0.99	1000	0.03	0.04	0.0		0	0	1.0	38
;WATER				64	64	128		0.01	0.0		255	0	0		0	0	0		0	0.0		1.0		2000	0.0		0.0		0.0		0	0	1.0	0
;FLAME				255	74	30		0.2		0.0		255	0	0		31	0	5		0	0.0		1.0		100		0.05	0.0		0.0		0	0	1.0	0
;FIREBALL			255	74	30		0.1		0.04	255	0	8		31	0	8		0	0.0		0.96	1000	0.1		0.0		0.0		0	0	1.0	0
;GUNFLASH			255	255	255		0.1		0.0		255	0	50		50	0	3		0	0.0		1.0		250		0.0		0.0		0.0		0	0	1.0	0
;GUNFLASHSTATIC		255	255	255		0.1		0.0		255	0	128		0	0	0		0	0.0		1.0		25		0.0		0.0		0.0		0	0	1.0	0
;SMOKE				32	32	32		0.15	0.015	255	4	25		31	0	5		0	-0.01	0.95	1000	0.05	0.05	0.01	3	0	1.0	0
;SHARD				255	255	255		0.03	0.0		255	0	0		0	0	0		0	0.0		0.96	300		0.0		0.0		0.0		0	0	1.0	0
;SPLASH				64	64	128		0.1		0.007	255	0	10		0	0	0		0	0.0		1.0		1000	0.0		0.0		0.0		0	0	1.0	0
;BLOOD_SPLASH		24	64	0		0.1		0.0		255	0	0		0	0	0		0	0.0		0.96	300		0.0		0.0		0.0		0	0	1.0	0
;RUBBER				40	40	40		0.4		0.005	255	1	25		31	0	5		0	0.0		1.0		1000	0.02	0.0		0.0		0	0	1.0	0
;CARFLAME			255	74	30		0.5		0.04	255	1	20		31	0	5		0	0.0		1.0		1000	0.4		0.0		0.0		0	0	1.0	64
;STEAM				64	64	64		0.5		0.05	255	0	16		31	0	5		0	-0.005	0.95	2000	0.01	0.03	0.0		0	0	1.0	0
;RAINDROP			32	32	32		0.6		0.0		255	0	0		0	0	0		0	0.1		1.0		1000	0.0		0.0		0.0		0	0	1.0	1
;RAIN_SPLASH			32	32	32		0.08	0.0		255	0	0		1	0	4		0	0.0		1.0		1000	0.0		0.0		0.0		0	0	1.0	0
;RAINDROP_SMALL		32	32	32		0.3		0.0		255	0	0		0	0	0		0	0.1		1.0		1000	0.0		0.0		0.0		0	0	1.0	1
;EXPLOSION_MEDIUM	80	80	80		0.6		0.04	255	4	8		7	0	11		0	0.0		0.96	30000	0.2		0.0		0.0		3	0	1.0	0
;EXPLOSION_LARGE		80	80	80		1.1		0.04	255	4	8		7	0	11		0	0.0		0.96	30000	0.8		0.0		0.0		3	0	1.0	0
;BOAT_SPLASH			32	64	32		0.2		0.2		255	0	0		0	0	0		0	0.01	1.0		2000	0.0		0.0		0.0		0	0	1.0	0
;BOAT_THRUSTJET		24	32	24		0.5		0.1		255	0	0		250	0	5		0	0.01	0.5		1000	0.0		0.0		0.0		0	4	1.0	8
;WATER_HYDRANT		64	64	128		0.4		0.01	255	1	2		20	0	5		0	0.007	0.99	500		0.02	0.05	0.0		0	4	1.0	256
;WATER_CANNON		64	64	128		0.03	0.03	255	0	0		0	0	0		0	0.0		1.0		1000	0.0		0.0		0.0		0	0	1.0	0
;EXTINGUISH_STEAM	32	32	32		0.1		0.0		255	0	0		0	0	0		0	0.0		1.0		1000	0.0		0.0		0.0		0	0	1.0	0
;PED_SPLASH			32	32	64		0.1		0.05	255	0	0		0	0	0		0	0.0		1.0		1000	0.0		0.0		0.0		0	0	1.0	0
;HELI_DUST			17	15	9		0.2		0.1		255	0	8		0	0	0		0	-0.001	1.0		1000	0.2		0.05	0.0		0	0	1.0	0
;HELI_ATTACK			255	255	128		0.01	0.0		255	0	0		0	0	0		0	0.0		1.0		500		0.0		0.0		0.0		0	0	0.5	10
;ENGINE_SMOKE		16	16	16		0.5		0.04	255	0	0		0	0	0		0	-0.005	0.95	2000	0.01	0.03	0.0		0	0	1.0	4
;ENGINE_SMOKE2		4	4	4		1.0		0.2		255	1	4		0	0	0		0	0.001	1.0		1000	0.0		0.0		0.0		0	3	1.0	4
;PAINT_SMOKE			255	0	0		0.1		0.01	255	0	8		0	0	0		0	0.0		0.95	3000	0.0		0.005	0.0		0	0	1.0	0
;TREE_LEAVES			64	64	64		0.2		0.0		255	0	0		0	0	0		0	0.0		1.0		1000	0.0		0.0		0.0		0	0	1.0	0
;TEST				255	64	64		0.2		0.05	255	0	16		0	0	0		0	0.0		1.0		3000	0.0		0.0		0.0		0	0	1.0	128
;
;
;the end


code:
newoption {
	trigger     = "glfwdir64",
	value       = "PATH",
	description = "Directory of glfw",
	default     = "vendor/glfw-3.3.2.bin.WIN64",
}

newoption {
	trigger     = "glfwdir32",
	value       = "PATH",
	description = "Directory of glfw",
	default     = "vendor/glfw-3.3.2.bin.WIN32",
}

newoption {
	trigger     = "with-asan",
	description = "Build with address sanitizer"
}

newoption {
	trigger     = "with-librw",
	description = "Build and use librw from this solution"
}

newoption {
	trigger     = "with-opus",
	description = "Build with opus"
}

newoption {
	trigger     = "with-lto",
	description = "Build with link time optimization"
}

newoption {
	trigger     = "no-git-hash",
	description = "Don't print git commit hash into binary"
}

newoption {
	trigger     = "no-full-paths",
	description = "Don't print full paths into binary"
}

require("autoconf")

if(_OPTIONS["with-librw"]) then
	Librw = "vendor/librw"
else
	Librw = os.getenv("LIBRW") or "vendor/librw"
end

function getsys(a)
	if a == 'windows' then
		return 'win'
	end
	return a
end

function getarch(a)
	if a == 'x86_64' then
		return 'amd64'
	elseif a == 'ARM' then
		return 'arm'
	elseif a == 'ARM64' then
		return 'arm64'
	end
	return a
end

workspace "re3"
	language "C++"
	configurations { "Debug", "Release" }
	startproject "re3"
	location "build"
	symbols "Full"
	staticruntime "off"

	if _OPTIONS["with-asan"] then
		buildoptions { "-fsanitize=address -g3 -fno-omit-frame-pointer" }
		linkoptions { "-fsanitize=address" }
	end

	filter { "system:windows" }
		configurations { "Vanilla" }
		platforms {
			"win-x86-RW33_d3d8-mss",
			"win-x86-librw_d3d9-mss",
			"win-x86-librw_gl3_glfw-mss",
			"win-x86-RW33_d3d8-oal",
			"win-x86-librw_d3d9-oal",
			"win-x86-librw_gl3_glfw-oal",
			"win-amd64-librw_d3d9-oal",
			"win-amd64-librw_gl3_glfw-oal",
		}

	filter { "system:linux" }
		platforms {
			"linux-x86-librw_gl3_glfw-oal",
			"linux-amd64-librw_gl3_glfw-oal",
			"linux-arm-librw_gl3_glfw-oal",
			"linux-arm64-librw_gl3_glfw-oal",
		}

	filter { "system:bsd" }
		platforms {
			"bsd-x86-librw_gl3_glfw-oal",
			"bsd-amd64-librw_gl3_glfw-oal",
			"bsd-arm-librw_gl3_glfw-oal",
			"bsd-arm64-librw_gl3_glfw-oal"
		}

	filter { "system:macosx" }
		platforms {
			"macosx-arm64-librw_gl3_glfw-oal",
			"macosx-amd64-librw_gl3_glfw-oal",
		}

	filter "configurations:Debug"
		defines { "DEBUG" }

	filter "configurations:not Debug"
		defines { "NDEBUG" }
		optimize "Speed"
		if(_OPTIONS["with-lto"]) then
			flags { "LinkTimeOptimization" }
		end

	filter { "platforms:win*" }
		system "windows"

	filter { "platforms:linux*" }
		system "linux"

	filter { "platforms:bsd*" }
		system "bsd"

	filter { "platforms:macosx*" }
		system "macosx"

	filter { "platforms:*x86*" }
		architecture "x86"

	filter { "platforms:*amd64*" }
		architecture "amd64"

	filter { "platforms:*arm*" }
		architecture "ARM"

	filter { "platforms:macosx-arm64-*", "files:**.cpp"}
		buildoptions { "-target", "arm64-apple-macos11", "-std=gnu++14" }

	filter { "platforms:macosx-arm64-*", "files:**.c"}
		buildoptions { "-target", "arm64-apple-macos11" }

	filter { "platforms:macosx-amd64-*", "files:**.cpp"}
		buildoptions { "-target", "x86_64-apple-macos10.12", "-std=gnu++14" }

	filter { "platforms:macosx-amd64-*", "files:**.c"}
		buildoptions { "-target", "x86_64-apple-macos10.12" }

	filter { "platforms:*librw_d3d9*" }
		defines { "RW_D3D9" }
		if(not _OPTIONS["with-librw"]) then
			libdirs { path.join(Librw, "lib/win-%{getarch(cfg.architecture)}-d3d9/%{cfg.buildcfg}") }
		end

	filter "platforms:*librw_gl3_glfw*"
		defines { "RW_GL3" }
		if(not _OPTIONS["with-librw"]) then
			libdirs { path.join(Librw, "lib/%{getsys(cfg.system)}-%{getarch(cfg.architecture)}-gl3/%{cfg.buildcfg}") }
		end

	filter "platforms:*x86-librw_gl3_glfw*"
		includedirs { path.join(_OPTIONS["glfwdir32"], "include") }

	filter "platforms:*amd64-librw_gl3_glfw*"
		includedirs { path.join(_OPTIONS["glfwdir64"], "include") }

	filter  {}

    function setpaths (gamepath, exepath)
       if (gamepath) then
          postbuildcommands {
             '{COPYFILE} "%{cfg.buildtarget.abspath}" "' .. gamepath .. '%{cfg.buildtarget.name}"'
          }
          debugdir (gamepath)
          if (exepath) then
			 -- Used VS variable $(TargetFileName) because it doesn't accept premake tokens. Does debugcommand even work outside VS??
             debugcommand (gamepath .. "$(TargetFileName)")
             dir, file = exepath:match'(.*/)(.*)'
             debugdir (gamepath .. (dir or ""))
          end
       end
    end

if(_OPTIONS["with-librw"]) then
project "librw"
	kind "StaticLib"
	targetname "rw"
	targetdir(path.join(Librw, "lib/%{cfg.platform}/%{cfg.buildcfg}"))
	files { path.join(Librw, "src/*.*") }
	files { path.join(Librw, "src/*/*.*") }
	files { path.join(Librw, "src/gl/*/*.*") }

	filter { "platforms:*x86*" }
		architecture "x86"

	filter { "platforms:*amd64*" }
		architecture "amd64"

	filter "platforms:win*"
		defines { "_CRT_SECURE_NO_WARNINGS", "_CRT_NONSTDC_NO_DEPRECATE" }
		staticruntime "on"
		buildoptions { "/Zc:sizedDealloc-" }

	filter "platforms:bsd*"
		includedirs { "/usr/local/include" }
		libdirs { "/usr/local/lib" }

	-- Support MacPorts and Homebrew
	filter "platforms:macosx-arm64-*"
		includedirs { "/opt/local/include" }
		includedirs {"/opt/homebrew/include" }
		libdirs { "/opt/local/lib" }
		libdirs { "/opt/homebrew/lib" }

	filter "platforms:macosx-amd64-*"
		includedirs { "/opt/local/include" }
		includedirs {"/usr/local/include" }
		libdirs { "/opt/local/lib" }
		libdirs { "/usr/local/lib" }

	filter "platforms:*gl3_glfw*"
		staticruntime "off"

	filter "platforms:*RW33*"
		flags { "ExcludeFromBuild" }
	filter  {}
end

local function addSrcFiles( prefix )
	return prefix .. "/*cpp", prefix .. "/*.h", prefix .. "/*.c", prefix .. "/*.ico", prefix .. "/*.rc"
end

project "re3"
	kind "WindowedApp"
	targetname "re3"
	targetdir "bin/%{cfg.platform}/%{cfg.buildcfg}"

	if(_OPTIONS["with-librw"]) then
		dependson "librw"
	end

	files { addSrcFiles("src") }
	files { addSrcFiles("src/animation") }
	files { addSrcFiles("src/audio") }
	files { addSrcFiles("src/audio/eax") }
	files { addSrcFiles("src/audio/oal") }
	files { addSrcFiles("src/buildings") }
	files { addSrcFiles("src/collision") }
	files { addSrcFiles("src/control") }
	files { addSrcFiles("src/core") }
	files { addSrcFiles("src/entities") }
	files { addSrcFiles("src/math") }
	files { addSrcFiles("src/modelinfo") }
	files { addSrcFiles("src/objects") }
	files { addSrcFiles("src/peds") }
	files { addSrcFiles("src/renderer") }
	files { addSrcFiles("src/rw") }
	files { addSrcFiles("src/save") }
	files { addSrcFiles("src/skel") }
	files { addSrcFiles("src/skel/glfw") }
	files { addSrcFiles("src/text") }
	files { addSrcFiles("src/vehicles") }
	files { addSrcFiles("src/weapons") }
	files { addSrcFiles("src/extras") }
	if(not _OPTIONS["no-git-hash"]) then
		files { "src/extras/GitSHA1.cpp" } -- this won't be in repo in first build
	else
		removefiles { "src/extras/GitSHA1.cpp" } -- but it will be everytime after
	end

	includedirs { "src" }
	includedirs { "src/animation" }
	includedirs { "src/audio" }
	includedirs { "src/audio/eax" }
	includedirs { "src/audio/oal" }
	includedirs { "src/buildings" }
	includedirs { "src/collision" }
	includedirs { "src/control" }
	includedirs { "src/core" }
	includedirs { "src/entities" }
	includedirs { "src/math" }
	includedirs { "src/modelinfo" }
	includedirs { "src/objects" }
	includedirs { "src/peds" }
	includedirs { "src/renderer" }
	includedirs { "src/rw" }
	includedirs { "src/save/" }
	includedirs { "src/skel/" }
	includedirs { "src/skel/glfw" }
	includedirs { "src/text" }
	includedirs { "src/vehicles" }
	includedirs { "src/weapons" }
	includedirs { "src/extras" }

	if(not _OPTIONS["no-git-hash"]) then
		defines { "USE_OUR_VERSIONING" }
	end

	if _OPTIONS["with-opus"] then
		includedirs { "vendor/ogg/include" }
		includedirs { "vendor/opus/include" }
		includedirs { "vendor/opusfile/include" }
	end

	filter "configurations:Vanilla"
		defines { "VANILLA_DEFINES" }

	filter "platforms:*mss"
		defines { "AUDIO_MSS" }
		includedirs { "vendor/milessdk/include" }
		libdirs { "vendor/milessdk/lib" }

	if _OPTIONS["with-opus"] then
		filter "platforms:win*"
			libdirs { "vendor/ogg/win32/VS2015/Win32/%{cfg.buildcfg}" }
			libdirs { "vendor/opus/win32/VS2015/Win32/%{cfg.buildcfg}" }
			libdirs { "vendor/opusfile/win32/VS2015/Win32/Release-NoHTTP" }
		filter {}
		defines { "AUDIO_OPUS" }
	end

	filter "platforms:*oal"
		defines { "AUDIO_OAL" }

	filter {}
	if(os.getenv("GTA_III_RE_DIR")) then
		setpaths(os.getenv("GTA_III_RE_DIR") .. "/", "%(cfg.buildtarget.name)")
	end

	filter "platforms:win*"
		files { addSrcFiles("src/skel/win") }
		includedirs { "src/skel/win" }
		buildoptions { "/Zc:sizedDealloc-" }
		linkoptions "/SAFESEH:NO"
		characterset ("MBCS")
		targetextension ".exe"
		if(_OPTIONS["no-full-paths"]) then
			usefullpaths "off"
			linkoptions "/PDBALTPATH:%_PDB%"
		end
		if(_OPTIONS["with-librw"]) then
			-- external librw is dynamic
			staticruntime "on"
		end
		if(not _OPTIONS["no-git-hash"]) then
			prebuildcommands { '"%{prj.location}..\\printHash.bat" "%{prj.location}..\\src\\extras\\GitSHA1.cpp"' }
		end

	filter "platforms:not win*"
		if(not _OPTIONS["no-git-hash"]) then
			prebuildcommands { '"%{prj.location}/../printHash.sh" "%{prj.location}/../src/extras/GitSHA1.cpp"' }
		end

	filter "platforms:win*glfw*"
		staticruntime "off"

	filter "platforms:*glfw*"
		premake.modules.autoconf.parameters = "-lglfw -lX11"
		autoconfigure {
			-- iterates all configs and runs on them
			["dontWrite"] = function (cfg)
				check_symbol_exists(cfg, "haveX11", "glfwGetX11Display", { "X11/Xlib.h", "X11/XKBlib.h", "GLFW/glfw3.h", "GLFW/glfw3native.h" }, "GLFW_EXPOSE_NATIVE_X11")
				if cfg.autoconf["haveX11"] ~= nil and cfg.autoconf["haveX11"] == 1 then
					table.insert(cfg.links, "X11")
					table.insert(cfg.defines, "GET_KEYBOARD_INPUT_FROM_X11")
				end
			end
		}

	filter "platforms:win*oal"
		includedirs { "vendor/openal-soft/include" }
		includedirs { "vendor/libsndfile/include" }
		includedirs { "vendor/mpg123/include" }

	filter "platforms:win-x86*oal"
		libdirs { "vendor/mpg123/lib/Win32" }
		libdirs { "vendor/libsndfile/lib/Win32" }
		libdirs { "vendor/openal-soft/libs/Win32" }

	filter "platforms:win-amd64*oal"
		libdirs { "vendor/mpg123/lib/Win64" }
		libdirs { "vendor/libsndfile/lib/Win64" }
		libdirs { "vendor/openal-soft/libs/Win64" }

	filter "platforms:linux*oal"
		links { "openal", "mpg123", "sndfile", "pthread" }

	filter "platforms:bsd*oal"
		links { "openal", "mpg123", "sndfile", "pthread" }

	filter "platforms:macosx*oal"
		links { "openal", "mpg123", "sndfile", "pthread" }

	filter "platforms:macosx-arm64-*oal"
		includedirs { "/opt/homebrew/opt/openal-soft/include" }
		libdirs { "/opt/homebrew/opt/openal-soft/lib" }

	filter "platforms:macosx-amd64-*oal"
		includedirs { "/usr/local/opt/openal-soft/include" }
		libdirs { "/usr/local/opt/openal-soft/lib" }

	if _OPTIONS["with-opus"] then
		filter {}
		links { "libogg" }
		links { "opus" }
		links { "opusfile" }
	end

	filter "platforms:*RW33*"
		includedirs { "sdk/rwsdk/include/d3d8" }
		libdirs { "sdk/rwsdk/lib/d3d8/release" }
		links { "rwcore", "rpworld", "rpmatfx", "rpskin", "rphanim", "rtbmp", "rtquat", "rtcharse", "rpanisot" }
		defines { "RWLIBS" }
		linkoptions "/SECTION:_rwcseg,ER!W /MERGE:_rwcseg=.text"

	filter "platforms:*librw*"
		defines { "LIBRW" }
		files { addSrcFiles("src/fakerw") }
		includedirs { "src/fakerw" }
		includedirs { Librw }
		if(_OPTIONS["with-librw"]) then
			libdirs { "vendor/librw/lib/%{cfg.platform}/%{cfg.buildcfg}" }
		end
		links { "rw" }

	filter "platforms:*d3d9*"
		defines { "USE_D3D9" }
		links { "d3d9" }

	filter "platforms:*x86*d3d*"
		includedirs { "sdk/dx8sdk/include" }
		libdirs { "sdk/dx8sdk/lib" }

	filter "platforms:win-x86*gl3_glfw*"
		libdirs { path.join(_OPTIONS["glfwdir32"], "lib-" .. string.gsub(_ACTION or '', "vs", "vc")) }
		links { "opengl32", "glfw3" }

	filter "platforms:win-amd64*gl3_glfw*"
		libdirs { path.join(_OPTIONS["glfwdir64"], "lib-" .. string.gsub(_ACTION or '', "vs", "vc")) }
		links { "opengl32", "glfw3" }

	filter "platforms:linux*gl3_glfw*"
		links { "GL", "glfw" }

	filter "platforms:bsd*gl3_glfw*"
		links { "GL", "glfw", "sysinfo" }
		includedirs { "/usr/local/include" }
		libdirs { "/usr/local/lib" }

	filter "platforms:macosx-arm64-*gl3_glfw*"
		links { "glfw" }
		linkoptions { "-framework OpenGL" }
		includedirs { "/opt/local/include" }
		includedirs {"/opt/homebrew/include" }
		libdirs { "/opt/local/lib" }
		libdirs { "/opt/homebrew/lib" }

	filter "platforms:macosx-amd64-*gl3_glfw*"
		links { "glfw" }
		linkoptions { "-framework OpenGL" }
		includedirs { "/opt/local/include" }
		includedirs {"/usr/local/include" }
		libdirs { "/opt/local/lib" }
		libdirs { "/usr/local/lib" }


code:
@echo off

REM creates version.h with HEAD commit hash
REM params: $1=full path to output file (usually points version.h)

setlocal enableextensions enabledelayedexpansion

cd /d "%~dp0"

break> %1

<nul set /p=^"#define GIT_SHA1 ^"^"> %1

where git
if "%errorlevel%" == "0" ( goto :havegit ) else ( goto :writeending )

:havegit
for /f %%v in ('git rev-parse --short HEAD') do set version=%%v
<nul set /p="%version%" >> %1

:writeending

echo ^" >> %1
echo const char* g_GIT_SHA1 = GIT_SHA1; >> %1

EXIT /B


code:
#!/usr/bin/env sh
if [ -z "${1}" ]
 then
  printf "%s\n" "Input the path to the file for writing the commit hash to."
 else
  printf "%s" "#define GIT_SHA1 \"" > $1

  if (command -v "git" >/dev/null) then
   git rev-parse --short HEAD | tr -d '\n' >> $1
  fi

  printf "%s\n" "\"" >> $1
  printf "%s\n" "const char* g_GIT_SHA1 = GIT_SHA1;" >> $1
fi


code:
find_package(Threads REQUIRED)
set(THREADS_PREFER_PTHREAD_FLAG ON)

file(GLOB_RECURSE ${PROJECT}_SOURCES "*.cpp" "*.h" "*.rc")

function(header_directories RETURN_LIST)
    file(GLOB_RECURSE ALL_SRCS *.h *.cpp *.c)
    set(RELDIRS)
    foreach(SRC ${ALL_SRCS})
        file(RELATIVE_PATH RELSRC "${CMAKE_CURRENT_SOURCE_DIR}" "${SRC}")
        get_filename_component(RELDIR "${RELSRC}" DIRECTORY)
        list(APPEND RELDIRS ${RELDIR})
    endforeach()
    list(REMOVE_DUPLICATES RELDIRS)
    set(${RETURN_LIST} ${RELDIRS} PARENT_SCOPE)
endfunction()

header_directories(${PROJECT}_INCLUDES)

configure_file("${CMAKE_CURRENT_SOURCE_DIR}/extras/GitSHA1.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/extras/GitSHA1.cpp" @ONLY)
list(APPEND ${PROJECT}_SOURCES "${CMAKE_CURRENT_BINARY_DIR}/extras/GitSHA1.cpp")

add_executable(${EXECUTABLE} WIN32
    ${${PROJECT}_SOURCES}
)

target_link_libraries(${EXECUTABLE} PRIVATE
    librw::librw
    Threads::Threads
)

target_include_directories(${EXECUTABLE}
    PRIVATE
        $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
        $<BUILD_INTERFACE:${${PROJECT}_INCLUDES}>
)

target_compile_definitions(${EXECUTABLE}
    PRIVATE
        $<IF:$<CONFIG:DEBUG>,DEBUG,NDEBUG>
        LIBRW
        CMAKE_NO_AUTOLINK
)

if(LIBRW_PLATFORM_D3D9)
    target_compile_definitions(${EXECUTABLE}
        PUBLIC
            USE_D3D9
    )
endif()

target_compile_definitions(${EXECUTABLE} PRIVATE CMAKE_BUILD)
target_compile_definitions(${EXECUTABLE} PRIVATE USE_OUR_VERSIONING)

if(${PROJECT}_AUDIO STREQUAL "OAL")
    find_package(OpenAL REQUIRED)
    if(TARGET OpenAL::OpenAL)
        target_link_libraries(${EXECUTABLE} PRIVATE OpenAL::OpenAL)
    else()
        target_include_directories(${EXECUTABLE} PRIVATE ${OPENAL_INCLUDE_DIR})
        target_link_libraries(${EXECUTABLE} PRIVATE ${OPENAL_LIBRARY})
        target_compile_definitions(${EXECUTABLE} PRIVATE ${OPENAL_DEFINITIONS})
    endif()
    target_compile_definitions(${EXECUTABLE} PRIVATE AUDIO_OAL)
elseif(${PROJECT}_AUDIO STREQUAL "MSS")
    find_package(MilesSDK REQUIRED)
    target_compile_definitions(${EXECUTABLE} PRIVATE AUDIO_MSS)
    target_link_libraries(${EXECUTABLE} PRIVATE MilesSDK::MilesSDK)
endif()

find_package(mpg123 REQUIRED)
target_link_libraries(${EXECUTABLE} PRIVATE
    MPG123::libmpg123
)
if(${PROJECT}_WITH_OPUS)
    find_package(opusfile REQUIRED)
    target_link_libraries(${EXECUTABLE} PRIVATE
        opusfile::opusfile
    )
    target_compile_definitions(${EXECUTABLE} PRIVATE AUDIO_OPUS)
endif()
if(${PROJECT}_WITH_LIBSNDFILE)
    find_package(SndFile REQUIRED)
    target_link_libraries(${EXECUTABLE} PRIVATE
        SndFile::SndFile
    )
    target_compile_definitions(${EXECUTABLE} PRIVATE AUDIO_OAL_USE_SNDFILE)
endif()

target_compile_definitions(${EXECUTABLE} PRIVATE )

option(${PROJECT}_WITH_SANITIZERS "Use UB sanitizers (better crash log)" OFF)
option(${PROJECT}_WITH_ASAN "Use Address sanitizer (better crash log)" OFF)

if(${PROJECT}_WITH_SANITIZERS)
    target_compile_options(${EXECUTABLE} PUBLIC
            -fsanitize=undefined,float-divide-by-zero,integer,implicit-conversion,implicit-integer-truncation,implicit-integer-arithmetic-value-change,local-bounds,nullability
            -g3 -fno-omit-frame-pointer)
    target_link_options(${EXECUTABLE} PUBLIC -fsanitize=undefined,float-divide-by-zero,integer,implicit-conversion,implicit-integer-truncation,implicit-integer-arithmetic-value-change,local-bounds,nullability)
endif()

if(${PROJECT}_WITH_ASAN)
    target_compile_options(${EXECUTABLE} PUBLIC -fsanitize=address -g3 -fno-omit-frame-pointer)
    target_link_options(${EXECUTABLE} PUBLIC -fsanitize=address)
endif()

if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
    target_compile_options(${EXECUTABLE}
        PRIVATE
            "-Wall"
    )
    if (NOT LIBRW_PLATFORM_PS2)
        target_compile_options(${EXECUTABLE}
            PRIVATE
                -Wextra
                -Wdouble-promotion
                -Wpedantic
        )
    endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    target_compile_options(${EXECUTABLE}
        PUBLIC
            /Zc:sizedDealloc-
    )
endif()

if(NINTENDO_SWITCH)
    set(${PROJECT}_C_CXX_EXTENSIONS ON)
else()
    set(${PROJECT}_C_CXX_EXTENSIONS OFF)
endif()

if(LIBRW_PLATFORM_GL3 AND LIBRW_GL3_GFXLIB STREQUAL "GLFW")
	include(CheckSymbolExists)

	set(CMAKE_REQUIRED_LIBRARIES glfw)
	set(CMAKE_REQUIRED_DEFINITIONS -DGLFW_EXPOSE_NATIVE_X11)
	check_symbol_exists(glfwGetX11Display "GLFW/glfw3.h;GLFW/glfw3native.h" GLFW_HAS_X11)
	unset(CMAKE_REQUIRED_DEFINITIONS)
	unset(CMAKE_REQUIRED_LIBRARIES)

	if (GLFW_HAS_X11)
		find_package(X11 REQUIRED)
		target_link_libraries(${EXECUTABLE} PRIVATE X11::X11)
		target_compile_definitions(${EXECUTABLE} PRIVATE GET_KEYBOARD_INPUT_FROM_X11)
	endif (GLFW_HAS_X11)
endif()

set_target_properties(${EXECUTABLE}
    PROPERTIES
        C_STANDARD 11
        C_EXTENSIONS ${${PROJECT}_C_CXX_EXTENSIONS}
        C_STANDARD_REQUIRED ON
        CXX_STANDARD 11
        CXX_EXTENSIONS ${${PROJECT}_C_CXX_EXTENSIONS}
        CXX_STANDARD_REQUIRED ON
)

if(${PROJECT}_INSTALL)
    install(
        TARGETS ${EXECUTABLE}
        EXPORT ${EXECUTABLE}-targets
        RUNTIME DESTINATION "."
    )
    if(MSVC)
        install(FILES $<TARGET_PDB_FILE:${EXECUTABLE}> DESTINATION "." OPTIONAL)
    endif()
endif()

re3_platform_target(${EXECUTABLE} INSTALL)


code:
#include "common.h"

#if defined _WIN32 && !defined __MINGW32__
#if defined __MWERKS__
#include <wctype.h>
#else
#include "ctype.h"
#endif
#else
#include <cwctype>
#endif

#include "General.h"
#include "RwHelper.h"
#include "ModelInfo.h"
#include "AnimManager.h"
#include "RpAnimBlend.h"
#include "AnimBlendAssociation.h"
#include "AnimBlendAssocGroup.h"

CAnimBlendAssocGroup::CAnimBlendAssocGroup(void)
{
	assocList = nil;
	numAssociations = 0;
}

CAnimBlendAssocGroup::~CAnimBlendAssocGroup(void)
{
	DestroyAssociations();
}

void
CAnimBlendAssocGroup::DestroyAssociations(void)
{
	if(assocList){
		delete[] assocList;
		assocList = nil;
		numAssociations = 0;
	}
}

CAnimBlendAssociation*
CAnimBlendAssocGroup::GetAnimation(uint32 id)
{
	return &assocList[id];
}

CAnimBlendAssociation*
CAnimBlendAssocGroup::GetAnimation(const char *name)
{
	int i;
	for(i = 0; i < numAssociations; i++)
		if(!CGeneral::faststricmp(assocList[i].hierarchy->name, name))
			return &assocList[i];
	return nil;
}

CAnimBlendAssociation*
CAnimBlendAssocGroup::CopyAnimation(uint32 id)
{
	CAnimBlendAssociation *anim = GetAnimation(id);
	if(anim == nil)
		return nil;
	CAnimManager::UncompressAnimation(anim->hierarchy);
	return new CAnimBlendAssociation(*anim);
}

CAnimBlendAssociation*
CAnimBlendAssocGroup::CopyAnimation(const char *name)
{
	CAnimBlendAssociation *anim = GetAnimation(name);
	if(anim == nil)
		return nil;
	CAnimManager::UncompressAnimation(anim->hierarchy);
	return new CAnimBlendAssociation(*anim);
}

bool
strcmpIgnoringDigits(const char *s1, const char *s2)
{
	char c1, c2;

	for(;;){
		c1 = *s1;
		c2 = *s2;
		if(c1) s1++;
		if(c2) s2++;
		if(c1 == '\0' && c2 == '\0') return true;
#ifndef ASCII_STRCMP
		if(iswdigit(c1) && iswdigit(c2))
#else
		if(__ascii_iswdigit(c1) && __ascii_iswdigit(c2))
#endif
			continue;
#ifndef ASCII_STRCMP
		c1 = toupper(c1);
		c2 = toupper(c2);
#else
		c1 = __ascii_toupper(c1);
		c2 = __ascii_toupper(c2);
#endif

		if(c1 != c2)
			return false;
	}
}

CBaseModelInfo*
GetModelFromName(const char *name)
{
	int i;
	CBaseModelInfo *mi;

	for(i = 0; i < MODELINFOSIZE; i++){
		mi = CModelInfo::GetModelInfo(i);
		if(mi && mi->GetRwObject() && RwObjectGetType(mi->GetRwObject()) == rpCLUMP &&
		   strcmpIgnoringDigits(mi->GetModelName(), name))
			return mi;
	}
	return nil;
}

void
CAnimBlendAssocGroup::CreateAssociations(const char *name)
{
	int i;
	CAnimBlock *animBlock;

	if(assocList)
		DestroyAssociations();

	animBlock = CAnimManager::GetAnimationBlock(name);
	assocList = new CAnimBlendAssociation[animBlock->numAnims];
	numAssociations = 0;

	for(i = 0; i < animBlock->numAnims; i++){
		CAnimBlendHierarchy *anim = CAnimManager::GetAnimation(animBlock->firstIndex + i);
		CBaseModelInfo *model = GetModelFromName(anim->name);
		assert(model);
		printf("Associated anim %s with model %s\n", anim->name, model->GetModelName());
		RpClump *clump = (RpClump*)model->CreateInstance();
#ifdef PED_SKIN
		if(IsClumpSkinned(clump))
			RpClumpForAllAtomics(clump, AtomicRemoveAnimFromSkinCB, nil);
#endif
		RpAnimBlendClumpInit(clump);
		assocList[i].Init(clump, anim);
		RpClumpDestroy(clump);
		assocList[i].animId = i;
	}
	numAssociations = animBlock->numAnims;
}

// Create associations from hierarchies for a given clump
void
CAnimBlendAssocGroup::CreateAssociations(const char *blockName, RpClump *clump, const char **animNames, int numAssocs)
{
	int i;
	CAnimBlock *animBlock;

	if(assocList)
		DestroyAssociations();

	animBlock = CAnimManager::GetAnimationBlock(blockName);
	assocList = new CAnimBlendAssociation[numAssocs];

	numAssociations = 0;
	for(i = 0; i < numAssocs; i++){
		assocList[i].Init(clump, CAnimManager::GetAnimation(animNames[i], animBlock));
		assocList[i].animId = i;
	}
	numAssociations = numAssocs;
}


code:
#pragma once

class CAnimBlendAssociation;

class CAnimBlendAssocGroup
{
public:
	CAnimBlendAssociation *assocList;
	int32 numAssociations;

	CAnimBlendAssocGroup(void);
	~CAnimBlendAssocGroup(void);
	void DestroyAssociations(void);
	CAnimBlendAssociation *GetAnimation(uint32 id);
	CAnimBlendAssociation *GetAnimation(const char *name);
	CAnimBlendAssociation *CopyAnimation(uint32 id);
	CAnimBlendAssociation *CopyAnimation(const char *name);
	void CreateAssociations(const char *name);
	void CreateAssociations(const char *blockName, RpClump *clump, const char **animNames, int numAssocs);
};


code:
#include "common.h"

#include "AnimBlendHierarchy.h"
#include "AnimBlendClumpData.h"
#include "RpAnimBlend.h"
#include "AnimManager.h"
#include "AnimBlendAssociation.h"
#include "MemoryMgr.h"

CAnimBlendAssociation::CAnimBlendAssociation(void)
{
	nodes = nil;
	blendAmount = 1.0f;
	blendDelta = 0.0f;
	currentTime = 0.0f;
	speed = 1.0f;
	timeStep = 0.0f;
	animId = -1;
	flags = 0;
	callbackType = CB_NONE;
	link.Init();
}

CAnimBlendAssociation::CAnimBlendAssociation(CAnimBlendAssociation &other)
{
	nodes = nil;
	blendAmount = 1.0f;
	blendDelta = 0.0f;
	currentTime = 0.0f;
	speed = 1.0f;
	timeStep = 0.0f;
	callbackType = CB_NONE;
	link.Init();
	Init(other);
}

CAnimBlendAssociation::~CAnimBlendAssociation(void)
{
	FreeAnimBlendNodeArray();
	link.Remove();
}

void
CAnimBlendAssociation::AllocateAnimBlendNodeArray(int n)
{
	int i;

	nodes = (CAnimBlendNode*)RwMallocAlign(n*sizeof(CAnimBlendNode), 64);
	for(i = 0; i < n; i++)
		nodes[i].Init();
}

void
CAnimBlendAssociation::FreeAnimBlendNodeArray(void)
{
	assert(nodes != nil);
	RwFreeAlign(nodes);
}

void
CAnimBlendAssociation::Init(RpClump *clump, CAnimBlendHierarchy *hier)
{
	int i;
	AnimBlendFrameData *frame;

	CAnimBlendClumpData *clumpData = *RPANIMBLENDCLUMPDATA(clump);
	numNodes = clumpData->numFrames;
	AllocateAnimBlendNodeArray(numNodes);
	for(i = 0; i < numNodes; i++)
		nodes[i].association = this;
	hierarchy = hier;

	// Init every node from a sequence and a Clump frame
	// NB: This is where the order of nodes is defined
	for(i = 0; i < hier->numSequences; i++){
		CAnimBlendSequence *seq = &hier->sequences[i];
		frame = RpAnimBlendClumpFindFrame(clump, seq->name);
		if(frame && seq->numFrames > 0)
			nodes[frame - clumpData->frames].sequence = seq;
	}
}

void
CAnimBlendAssociation::Init(CAnimBlendAssociation &assoc)
{
	int i;

	hierarchy = assoc.hierarchy;
	numNodes = assoc.numNodes;
	flags = assoc.flags;
	animId = assoc.animId;
	AllocateAnimBlendNodeArray(numNodes);
	for(i = 0; i < numNodes; i++){
		nodes[i] = assoc.nodes[i];
		nodes[i].association = this;
	}
}

void
CAnimBlendAssociation::SetBlend(float amount, float delta)
{
	blendAmount = amount;
	blendDelta = delta;
}

void
CAnimBlendAssociation::SetFinishCallback(void (*cb)(CAnimBlendAssociation*, void*), void *arg)
{
	callbackType = CB_FINISH;
	callback = cb;
	callbackArg = arg;
}

void
CAnimBlendAssociation::SetDeleteCallback(void (*cb)(CAnimBlendAssociation*, void*), void *arg)
{
	callbackType = CB_DELETE;
	callback = cb;
	callbackArg = arg;
}

void
CAnimBlendAssociation::SetCurrentTime(float time)
{
	int i;

	for(currentTime = time; currentTime >= hierarchy->totalLength; currentTime -= hierarchy->totalLength)
		if(!IsRepeating())
			return;
	CAnimManager::UncompressAnimation(hierarchy);
	for(i = 0; i < numNodes; i++)
		if(nodes[i].sequence)
			nodes[i].FindKeyFrame(currentTime);
}

void
CAnimBlendAssociation::SyncAnimation(CAnimBlendAssociation *other)
{
	SetCurrentTime(other->currentTime/other->hierarchy->totalLength * hierarchy->totalLength);
}

void
CAnimBlendAssociation::Start(float time)
{
	flags |= ASSOC_RUNNING;
	SetCurrentTime(time);
}

bool
CAnimBlendAssociation::UpdateTime(float timeDelta, float relSpeed)
{
	if(!IsRunning())
		return true;

	timeStep = (flags & ASSOC_MOVEMENT ? relSpeed*hierarchy->totalLength : speed) * timeDelta;
	currentTime += timeStep;

	if(currentTime >= hierarchy->totalLength){
		// Ran past end

		if(IsRepeating())
			currentTime -= hierarchy->totalLength;
		else{
			currentTime = hierarchy->totalLength;
			flags &= ~ASSOC_RUNNING;
			if(flags & ASSOC_FADEOUTWHENDONE){
				flags |= ASSOC_DELETEFADEDOUT;
				blendDelta = -4.0f;
			}
			if(callbackType == CB_FINISH){
				callbackType = CB_NONE;
				callback(this, callbackArg);
			}
		}
	}
	return true;
}

// return whether we still exist after this function
bool
CAnimBlendAssociation::UpdateBlend(float timeDelta)
{
	blendAmount += blendDelta * timeDelta;

	if(blendAmount <= 0.0f && blendDelta < 0.0f){
		// We're faded out and are not fading in
		blendAmount = 0.0f;
		blendDelta = Max(0.0f, blendDelta);
		if(flags & ASSOC_DELETEFADEDOUT){
			if(callbackType == CB_FINISH || callbackType == CB_DELETE)
				callback(this, callbackArg);
			delete this;
			return false;
		}
	}

	if(blendAmount > 1.0f){
		// Maximally faded in, clamp values
		blendAmount = 1.0f;
		blendDelta = Min(0.0f, blendDelta);
	}

	return true;
}


code:
#pragma once

#include "AnimBlendList.h"
#include "AnimBlendNode.h"
#include "AnimBlendHierarchy.h"

enum {
	ASSOC_RUNNING = 1,
	ASSOC_REPEAT = 2,
	ASSOC_DELETEFADEDOUT = 4,
	ASSOC_FADEOUTWHENDONE = 8,
	ASSOC_PARTIAL = 0x10,
	ASSOC_MOVEMENT = 0x20,	// ???
	ASSOC_HAS_TRANSLATION = 0x40,
	ASSOC_WALK = 0x80,	// for CPed::PlayFootSteps(void)
	ASSOC_IDLE = 0x100,	// only used by xpress scratch, see CPed::Chat(void)
	ASSOC_NOWALK = 0x200,	// see CPed::PlayFootSteps(void)
	ASSOC_BLOCK = 0x400,	// unused in assoc description, blocks other anims from being played
	ASSOC_FRONTAL = 0x800, // anims that we fall to front
	ASSOC_HAS_X_TRANSLATION = 0x1000,	// for 2d velocity extraction
};

// Anim hierarchy associated with a clump
// Holds the interpolated state of all nodes.
// Also used as template for other clumps.
class CAnimBlendAssociation
{
public:
	enum {
		// callbackType
		CB_NONE,
		CB_FINISH,
		CB_DELETE
	};

	CAnimBlendLink link;

	int32 numNodes;			// taken from CAnimBlendClumpData::numFrames
	// NB: Order of these depends on order of nodes in Clump this was built from
	CAnimBlendNode *nodes;
	CAnimBlendHierarchy *hierarchy;
	float blendAmount;
	float blendDelta;	// how much blendAmount changes over time
	float currentTime;
	float speed;
	float timeStep;
	int32 animId;
	int32 flags;
	int32 callbackType;
	void (*callback)(CAnimBlendAssociation*, void*);
	void *callbackArg;

	bool IsRunning(void) { return !!(flags & ASSOC_RUNNING); }
	bool IsRepeating(void) { return !!(flags & ASSOC_REPEAT); }
	bool IsPartial(void) { return !!(flags & ASSOC_PARTIAL); }
	bool IsMovement(void) { return !!(flags & ASSOC_MOVEMENT); }
	bool HasTranslation(void) { return !!(flags & ASSOC_HAS_TRANSLATION); }
	bool HasXTranslation(void) { return !!(flags & ASSOC_HAS_X_TRANSLATION); }

	float GetBlendAmount(float weight) { return IsPartial() ? blendAmount : blendAmount*weight; }
	CAnimBlendNode *GetNode(int i) { return &nodes[i]; }

	CAnimBlendAssociation(void);
	CAnimBlendAssociation(CAnimBlendAssociation &other);
#ifndef FIX_BUGS
	virtual
#endif
	~CAnimBlendAssociation(void);
	void AllocateAnimBlendNodeArray(int n);
	void FreeAnimBlendNodeArray(void);
	void Init(RpClump *clump, CAnimBlendHierarchy *hier);
	void Init(CAnimBlendAssociation &assoc);
	void SetBlend(float amount, float delta);
	void SetFinishCallback(void (*callback)(CAnimBlendAssociation*, void*), void *arg);
	void SetDeleteCallback(void (*callback)(CAnimBlendAssociation*, void*), void *arg);
	void SetCurrentTime(float time);
	void SyncAnimation(CAnimBlendAssociation *other);
	void Start(float time);
	bool UpdateTime(float timeDelta, float relSpeed);
	bool UpdateBlend(float timeDelta);

	void SetRun(void) { flags |= ASSOC_RUNNING; }

	inline float GetTimeLeft() { return hierarchy->totalLength - currentTime; }

	static CAnimBlendAssociation *FromLink(CAnimBlendLink *l) {
		return (CAnimBlendAssociation*)((uint8*)l - offsetof(CAnimBlendAssociation, link));
	}
};

VALIDATE_SIZE(CAnimBlendAssociation, 0x40);


code:
#include "common.h"

#include "AnimBlendClumpData.h"
#include "MemoryMgr.h"

CAnimBlendClumpData::CAnimBlendClumpData(void)
{
	numFrames = 0;
	velocity2d = nil;
	frames = nil;
	link.Init();
}

CAnimBlendClumpData::~CAnimBlendClumpData(void)
{
	link.Remove();
	if(frames)
		RwFreeAlign(frames);
}

void
CAnimBlendClumpData::SetNumberOfFrames(int n)
{
	if(frames)
		RwFreeAlign(frames);
	numFrames = n;
	frames = (AnimBlendFrameData*)RwMallocAlign(numFrames * sizeof(AnimBlendFrameData), 64);
}

void
CAnimBlendClumpData::ForAllFrames(void (*cb)(AnimBlendFrameData*, void*), void *arg)
{
	int i;
	for(i = 0; i < numFrames; i++)
		cb(&frames[i], arg);
}


code:
#pragma once

#include "AnimBlendList.h"

struct AnimBlendFrameData
{
	enum {
		IGNORE_ROTATION = 2,
		IGNORE_TRANSLATION = 4,
		VELOCITY_EXTRACTION = 8,
		VELOCITY_EXTRACTION_3D = 0x10,
	};

	uint8 flag;
	RwV3d resetPos;
#ifdef PED_SKIN
	union {
		RwFrame *frame;
		RpHAnimStdInterpFrame *hanimFrame;
	};
	int32 nodeID;
#else
	RwFrame *frame;
#endif
};
#ifndef PED_SKIN
VALIDATE_SIZE(AnimBlendFrameData, 0x14);
#endif

class CAnimBlendClumpData
{
public:
	CAnimBlendLink link;
	int32 numFrames;
#ifdef PED_SKIN
	int32 modelNumber;	// doesn't seem to be used
#endif
	union {
		CVector2D *velocity2d;
		CVector *velocity3d;
	};
	// order of frames is determined by RW hierarchy
	AnimBlendFrameData *frames;

	CAnimBlendClumpData(void);
	~CAnimBlendClumpData(void);
	void SetNumberOfFrames(int n);
#ifdef PED_SKIN
	void SetNumberOfBones(int n) { SetNumberOfFrames(n); }
#endif
	void ForAllFrames(void (*cb)(AnimBlendFrameData*, void*), void *arg);
};
#ifndef PED_SKIN
VALIDATE_SIZE(CAnimBlendClumpData, 0x14);
#endif


code:
#include "common.h"

#include "AnimBlendSequence.h"
#include "AnimBlendHierarchy.h"

CAnimBlendHierarchy::CAnimBlendHierarchy(void)
{
	sequences = nil;
	numSequences = 0;
	compressed = 0;
	totalLength = 0.0f;
	linkPtr = nil;
}

void
CAnimBlendHierarchy::Shutdown(void)
{
	RemoveAnimSequences();
	compressed = 0;
	linkPtr = nil;
}

void
CAnimBlendHierarchy::SetName(char *name)
{
	strncpy(this->name, name, 24);
}

void
CAnimBlendHierarchy::CalcTotalTime(void)
{
	int i, j;
	totalLength = 0.0f;

	for(i = 0; i < numSequences; i++){
		float seqTime = 0.0f;
		for(j = 0; j < sequences[i].numFrames; j++)
			seqTime += sequences[i].GetKeyFrame(j)->deltaTime;
		totalLength = Max(totalLength, seqTime);
	}
}

void
CAnimBlendHierarchy::RemoveQuaternionFlips(void)
{
	int i;

	for(i = 0; i < numSequences; i++)
		sequences[i].RemoveQuaternionFlips();
}

void
CAnimBlendHierarchy::RemoveAnimSequences(void)
{
	delete[] sequences;
	numSequences = 0;
}

void
CAnimBlendHierarchy::Uncompress(void)
{
#ifdef ANIM_COMPRESSION
	int i;
	assert(compressed);
	for(i = 0; i < numSequences; i++)
		sequences[i].Uncompress();
#endif
	if(totalLength == 0.0f)
		CalcTotalTime();
	compressed = 0;
}

void
CAnimBlendHierarchy::RemoveUncompressedData(void)
{
#ifdef ANIM_COMPRESSION
	int i;
	assert(!compressed);
	for(i = 0; i < numSequences; i++)
		sequences[i].RemoveUncompressedData();
#endif
	compressed = 1;
}

#ifdef USE_CUSTOM_ALLOCATOR
void
CAnimBlendHierarchy::MoveMemory(bool onlyone)
{
	int i;
	for(i = 0; i < numSequences; i++)
		if(sequences[i].MoveMemory() && onlyone)
			return;
}
#endif


code:
#pragma once

#include "templates.h"

#ifdef MoveMemory
#undef MoveMemory	// windows shit
#endif

class CAnimBlendSequence;

// A collection of sequences
class CAnimBlendHierarchy
{
public:
	char name[24];
	CAnimBlendSequence *sequences;
	int16 numSequences;
	int16 compressed;
	float totalLength;
	CLink<CAnimBlendHierarchy*> *linkPtr;

	CAnimBlendHierarchy(void);
	void Shutdown(void);
	void SetName(char *name);
	void CalcTotalTime(void);
	void RemoveQuaternionFlips(void);
	void RemoveAnimSequences(void);
	void Uncompress(void);
	void RemoveUncompressedData(void);
	void MoveMemory(bool onlyone = false);
};

VALIDATE_SIZE(CAnimBlendHierarchy, 0x28);


code:
#pragma once

// name made up
class CAnimBlendLink
{
public:
	CAnimBlendLink *next;
	CAnimBlendLink *prev;

	void Init(void){
		next = nil;
		prev = nil;
	}
	void Prepend(CAnimBlendLink *link){
		if(next)
		        next->prev = link;
		link->next = next;
		link->prev = this;
		next = link;
	}
	void Remove(void){
		if(prev)
			prev->next = next;
		if(next)
			next->prev = prev;
		Init();
	}
};


code:
#include "common.h"

#include "AnimBlendAssociation.h"
#include "AnimBlendNode.h"

void
CAnimBlendNode::Init(void)
{
	frameA = -1;
	frameB = -1;
	remainingTime = 0.0f;
	sequence = nil;
	association = nil;
}

bool
CAnimBlendNode::Update(CVector &trans, CQuaternion &rot, float weight)
{
	bool looped = false;

	trans = CVector(0.0f, 0.0f, 0.0f);
	rot = CQuaternion(0.0f, 0.0f, 0.0f, 0.0f);

	if(association->IsRunning()){
		remainingTime -= association->timeStep;
		if(remainingTime <= 0.0f)
			looped = NextKeyFrame();
	}

	float blend = association->GetBlendAmount(weight);
	if(blend > 0.0f){
		KeyFrameTrans *kfA = (KeyFrameTrans*)sequence->GetKeyFrame(frameA);
		KeyFrameTrans *kfB = (KeyFrameTrans*)sequence->GetKeyFrame(frameB);
		float t = kfA->deltaTime == 0.0f ? 0.0f : (kfA->deltaTime - remainingTime)/kfA->deltaTime;
		if(sequence->type & CAnimBlendSequence::KF_TRANS){
			trans = kfB->translation + t*(kfA->translation - kfB->translation);
			trans *= blend;
		}
		if(sequence->type & CAnimBlendSequence::KF_ROT){
			rot.Slerp(kfB->rotation, kfA->rotation, theta, invSin, t);
			rot *= blend;
		}
	}

	return looped;
}

bool
CAnimBlendNode::NextKeyFrame(void)
{
	bool looped;

	if(sequence->numFrames <= 1)
		return false;

	looped = false;
	frameB = frameA;

	// Advance as long as we have to
	while(remainingTime <= 0.0f){
		frameA++;

		if(frameA >= sequence->numFrames){
			// reached end of animation
			if(!association->IsRepeating()){
				frameA--;
				remainingTime = 0.0f;
				return false;
			}
			looped = true;
			frameA = 0;
		}

		remainingTime += sequence->GetKeyFrame(frameA)->deltaTime;
	}

	frameB = frameA - 1;
	if(frameB < 0)
		frameB += sequence->numFrames;

	CalcDeltas();
	return looped;
}

// Set animation to time t
bool
CAnimBlendNode::FindKeyFrame(float t)
{
	if(sequence->numFrames < 1)
		return false;

	frameA = 0;
	frameB = frameA;

	if(sequence->numFrames >= 2){
		frameA++;

		// advance until t is between frameB and frameA
		while(t > sequence->GetKeyFrame(frameA)->deltaTime){
			t -= sequence->GetKeyFrame(frameA)->deltaTime;
			frameB = frameA++;
			if(frameA >= sequence->numFrames){
				// reached end of animation
				if(!association->IsRepeating())
					return false;
				frameA = 0;
				frameB = 0;
			}
		}

		remainingTime = sequence->GetKeyFrame(frameA)->deltaTime - t;
	}

	CalcDeltas();
	return true;
}

void
CAnimBlendNode::CalcDeltas(void)
{
	if((sequence->type & CAnimBlendSequence::KF_ROT) == 0)
		return;
	KeyFrame *kfA = sequence->GetKeyFrame(frameA);
	KeyFrame *kfB = sequence->GetKeyFrame(frameB);
	float cos = DotProduct(kfA->rotation, kfB->rotation);
	if(cos > 1.0f)
		cos = 1.0f;
	theta = Acos(cos);
	invSin = theta == 0.0f ?  0.0f : 1.0f/Sin(theta);
}

void
CAnimBlendNode::GetCurrentTranslation(CVector &trans, float weight)
{
	trans = CVector(0.0f, 0.0f, 0.0f);

	float blend = association->GetBlendAmount(weight);
	if(blend > 0.0f){
		KeyFrameTrans *kfA = (KeyFrameTrans*)sequence->GetKeyFrame(frameA);
		KeyFrameTrans *kfB = (KeyFrameTrans*)sequence->GetKeyFrame(frameB);
		float t = (kfA->deltaTime - remainingTime)/kfA->deltaTime;
		if(sequence->type & CAnimBlendSequence::KF_TRANS){
			trans = kfB->translation + t*(kfA->translation - kfB->translation);
			trans *= blend;
		}
	}
}

void
CAnimBlendNode::GetEndTranslation(CVector &trans, float weight)
{
	trans = CVector(0.0f, 0.0f, 0.0f);

	float blend = association->GetBlendAmount(weight);
	if(blend > 0.0f){
		KeyFrameTrans *kf = (KeyFrameTrans*)sequence->GetKeyFrame(sequence->numFrames-1);
		if(sequence->type & CAnimBlendSequence::KF_TRANS)
			trans = kf->translation * blend;
	}
}


code:
#pragma once

#include "AnimBlendSequence.h"

class CAnimBlendAssociation;

// The interpolated state between two key frames in a sequence
class CAnimBlendNode
{
public:
	// for slerp
	float theta;		// angle between quaternions
	float invSin;		// 1/Sin(theta)
	// indices into array in sequence
	int32 frameA;		// next key frame
	int32 frameB;		// previous key frame
	float remainingTime;	// time until frames have to advance
	CAnimBlendSequence *sequence;
	CAnimBlendAssociation *association;

	void Init(void);
	bool Update(CVector &trans, CQuaternion &rot, float weight);
	bool NextKeyFrame(void);
	bool FindKeyFrame(float t);
	void CalcDeltas(void);
	void GetCurrentTranslation(CVector &trans, float weight);
	void GetEndTranslation(CVector &trans, float weight);
};

VALIDATE_SIZE(CAnimBlendNode, 0x1C);


code:
#include "common.h"

#include "AnimBlendSequence.h"
#include "MemoryHeap.h"

CAnimBlendSequence::CAnimBlendSequence(void)
{
	type = 0;
	numFrames = 0;
	keyFrames = nil;
	keyFramesCompressed = nil;
#ifdef PED_SKIN
	boneTag = -1;
#endif
}

CAnimBlendSequence::~CAnimBlendSequence(void)
{
	if(keyFrames)
		RwFree(keyFrames);
	if(keyFramesCompressed)
		RwFree(keyFramesCompressed);
}

void
CAnimBlendSequence::SetName(char *name)
{
	strncpy(this->name, name, 24);
}

void
CAnimBlendSequence::SetNumFrames(int numFrames, bool translation)
{
	int sz;

	if(translation){
		sz = sizeof(KeyFrameTrans);
		type |= KF_ROT | KF_TRANS;
	}else{
		sz = sizeof(KeyFrame);
		type |= KF_ROT;
	}
	keyFrames = RwMalloc(sz * numFrames);
	this->numFrames = numFrames;
}

void
CAnimBlendSequence::RemoveQuaternionFlips(void)
{
	int i;
	CQuaternion last;
	KeyFrame *frame;

	if(numFrames < 2)
		return;

	frame = GetKeyFrame(0);
	last = frame->rotation;
	for(i = 1; i < numFrames; i++){
		frame = GetKeyFrame(i);
		if(DotProduct(last, frame->rotation) < 0.0f)
			frame->rotation = -frame->rotation;
		last = frame->rotation;
	}
}

void
CAnimBlendSequence::Uncompress(void)
{
	int i;

	if(numFrames == 0)
		return;

	PUSH_MEMID(MEMID_ANIMATION);

	float rotScale = 1.0f/4096.0f;
	float timeScale = 1.0f/60.0f;
	float transScale = 1.0f/128.0f;
	if(type & KF_TRANS){
		void *newKfs = RwMalloc(numFrames * sizeof(KeyFrameTrans));
		KeyFrameTransCompressed *ckf = (KeyFrameTransCompressed*)keyFramesCompressed;
		KeyFrameTrans *kf = (KeyFrameTrans*)newKfs;
		for(i = 0; i < numFrames; i++){
			kf->rotation.x = ckf->rot[0]*rotScale;
			kf->rotation.y = ckf->rot[1]*rotScale;
			kf->rotation.z = ckf->rot[2]*rotScale;
			kf->rotation.w = ckf->rot[3]*rotScale;
			kf->deltaTime = ckf->deltaTime*timeScale;
			kf->translation.x = ckf->trans[0]*transScale;
			kf->translation.y = ckf->trans[1]*transScale;
			kf->translation.z = ckf->trans[2]*transScale;
			kf++;
			ckf++;
		}
		keyFrames = newKfs;
	}else{
		void *newKfs = RwMalloc(numFrames * sizeof(KeyFrame));
		KeyFrameCompressed *ckf = (KeyFrameCompressed*)keyFramesCompressed;
		KeyFrame *kf = (KeyFrame*)newKfs;
		for(i = 0; i < numFrames; i++){
			kf->rotation.x = ckf->rot[0]*rotScale;
			kf->rotation.y = ckf->rot[1]*rotScale;
			kf->rotation.z = ckf->rot[2]*rotScale;
			kf->rotation.w = ckf->rot[3]*rotScale;
			kf->deltaTime = ckf->deltaTime*timeScale;
			kf++;
			ckf++;
		}
		keyFrames = newKfs;
	}
	REGISTER_MEMPTR(&keyFrames);

	RwFree(keyFramesCompressed);
	keyFramesCompressed = nil;

	POP_MEMID();
}

void
CAnimBlendSequence::CompressKeyframes(void)
{
	int i;

	if(numFrames == 0)
		return;

	PUSH_MEMID(MEMID_ANIMATION);

	float rotScale = 4096.0f;
	float timeScale = 60.0f;
	float transScale = 128.0f;
	if(type & KF_TRANS){
		void *newKfs = RwMalloc(numFrames * sizeof(KeyFrameTransCompressed));
		KeyFrameTransCompressed *ckf = (KeyFrameTransCompressed*)newKfs;
		KeyFrameTrans *kf = (KeyFrameTrans*)keyFrames;
		for(i = 0; i < numFrames; i++){
			ckf->rot[0] = kf->rotation.x*rotScale;
			ckf->rot[1] = kf->rotation.y*rotScale;
			ckf->rot[2] = kf->rotation.z*rotScale;
			ckf->rot[3] = kf->rotation.w*rotScale;
			ckf->deltaTime = kf->deltaTime*timeScale + 0.5f;
			ckf->trans[0] = kf->translation.x*transScale;
			ckf->trans[1] = kf->translation.y*transScale;
			ckf->trans[2] = kf->translation.z*transScale;
			kf++;
			ckf++;
		}
		keyFramesCompressed = newKfs;
	}else{
		void *newKfs = RwMalloc(numFrames * sizeof(KeyFrameCompressed));
		KeyFrameCompressed *ckf = (KeyFrameCompressed*)newKfs;
		KeyFrame *kf = (KeyFrame*)keyFrames;
		for(i = 0; i < numFrames; i++){
			ckf->rot[0] = kf->rotation.x*rotScale;
			ckf->rot[1] = kf->rotation.y*rotScale;
			ckf->rot[2] = kf->rotation.z*rotScale;
			ckf->rot[3] = kf->rotation.w*rotScale;
			ckf->deltaTime = kf->deltaTime*timeScale + 0.5f;
			kf++;
			ckf++;
		}
		keyFramesCompressed = newKfs;
	}
	REGISTER_MEMPTR(&keyFramesCompressed);

	POP_MEMID();
}

void
CAnimBlendSequence::RemoveUncompressedData(void)
{
	if(numFrames == 0)
		return;
	CompressKeyframes();
	RwFree(keyFrames);
	keyFrames = nil;
}

#ifdef USE_CUSTOM_ALLOCATOR
bool
CAnimBlendSequence::MoveMemory(void)
{
	if(keyFrames){
		void *newaddr = gMainHeap.MoveMemory(keyFrames);
		if(newaddr != keyFrames){
			keyFrames = newaddr;
			return true;
		}
	}else if(keyFramesCompressed){
		void *newaddr = gMainHeap.MoveMemory(keyFramesCompressed);
		if(newaddr != keyFramesCompressed){
			keyFramesCompressed = newaddr;
			return true;
		}
	}
	return false;
}
#endif


code:
#pragma once

#include "Quaternion.h"

#ifdef MoveMemory
#undef MoveMemory	// windows shit
#endif

// TODO: put them somewhere else?
struct KeyFrame {
	CQuaternion rotation;
	float deltaTime;	// relative to previous key frame
};

struct KeyFrameTrans : KeyFrame {
	CVector translation;
};

struct KeyFrameCompressed {
	int16 rot[4];		// 4096
	int16 deltaTime;	// 60
};

struct KeyFrameTransCompressed : KeyFrameCompressed {
	int16 trans[3];		// 128
};

// The sequence of key frames of one animated node
class CAnimBlendSequence
{
public:
	enum {
		KF_ROT = 1,
		KF_TRANS = 2
	};
	int32 type;
	char name[24];
	int32 numFrames;
#ifdef PED_SKIN
	int16 boneTag;
#endif
	void *keyFrames;
	void *keyFramesCompressed;

	CAnimBlendSequence(void);
	virtual ~CAnimBlendSequence(void);
	void SetName(char *name);
	void SetNumFrames(int numFrames, bool translation);
	void RemoveQuaternionFlips(void);
	KeyFrame *GetKeyFrame(int n) {
		return type & KF_TRANS ?
			&((KeyFrameTrans*)keyFrames)[n] :
			&((KeyFrame*)keyFrames)[n];
	}
	bool HasTranslation(void) { return !!(type & KF_TRANS); }
	void Uncompress(void);
	void CompressKeyframes(void);
	void RemoveUncompressedData(void);
	bool MoveMemory(void);

#ifdef PED_SKIN
	void SetBoneTag(int tag) { boneTag = tag; }
#endif
};
#ifndef PED_SKIN
VALIDATE_SIZE(CAnimBlendSequence, 0x2C);
#endif